mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-12 11:31:08 +00:00
fix: preserve GPU stats while pinning clocks
This commit is contained in:
1 parent
a4447039b9
commit
4d86839910
7 files changed
+378
-2
No files matched your search
@@ -8,6 +8,7 @@ add_library(droiddeckwayland SHARED
|
||||
src/sc_layer.c
|
||||
src/ahb_swapchain.c
|
||||
src/waylandcomp_jni.c
|
||||
src/gpu_clock_pin.c
|
||||
src/droiddeck_ext.c
|
||||
src/wl_dmabuf.c
|
||||
src/wl_clipboard.c
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
#include "gpu_clock_pin.h"
|
||||
|
||||
#include <android/log.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <pthread.h>
|
||||
#include <stdatomic.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "../../adrenotools/src/hook/kgsl.h"
|
||||
|
||||
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
static pthread_t worker;
|
||||
static bool joinable;
|
||||
static atomic_bool running;
|
||||
|
||||
struct pin {
|
||||
int fd;
|
||||
unsigned int context;
|
||||
unsigned int memory;
|
||||
unsigned int timestamp;
|
||||
unsigned long gpuaddr;
|
||||
void *commands;
|
||||
size_t size;
|
||||
};
|
||||
|
||||
static struct pin pin = { .fd = -1 };
|
||||
|
||||
static void release_pin(void) {
|
||||
if (pin.context) {
|
||||
struct kgsl_drawctxt_destroy destroy = { .drawctxt_id = pin.context };
|
||||
ioctl(pin.fd, IOCTL_KGSL_DRAWCTXT_DESTROY, &destroy);
|
||||
}
|
||||
if (pin.commands) munmap(pin.commands, pin.size);
|
||||
if (pin.memory) {
|
||||
struct kgsl_gpumem_free_id free_memory = { .id = pin.memory };
|
||||
ioctl(pin.fd, IOCTL_KGSL_GPUMEM_FREE_ID, &free_memory);
|
||||
}
|
||||
if (pin.fd >= 0) close(pin.fd);
|
||||
pin = (struct pin) { .fd = -1 };
|
||||
}
|
||||
|
||||
static bool submit_pin(void) {
|
||||
struct kgsl_command_object object = {
|
||||
.gpuaddr = pin.gpuaddr,
|
||||
.size = 2 * sizeof(uint32_t),
|
||||
.flags = KGSL_CMDLIST_IB,
|
||||
.id = pin.memory,
|
||||
};
|
||||
struct kgsl_gpu_command command = {
|
||||
.flags = KGSL_CMDBATCH_PWR_CONSTRAINT,
|
||||
.cmdlist = (uintptr_t)&object,
|
||||
.cmdsize = sizeof(object),
|
||||
.numcmds = 1,
|
||||
.context_id = pin.context,
|
||||
};
|
||||
if (ioctl(pin.fd, IOCTL_KGSL_GPU_COMMAND, &command) < 0) return false;
|
||||
pin.timestamp = command.timestamp;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool prepare_pin(void) {
|
||||
pin.fd = open("/dev/kgsl-3d0", O_RDWR | O_CLOEXEC);
|
||||
if (pin.fd < 0) return false;
|
||||
struct kgsl_drawctxt_create context = {
|
||||
.flags = KGSL_CONTEXT_NO_GMEM_ALLOC | KGSL_CONTEXT_PREAMBLE | KGSL_CONTEXT_PWR_CONSTRAINT,
|
||||
};
|
||||
if (ioctl(pin.fd, IOCTL_KGSL_DRAWCTXT_CREATE, &context) < 0) return false;
|
||||
pin.context = context.drawctxt_id;
|
||||
struct kgsl_device_constraint_pwrlevel level = { .level = KGSL_CONSTRAINT_PWR_MAX };
|
||||
struct kgsl_device_constraint constraint = {
|
||||
.type = KGSL_CONSTRAINT_PWRLEVEL,
|
||||
.context_id = pin.context,
|
||||
.data = &level,
|
||||
.size = sizeof(level),
|
||||
};
|
||||
struct kgsl_device_getproperty property = {
|
||||
.type = KGSL_PROP_PWR_CONSTRAINT,
|
||||
.value = &constraint,
|
||||
.sizebytes = sizeof(constraint),
|
||||
};
|
||||
if (ioctl(pin.fd, IOCTL_KGSL_SETPROPERTY, &property) < 0) return false;
|
||||
struct kgsl_gpumem_alloc_id memory = { .size = 4096 };
|
||||
if (ioctl(pin.fd, IOCTL_KGSL_GPUMEM_ALLOC_ID, &memory) < 0) return false;
|
||||
pin.memory = memory.id;
|
||||
pin.gpuaddr = memory.gpuaddr;
|
||||
pin.size = memory.mmapsize;
|
||||
void *commands = mmap(NULL, pin.size, PROT_READ | PROT_WRITE, MAP_SHARED, pin.fd,
|
||||
(off_t)pin.memory << 12);
|
||||
if (commands == MAP_FAILED) return false;
|
||||
pin.commands = commands;
|
||||
// Type-7 CP_NOP, one payload dword; both odd-parity bits are zero.
|
||||
uint32_t *words = commands;
|
||||
words[0] = 0x70100001;
|
||||
words[1] = 0;
|
||||
struct kgsl_gpumem_sync_cache sync = { .id = pin.memory, .op = KGSL_GPUMEM_CACHE_CLEAN };
|
||||
if (ioctl(pin.fd, IOCTL_KGSL_GPUMEM_SYNC_CACHE, &sync) < 0) return false;
|
||||
return submit_pin();
|
||||
}
|
||||
|
||||
static void *hold_pin(void *unused) {
|
||||
(void)unused;
|
||||
while (atomic_load(&running)) {
|
||||
struct kgsl_device_waittimestamp_ctxtid wait = {
|
||||
.context_id = pin.context,
|
||||
.timestamp = pin.timestamp,
|
||||
.timeout = 100,
|
||||
};
|
||||
if (ioctl(pin.fd, IOCTL_KGSL_DEVICE_WAITTIMESTAMP_CTXTID, &wait) < 0) {
|
||||
if (errno == EINTR || errno == ETIMEDOUT) continue;
|
||||
break;
|
||||
}
|
||||
// Constraints expire after idle_timer (normally 80 ms); renew with a NOP, keeping devfreq alive.
|
||||
usleep(25000);
|
||||
if (atomic_load(&running) && !submit_pin()) break;
|
||||
}
|
||||
if (atomic_exchange(&running, false))
|
||||
__android_log_print(ANDROID_LOG_WARN, "GpuClockPin", "constraint stopped: %s", strerror(errno));
|
||||
release_pin();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool gpu_clock_pin_set(bool on) {
|
||||
pthread_mutex_lock(&lock);
|
||||
if (joinable) {
|
||||
if (on && atomic_load(&running)) {
|
||||
pthread_mutex_unlock(&lock);
|
||||
return true;
|
||||
}
|
||||
atomic_store(&running, false);
|
||||
pthread_join(worker, NULL);
|
||||
joinable = false;
|
||||
}
|
||||
bool ok = true;
|
||||
if (on) {
|
||||
ok = prepare_pin();
|
||||
if (ok) {
|
||||
atomic_store(&running, true);
|
||||
int error = pthread_create(&worker, NULL, hold_pin, NULL);
|
||||
if (error) {
|
||||
atomic_store(&running, false);
|
||||
errno = error;
|
||||
ok = false;
|
||||
} else {
|
||||
joinable = true;
|
||||
}
|
||||
}
|
||||
if (!ok) {
|
||||
int error = errno;
|
||||
release_pin();
|
||||
errno = error;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&lock);
|
||||
return ok;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
bool gpu_clock_pin_set(bool on);
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "effects_chain.h"
|
||||
#include "cardboard.h"
|
||||
#include "droiddeck_color.h"
|
||||
#include "gpu_clock_pin.h"
|
||||
|
||||
extern int droiddeck_wayland_run(void);
|
||||
extern void droiddeck_wayland_set_scale_mode(int mode, int alignment);
|
||||
@@ -634,8 +635,14 @@ Java_com_droiddeck_launcher_wayland_WaylandCompositor_nativeTextInputDelete(JNIE
|
||||
extern void adrenotools_set_turbo(bool turbo);
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_droiddeck_launcher_wayland_WaylandCompositor_nativeSetGpuTurbo(JNIEnv *env, jclass clazz, jboolean on) {
|
||||
(void)env; (void)clazz;
|
||||
adrenotools_set_turbo(on == JNI_TRUE);
|
||||
(void)clazz;
|
||||
if (!gpu_clock_pin_set(on == JNI_TRUE)) {
|
||||
int saved_errno = errno;
|
||||
jclass error = (*env)->FindClass(env, "java/lang/IllegalStateException");
|
||||
if (error) (*env)->ThrowNew(env, error, strerror(saved_errno));
|
||||
}
|
||||
// Clear a legacy global pin when recovering its marker after an app upgrade or crash.
|
||||
if (on != JNI_TRUE) adrenotools_set_turbo(false);
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
|
||||
@@ -26,6 +26,7 @@ object GpuClockPin {
|
||||
pinned = true
|
||||
"gpu clock: held at its top"
|
||||
} catch (t: Throwable) {
|
||||
File(context.filesDir, MARKER).delete()
|
||||
Log.w(TAG, "pin", t)
|
||||
"gpu clock: pin failed"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <pthread.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdatomic.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "../../app/src/main/cpp/adrenotools/src/hook/kgsl.h"
|
||||
#include "../../app/src/main/cpp/waylandcomp/src/gpu_clock_pin.h"
|
||||
|
||||
enum { NONE, OPEN, CONTEXT, CONSTRAINT, MEMORY, MAP, SYNC, SUBMIT, THREAD, WAIT };
|
||||
static atomic_int failure, descriptors, contexts, memories, mappings, submissions;
|
||||
static atomic_bool timeout_wait;
|
||||
static uint32_t commands[1024];
|
||||
|
||||
int __android_log_print(int priority, const char *tag, const char *format, ...) {
|
||||
(void)priority; (void)tag; (void)format;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int __wrap_open(const char *path, int flags, ...) {
|
||||
(void)flags;
|
||||
assert(!strcmp(path, "/dev/kgsl-3d0"));
|
||||
if (atomic_load(&failure) == OPEN) { errno = EACCES; return -1; }
|
||||
assert(atomic_fetch_add(&descriptors, 1) == 0);
|
||||
return 91;
|
||||
}
|
||||
|
||||
int __wrap_close(int fd) {
|
||||
assert(fd == 91);
|
||||
assert(atomic_fetch_sub(&descriptors, 1) == 1);
|
||||
errno = EBADF;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void *__wrap_mmap(void *address, size_t size, int protection, int flags, int fd, off_t offset) {
|
||||
(void)address; (void)protection; (void)flags;
|
||||
assert(fd == 91 && size == sizeof(commands) && offset == (23 << 12));
|
||||
if (atomic_load(&failure) == MAP) { errno = EACCES; return MAP_FAILED; }
|
||||
assert(atomic_fetch_add(&mappings, 1) == 0);
|
||||
return commands;
|
||||
}
|
||||
|
||||
int __wrap_munmap(void *address, size_t size) {
|
||||
assert(address == commands && size == sizeof(commands));
|
||||
assert(atomic_fetch_sub(&mappings, 1) == 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef __ANDROID__
|
||||
int __wrap_ioctl(int fd, int request, ...) {
|
||||
#else
|
||||
int __wrap_ioctl(int fd, unsigned long request, ...) {
|
||||
#endif
|
||||
assert(fd == 91);
|
||||
va_list args;
|
||||
va_start(args, request);
|
||||
void *arg = va_arg(args, void *);
|
||||
va_end(args);
|
||||
int step = NONE;
|
||||
switch ((unsigned int)request) {
|
||||
case IOCTL_KGSL_DRAWCTXT_CREATE: step = CONTEXT; break;
|
||||
case IOCTL_KGSL_SETPROPERTY: step = CONSTRAINT; break;
|
||||
case IOCTL_KGSL_GPUMEM_ALLOC_ID: step = MEMORY; break;
|
||||
case IOCTL_KGSL_GPUMEM_SYNC_CACHE: step = SYNC; break;
|
||||
case IOCTL_KGSL_GPU_COMMAND: step = SUBMIT; break;
|
||||
case IOCTL_KGSL_DEVICE_WAITTIMESTAMP_CTXTID: step = WAIT; break;
|
||||
}
|
||||
if (step && atomic_load(&failure) == step) { errno = EACCES; return -1; }
|
||||
switch ((unsigned int)request) {
|
||||
case IOCTL_KGSL_DRAWCTXT_CREATE: {
|
||||
struct kgsl_drawctxt_create *context = arg;
|
||||
assert(context->flags & KGSL_CONTEXT_PWR_CONSTRAINT);
|
||||
context->drawctxt_id = 17;
|
||||
atomic_fetch_add(&contexts, 1);
|
||||
break;
|
||||
}
|
||||
case IOCTL_KGSL_SETPROPERTY: {
|
||||
struct kgsl_device_getproperty *property = arg;
|
||||
assert(property->type == KGSL_PROP_PWR_CONSTRAINT);
|
||||
struct kgsl_device_constraint *constraint = property->value;
|
||||
assert(constraint->context_id == 17 && constraint->type == KGSL_CONSTRAINT_PWRLEVEL);
|
||||
assert(((struct kgsl_device_constraint_pwrlevel *)constraint->data)->level == KGSL_CONSTRAINT_PWR_MAX);
|
||||
break;
|
||||
}
|
||||
case IOCTL_KGSL_GPUMEM_ALLOC_ID: {
|
||||
struct kgsl_gpumem_alloc_id *memory = arg;
|
||||
memory->id = 23; memory->gpuaddr = 0x100000; memory->mmapsize = sizeof(commands);
|
||||
atomic_fetch_add(&memories, 1);
|
||||
break;
|
||||
}
|
||||
case IOCTL_KGSL_GPUMEM_SYNC_CACHE:
|
||||
assert(((struct kgsl_gpumem_sync_cache *)arg)->op == KGSL_GPUMEM_CACHE_CLEAN);
|
||||
break;
|
||||
case IOCTL_KGSL_GPU_COMMAND: {
|
||||
struct kgsl_gpu_command *command = arg;
|
||||
struct kgsl_command_object *object = (void *)(uintptr_t)command->cmdlist;
|
||||
assert(command->context_id == 17 && command->numcmds == 1);
|
||||
assert(command->flags & KGSL_CMDBATCH_PWR_CONSTRAINT);
|
||||
assert(object->id == 23 && object->gpuaddr == 0x100000 && object->size == 8);
|
||||
assert(commands[0] == 0x70100001 && commands[1] == 0);
|
||||
command->timestamp = atomic_fetch_add(&submissions, 1) + 1;
|
||||
break;
|
||||
}
|
||||
case IOCTL_KGSL_DEVICE_WAITTIMESTAMP_CTXTID:
|
||||
if (atomic_load(&timeout_wait)) { errno = ETIMEDOUT; return -1; }
|
||||
break;
|
||||
case IOCTL_KGSL_DRAWCTXT_DESTROY:
|
||||
assert(atomic_fetch_sub(&contexts, 1) == 1);
|
||||
break;
|
||||
case IOCTL_KGSL_GPUMEM_FREE_ID:
|
||||
assert(atomic_fetch_sub(&memories, 1) == 1);
|
||||
break;
|
||||
default: assert(!"unexpected ioctl");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int __real_pthread_create(pthread_t *, const pthread_attr_t *, void *(*)(void *), void *);
|
||||
int __wrap_pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start)(void *), void *arg) {
|
||||
if (atomic_load(&failure) == THREAD) return EAGAIN;
|
||||
return __real_pthread_create(thread, attr, start, arg);
|
||||
}
|
||||
|
||||
static void assert_released(void) {
|
||||
assert(!atomic_load(&descriptors) && !atomic_load(&contexts));
|
||||
assert(!atomic_load(&memories) && !atomic_load(&mappings));
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
assert(gpu_clock_pin_set(false));
|
||||
assert_released();
|
||||
for (int step = OPEN; step <= THREAD; ++step) {
|
||||
atomic_store(&failure, step);
|
||||
assert(!gpu_clock_pin_set(true));
|
||||
assert(errno == (step == THREAD ? EAGAIN : EACCES));
|
||||
assert_released();
|
||||
}
|
||||
atomic_store(&failure, NONE);
|
||||
atomic_store(&submissions, 0);
|
||||
assert(gpu_clock_pin_set(true));
|
||||
assert(gpu_clock_pin_set(true));
|
||||
usleep(80000);
|
||||
assert(atomic_load(&submissions) > 1);
|
||||
assert(gpu_clock_pin_set(false));
|
||||
assert(gpu_clock_pin_set(false));
|
||||
assert_released();
|
||||
|
||||
atomic_store(&timeout_wait, true);
|
||||
atomic_store(&submissions, 0);
|
||||
assert(gpu_clock_pin_set(true));
|
||||
usleep(80000);
|
||||
assert(atomic_load(&submissions) == 1);
|
||||
assert(gpu_clock_pin_set(false));
|
||||
assert_released();
|
||||
atomic_store(&timeout_wait, false);
|
||||
|
||||
atomic_store(&failure, WAIT);
|
||||
assert(gpu_clock_pin_set(true));
|
||||
for (int i = 0; i < 100 && atomic_load(&descriptors); ++i) usleep(10000);
|
||||
assert_released();
|
||||
atomic_store(&failure, NONE);
|
||||
assert(gpu_clock_pin_set(true));
|
||||
assert(gpu_clock_pin_set(false));
|
||||
assert_released();
|
||||
puts("gpu clock pin: failure cleanup, renewal, bounded queue and restart passed");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import pathlib
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
|
||||
ROOT = pathlib.Path(__file__).resolve().parents[2]
|
||||
|
||||
|
||||
class GpuClockPinTest(unittest.TestCase):
|
||||
@unittest.skipUnless(sys.platform.startswith('linux') and shutil.which('cc'), 'requires a Linux C compiler')
|
||||
def test_constraint_lifecycle(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
directory = pathlib.Path(tmp)
|
||||
(directory / 'android').mkdir()
|
||||
(directory / 'android/log.h').write_text(
|
||||
'#define ANDROID_LOG_WARN 5\n'
|
||||
'int __android_log_print(int, const char *, const char *, ...);\n')
|
||||
binary = directory / 'gpu-clock-pin-test'
|
||||
subprocess.run([
|
||||
'cc', '-std=c11', '-D_DEFAULT_SOURCE', '-D__user=', '-Wall', '-Wextra', '-Werror',
|
||||
'-I', str(directory), '-pthread',
|
||||
str(ROOT / 'tools/tests/test_gpu_clock_pin.c'),
|
||||
str(ROOT / 'app/src/main/cpp/waylandcomp/src/gpu_clock_pin.c'),
|
||||
'-Wl,--wrap=open,--wrap=close,--wrap=mmap,--wrap=munmap,--wrap=ioctl,--wrap=pthread_create',
|
||||
'-o', str(binary),
|
||||
], check=True)
|
||||
subprocess.run([str(binary)], check=True, timeout=10)
|
||||
Reference in new issue
Block a user