From ef5fde9be007e8f307316617a0305b4a4db01cfb Mon Sep 17 00:00:00 2001 From: Robbe Derks Date: Thu, 30 Apr 2026 19:06:33 +0200 Subject: [PATCH] power blip test (#558) * add power blip test * synchronous test, and bump default governor max freq to compensate * ditch DSP for speeding up teardown * new abl * limit max freq after --------- Co-authored-by: Adeeb Shihadeh --- Dockerfile.agnos | 5 + firmware/abl.img | 2 +- userspace/power_burn/power_burn_max.c | 234 ++++++++++++++++++++++++++ userspace/usr/comma/comma.sh | 7 + userspace/usr/comma/init.qcom.sh | 6 +- 5 files changed, 250 insertions(+), 4 deletions(-) create mode 100644 userspace/power_burn/power_burn_max.c diff --git a/Dockerfile.agnos b/Dockerfile.agnos index 6e52b66..45e796e 100644 --- a/Dockerfile.agnos +++ b/Dockerfile.agnos @@ -161,6 +161,11 @@ COPY ./userspace/libs/* /usr/lib/aarch64-linux-gnu/ COPY ./userspace/libs32/* /usr/lib/arm-linux-gnueabihf/ COPY ./output/linux-headers/include/ /usr/include/ +COPY ./userspace/power_burn/power_burn_max.c /tmp/agnos/ +RUN gcc -O2 -o /home/$USERNAME/power_burn_max /tmp/agnos/power_burn_max.c \ + -lOpenCL -lpthread -ldl && rm /tmp/agnos/power_burn_max.c \ + && chown $USERNAME: /home/$USERNAME/power_burn_max + # Weston with hacked touch rotate and color correction COPY ./userspace/files/weston /usr/bin/weston COPY ./userspace/files/gl-renderer.so /usr/lib/arm-linux-gnueabihf/weston diff --git a/firmware/abl.img b/firmware/abl.img index a79ce16..9a8649f 100644 --- a/firmware/abl.img +++ b/firmware/abl.img @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:556bbb4ed1c671402b217bd2f3c07edce4f88b0bbd64e92241b82e396aa9ebee +oid sha256:10c0abe08e3c30374852a065143a76f33169a3578aa78a61ee7662b553dfa9f1 size 274432 diff --git a/userspace/power_burn/power_burn_max.c b/userspace/power_burn/power_burn_max.c new file mode 100644 index 0000000..7b3a361 --- /dev/null +++ b/userspace/power_burn/power_burn_max.c @@ -0,0 +1,234 @@ +/* + * power_burn_max - CPU/GPU/LED power burn for tici. + * + * power_burn_max [seconds] [n_perf_cores] + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static volatile sig_atomic_t running = 1; +static volatile sig_atomic_t exit_signum = 0; +static void stop_sig(int s) { exit_signum = s; running = 0; } + +/* End-of-ramoops DDR page. Survives warm reset if DDR stays in self-refresh + * across a PSU dropout, so ABL reads this cookie on boot to detect a mid-burn + * power cut. */ +#define POWER_TEST_ADDR 0xB03FFFFCull +#define POWER_TEST_MAGIC 0x57505354u /* "WPST" */ + +static void write_magic(uint32_t val) { + int fd = open("/dev/mem", O_RDWR | O_SYNC); + if (fd < 0) { perror("open /dev/mem"); return; } + const uintptr_t page = POWER_TEST_ADDR & ~0xFFFull; + const uintptr_t off = POWER_TEST_ADDR & 0xFFFull; + void *p = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, fd, (off_t)page); + if (p == MAP_FAILED) { perror("mmap /dev/mem"); close(fd); return; } + *(volatile uint32_t *)((char *)p + off) = val; + msync(p, 0x1000, MS_SYNC); + munmap(p, 0x1000); + close(fd); +} + +#define MAX_SAVED 96 +static struct { char path[192]; char val[48]; } saved[MAX_SAVED]; +static int nsaved; + +static int sysfs_read(const char *p, char *b, int sz) { + FILE *f = fopen(p, "r"); + if (!f) return -1; + if (!fgets(b, sz, f)) { fclose(f); return -1; } + fclose(f); + b[strcspn(b, "\n")] = 0; + return 0; +} +static void sysfs_write(const char *p, const char *v) { + FILE *f = fopen(p, "w"); + if (!f) return; + fputs(v, f); + fclose(f); +} +static void save_and_write(const char *p, const char *v) { + if (nsaved < MAX_SAVED && sysfs_read(p, saved[nsaved].val, sizeof(saved[0].val)) == 0) { + strncpy(saved[nsaved].path, p, sizeof(saved[0].path) - 1); + nsaved++; + } + sysfs_write(p, v); +} +static void restore_all(void) { + for (int i = nsaved - 1; i >= 0; i--) sysfs_write(saved[i].path, saved[i].val); +} +static void fatal_sig(int s) { + const char *m = "[power_burn_max] fault: signal\n"; + switch (s) { + case SIGABRT: m = "[power_burn_max] fault: SIGABRT\n"; break; + case SIGSEGV: m = "[power_burn_max] fault: SIGSEGV\n"; break; + case SIGBUS: m = "[power_burn_max] fault: SIGBUS\n"; break; + } + (void)!write(2, m, strlen(m)); + write_magic(0); + restore_all(); + _exit(128 + s); +} + +static void leds_on(void) { + save_and_write("/sys/class/leds/led:torch_0/brightness", "500"); + save_and_write("/sys/class/leds/led:torch_1/brightness", "500"); + save_and_write("/sys/class/leds/led:torch_2/brightness", "300"); + save_and_write("/sys/class/leds/led:switch_0/brightness", "255"); + save_and_write("/sys/class/leds/led:switch_1/brightness", "255"); + save_and_write("/sys/class/leds/led:switch_2/brightness", "255"); +} +static void freq_max(void) { + /* max_pwrlevel=0 unlocks the 710MHz step that Adreno gates off by default + * even under governor=performance. */ + save_and_write("/sys/class/kgsl/kgsl-3d0/devfreq/governor", "performance"); + save_and_write("/sys/class/kgsl/kgsl-3d0/max_pwrlevel", "0"); + save_and_write("/sys/class/kgsl/kgsl-3d0/force_bus_on", "1"); + save_and_write("/sys/class/kgsl/kgsl-3d0/force_clk_on", "1"); + save_and_write("/sys/class/kgsl/kgsl-3d0/force_rail_on", "1"); + save_and_write("/sys/class/kgsl/kgsl-3d0/force_no_nap", "1"); + save_and_write("/sys/class/devfreq/soc:qcom,gpubw/governor", "performance"); + save_and_write("/sys/class/devfreq/soc:qcom,cpubw/governor", "performance"); + save_and_write("/sys/class/devfreq/soc:qcom,llccbw/governor", "performance"); + save_and_write("/sys/class/devfreq/soc:qcom,l3-cpu0/governor","performance"); + save_and_write("/sys/class/devfreq/soc:qcom,l3-cpu4/governor","performance"); + save_and_write("/sys/class/devfreq/soc:qcom,l3-cdsp/governor","performance"); +} +static void pin_perf_core(int cpu) { + char path[128]; + snprintf(path, sizeof(path), "/sys/devices/system/cpu/cpu%d/online", cpu); + save_and_write(path, "1"); + snprintf(path, sizeof(path), "/sys/devices/system/cpu/cpu%d/cpufreq/scaling_governor", cpu); + save_and_write(path, "performance"); +} + +static void *cpu_burn(void *arg) { + int core = (int)(intptr_t)arg; + cpu_set_t set; CPU_ZERO(&set); CPU_SET(core, &set); + pthread_setaffinity_np(pthread_self(), sizeof(set), &set); + + /* Contraction map x := 0.5*x + 0.25 — fixed point at 0.5, keeps values + * bounded away from subnormal/NaN paths that short-circuit the FPU. */ + float32x4_t v0 = vdupq_n_f32(0.50f + core * 0.01f); + float32x4_t v1 = vdupq_n_f32(0.25f); + float32x4_t v2 = vdupq_n_f32(0.75f); + float32x4_t v3 = vdupq_n_f32(0.125f); + float32x4_t v4 = vdupq_n_f32(0.875f); + float32x4_t v5 = vdupq_n_f32(0.625f); + float32x4_t v6 = vdupq_n_f32(0.375f); + float32x4_t v7 = vdupq_n_f32(0.1875f); + const float32x4_t half = vdupq_n_f32(0.5f); + const float32x4_t quarter = vdupq_n_f32(0.25f); + + while (running) { + for (int i = 0; i < 10000; i++) { + v0 = vfmaq_f32(quarter, v0, half); + v1 = vfmaq_f32(quarter, v1, half); + v2 = vfmaq_f32(quarter, v2, half); + v3 = vfmaq_f32(quarter, v3, half); + v4 = vfmaq_f32(quarter, v4, half); + v5 = vfmaq_f32(quarter, v5, half); + v6 = vfmaq_f32(quarter, v6, half); + v7 = vfmaq_f32(quarter, v7, half); + } + } + volatile float sink = vgetq_lane_f32(v0,0) + vgetq_lane_f32(v1,0) + vgetq_lane_f32(v2,0) + + vgetq_lane_f32(v3,0) + vgetq_lane_f32(v4,0) + vgetq_lane_f32(v5,0) + + vgetq_lane_f32(v6,0) + vgetq_lane_f32(v7,0); + (void)sink; + return NULL; +} + +static const char *GPU_KSRC = + "__kernel void burn(__global float *buf, int n) {" + " int id = get_global_id(0);" + " float4 a = (float4)(0.6f,0.7f,0.8f,0.9f) + (float)(id&15)*0.001f;" + " float4 b=a*0.5f, c=a*0.75f, d=a*0.8f, e=a*0.9f, f=a*0.55f, g=a*0.65f, h=a*0.85f;" + " for (int i = 0; i < n; i++) {" + " a = mad(a,(float4)(0.5f),(float4)(0.25f));" + " b = mad(b,(float4)(0.5f),(float4)(0.25f));" + " c = mad(c,(float4)(0.5f),(float4)(0.25f));" + " d = mad(d,(float4)(0.5f),(float4)(0.25f));" + " e = mad(e,(float4)(0.5f),(float4)(0.25f));" + " f = mad(f,(float4)(0.5f),(float4)(0.25f));" + " g = mad(g,(float4)(0.5f),(float4)(0.25f));" + " h = mad(h,(float4)(0.5f),(float4)(0.25f));" + " }" + " if ((id&1023)==0) buf[id/1024] = a.x+b.x+c.x+d.x+e.x+f.x+g.x+h.x;" + "}"; + +int main(int argc, char **argv) { + int duration = 1, n_perf = 4; + if (argc > 1) duration = atoi(argv[1]); + if (argc > 2) n_perf = atoi(argv[2]); + if (n_perf < 0) n_perf = 0; if (n_perf > 4) n_perf = 4; + + signal(SIGTERM, stop_sig); + signal(SIGINT, stop_sig); + signal(SIGABRT, fatal_sig); + signal(SIGSEGV, fatal_sig); + signal(SIGBUS, fatal_sig); + + freq_max(); + for (int i = 0; i < n_perf; i++) pin_perf_core(4 + i); + + cl_platform_id plat; cl_device_id dev; cl_int err; + clGetPlatformIDs(1, &plat, NULL); + clGetDeviceIDs(plat, CL_DEVICE_TYPE_GPU, 1, &dev, NULL); + cl_context ctx = clCreateContext(NULL, 1, &dev, NULL, NULL, &err); + cl_command_queue q = clCreateCommandQueue(ctx, dev, 0, &err); + cl_program prog = clCreateProgramWithSource(ctx, 1, &GPU_KSRC, NULL, &err); + clBuildProgram(prog, 1, &dev, "-cl-fast-relaxed-math -cl-mad-enable", NULL, NULL); + cl_kernel kern = clCreateKernel(prog, "burn", &err); + cl_mem buf = clCreateBuffer(ctx, CL_MEM_WRITE_ONLY, 4096 * sizeof(float), NULL, &err); + int n_inner = 200; + clSetKernelArg(kern, 0, sizeof(cl_mem), &buf); + clSetKernelArg(kern, 1, sizeof(int), &n_inner); + const size_t gs = 65536; + + write_magic(POWER_TEST_MAGIC); + + pthread_t cpu_th[4]; + for (int i = 0; i < n_perf; i++) + pthread_create(&cpu_th[i], NULL, cpu_burn, (void *)(intptr_t)(4 + i)); + + leds_on(); + + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + uint64_t t_end_ns = (uint64_t)ts.tv_sec * 1000000000ull + ts.tv_nsec + (uint64_t)duration * 1000000000ull; + while (running) { + clEnqueueNDRangeKernel(q, kern, 1, NULL, &gs, NULL, 0, NULL, NULL); + clFinish(q); + if (duration > 0) { + clock_gettime(CLOCK_MONOTONIC, &ts); + if ((uint64_t)ts.tv_sec * 1000000000ull + ts.tv_nsec >= t_end_ns) break; + } + } + running = 0; + + for (int i = 0; i < n_perf; i++) pthread_join(cpu_th[i], NULL); + + clReleaseMemObject(buf); + clReleaseKernel(kern); + clReleaseProgram(prog); + clReleaseCommandQueue(q); + clReleaseContext(ctx); + restore_all(); + write_magic(0); + if (exit_signum) fprintf(stderr, "[power_burn_max] interrupted by signal %d\n", (int)exit_signum); + return 0; +} diff --git a/userspace/usr/comma/comma.sh b/userspace/usr/comma/comma.sh index 526fa8d..56bafd8 100755 --- a/userspace/usr/comma/comma.sh +++ b/userspace/usr/comma/comma.sh @@ -8,6 +8,13 @@ CONTINUE="/data/continue.sh" INSTALLER="/tmp/installer" RESET_TRIGGER="/data/__system_reset__" +# blip power to ~10W to see if the PSU is stable +sudo timeout --kill-after=2 5 /home/comma/power_burn_max + +# use max freq to boot up quickly, then limit +echo 1689600 | sudo tee /sys/devices/system/cpu/cpufreq/policy0/scaling_max_freq +echo 1689600 | sudo tee /sys/devices/system/cpu/cpufreq/policy4/scaling_max_freq + echo "waiting for magic" for i in {1..200}; do if systemctl is-active --quiet magic && [ -S /tmp/drmfd.sock ]; then diff --git a/userspace/usr/comma/init.qcom.sh b/userspace/usr/comma/init.qcom.sh index ea8bf6e..7fbb99c 100755 --- a/userspace/usr/comma/init.qcom.sh +++ b/userspace/usr/comma/init.qcom.sh @@ -5,9 +5,9 @@ for i in {0..7}; do echo "related" | sudo tee /sys/bus/msm_subsys/devices/subsys${i}/restart_level done -# use max freq to boot up quickly, then limit -echo 1689600 | sudo tee /sys/devices/system/cpu/cpufreq/policy0/scaling_max_freq -echo 1689600 | sudo tee /sys/devices/system/cpu/cpufreq/policy4/scaling_max_freq +# raise scaling_max so policy=performance can reach the BOOST top step +echo 2649600 | sudo tee /sys/devices/system/cpu/cpufreq/policy0/scaling_max_freq +echo 2649600 | sudo tee /sys/devices/system/cpu/cpufreq/policy4/scaling_max_freq # boot wifi echo 1 | sudo tee /sys/kernel/boot_wlan/boot_wlan