189 lines
7.0 KiB
C
189 lines
7.0 KiB
C
#define _POSIX_C_SOURCE 200809L
|
|
#include "h618_hub75.h"
|
|
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
|
|
static double monotonic_seconds(void) {
|
|
struct timespec value;
|
|
clock_gettime(CLOCK_MONOTONIC, &value);
|
|
return (double)value.tv_sec + (double)value.tv_nsec / 1000000000.0;
|
|
}
|
|
|
|
static int integer_argument(const char *value, const char *name) {
|
|
char *end = NULL;
|
|
long parsed = strtol(value, &end, 10);
|
|
if (value[0] == '\0' || end == NULL || *end != '\0' || parsed < 0 || parsed > 1000000) {
|
|
fprintf(stderr, "invalid %s: %s\n", name, value);
|
|
exit(2);
|
|
}
|
|
return (int)parsed;
|
|
}
|
|
|
|
static int hex_nibble(char value) {
|
|
if (value >= '0' && value <= '9') return value - '0';
|
|
if (value >= 'a' && value <= 'f') return value - 'a' + 10;
|
|
if (value >= 'A' && value <= 'F') return value - 'A' + 10;
|
|
return -1;
|
|
}
|
|
|
|
static void color_argument(const char *value, uint8_t output[3]) {
|
|
if (strlen(value) != 6) {
|
|
fprintf(stderr, "invalid color: %s (expected RRGGBB)\n", value);
|
|
exit(2);
|
|
}
|
|
for (unsigned channel = 0; channel < 3; ++channel) {
|
|
int high = hex_nibble(value[channel * 2]);
|
|
int low = hex_nibble(value[channel * 2 + 1]);
|
|
if (high < 0 || low < 0) {
|
|
fprintf(stderr, "invalid color: %s (expected RRGGBB)\n", value);
|
|
exit(2);
|
|
}
|
|
output[channel] = (uint8_t)((high << 4) | low);
|
|
}
|
|
}
|
|
|
|
int main(int argc, char **argv) {
|
|
int duration = 10;
|
|
int refresh_rate = 100;
|
|
int brightness = 40;
|
|
int use_dev_mem = 0;
|
|
int use_static_color = 0;
|
|
uint8_t static_color[3] = {0};
|
|
for (int index = 1; index < argc; ++index) {
|
|
if (strcmp(argv[index], "--duration") == 0 && index + 1 < argc) {
|
|
duration = integer_argument(argv[++index], "duration");
|
|
} else if (strcmp(argv[index], "--refresh-rate") == 0 && index + 1 < argc) {
|
|
refresh_rate = integer_argument(argv[++index], "refresh rate");
|
|
} else if (strcmp(argv[index], "--brightness") == 0 && index + 1 < argc) {
|
|
brightness = integer_argument(argv[++index], "brightness");
|
|
} else if (strcmp(argv[index], "--dev-mem") == 0) {
|
|
use_dev_mem = 1;
|
|
} else if (strcmp(argv[index], "--color") == 0 && index + 1 < argc) {
|
|
color_argument(argv[++index], static_color);
|
|
use_static_color = 1;
|
|
} else {
|
|
fprintf(stderr, "usage: %s [--duration N] [--refresh-rate N] [--brightness N] [--color RRGGBB] [--dev-mem]\n", argv[0]);
|
|
return 2;
|
|
}
|
|
}
|
|
if (duration < 1) {
|
|
fputs("duration must be at least one second\n", stderr);
|
|
return 2;
|
|
}
|
|
|
|
hub75_config config = {
|
|
.brightness = brightness,
|
|
.refresh_rate_hz = refresh_rate,
|
|
.cpu_affinity = 3,
|
|
.realtime_priority = 50,
|
|
.use_dev_mem = use_dev_mem,
|
|
};
|
|
char error[512] = "";
|
|
hub75_context *context = hub75_create(&config, error, sizeof(error));
|
|
if (context == NULL) {
|
|
fprintf(stderr, "create failed: %s\n", error);
|
|
return 1;
|
|
}
|
|
if (hub75_start(context) != 0) {
|
|
fputs("start failed\n", stderr);
|
|
hub75_destroy(context);
|
|
return 1;
|
|
}
|
|
|
|
uint8_t frame[HUB75_FRAME_BYTES];
|
|
if (use_static_color) {
|
|
for (unsigned pixel = 0; pixel < HUB75_WIDTH * HUB75_HEIGHT; ++pixel) {
|
|
memcpy(frame + pixel * 3u, static_color, sizeof(static_color));
|
|
}
|
|
if (hub75_submit_rgb(context, frame, sizeof(frame)) != 0) {
|
|
fputs("initial static submit failed\n", stderr);
|
|
hub75_destroy(context);
|
|
return 1;
|
|
}
|
|
}
|
|
double started = monotonic_seconds();
|
|
unsigned submitted = 0;
|
|
while (monotonic_seconds() - started < duration) {
|
|
if (!use_static_color) {
|
|
for (unsigned pixel = 0; pixel < HUB75_WIDTH * HUB75_HEIGHT; ++pixel) {
|
|
frame[pixel * 3] = (uint8_t)(pixel + submitted);
|
|
frame[pixel * 3 + 1] = (uint8_t)(pixel * 3u + submitted);
|
|
frame[pixel * 3 + 2] = (uint8_t)(pixel * 7u + submitted);
|
|
}
|
|
if (hub75_submit_rgb(context, frame, sizeof(frame)) != 0) {
|
|
fputs("submit failed\n", stderr);
|
|
hub75_destroy(context);
|
|
return 1;
|
|
}
|
|
++submitted;
|
|
}
|
|
struct timespec delay = {.tv_sec = 0, .tv_nsec = 20000000};
|
|
nanosleep(&delay, NULL);
|
|
}
|
|
|
|
hub75_stats stats;
|
|
if (hub75_get_stats(context, &stats) != 0) {
|
|
fputs("stats failed\n", stderr);
|
|
hub75_destroy(context);
|
|
return 1;
|
|
}
|
|
hub75_destroy(context);
|
|
int expected_scans = hub75_scans_per_frame_for_brightness(brightness);
|
|
double miss_rate = stats.completed_frames == 0 ? 100.0 :
|
|
(double)stats.deadline_misses * 100.0 / (double)stats.completed_frames;
|
|
printf(
|
|
"{\"mode\":\"%s\",\"configured_hz\":%d,\"actual_hz\":%.3f,"
|
|
"\"panel_scan_hz\":%.3f,\"completed_frames\":%llu,"
|
|
"\"completed_scans\":%llu,\"scans_per_frame\":%d,"
|
|
"\"deadline_misses\":%llu,"
|
|
"\"deadline_miss_percent\":%.6f,\"buffer_swaps\":%llu,"
|
|
"\"submitted_frames\":%llu,\"oe_timing_backend\":\"%s\","
|
|
"\"oe_pulse_faults\":%llu,\"oe_forced_blanks\":%llu,"
|
|
"\"max_programmed_oe_ns\":%llu,\"oe_timing_error\":\"%s\","
|
|
"\"cpu_affinity_active\":%s,"
|
|
"\"realtime_priority_active\":%s,\"memory_locked\":%s,"
|
|
"\"governor\":\"%s\",\"affinity_error\":\"%s\","
|
|
"\"realtime_error\":\"%s\",\"memory_lock_error\":\"%s\","
|
|
"\"last_error\":\"%s\"}\n",
|
|
use_dev_mem ? "dev-mem" : "fake-registers",
|
|
refresh_rate,
|
|
stats.actual_refresh_rate_hz,
|
|
stats.panel_scan_rate_hz,
|
|
(unsigned long long)stats.completed_frames,
|
|
(unsigned long long)stats.completed_scans,
|
|
stats.scans_per_frame,
|
|
(unsigned long long)stats.deadline_misses,
|
|
miss_rate,
|
|
(unsigned long long)stats.buffer_swaps,
|
|
(unsigned long long)stats.submitted_frames,
|
|
stats.oe_timing_backend,
|
|
(unsigned long long)stats.oe_pulse_faults,
|
|
(unsigned long long)stats.oe_forced_blanks,
|
|
(unsigned long long)stats.max_programmed_oe_ns,
|
|
stats.oe_timing_error,
|
|
stats.cpu_affinity_active ? "true" : "false",
|
|
stats.realtime_priority_active ? "true" : "false",
|
|
stats.memory_locked ? "true" : "false",
|
|
stats.governor,
|
|
stats.affinity_error,
|
|
stats.realtime_error,
|
|
stats.memory_lock_error,
|
|
stats.last_error
|
|
);
|
|
if (
|
|
stats.completed_frames == 0 ||
|
|
stats.actual_refresh_rate_hz <= 0.0 ||
|
|
stats.panel_scan_rate_hz <= 0.0 ||
|
|
stats.scans_per_frame != expected_scans ||
|
|
stats.completed_scans != stats.completed_frames * (uint64_t)expected_scans ||
|
|
stats.oe_pulse_faults != 0 ||
|
|
strcmp(stats.oe_timing_backend, HUB75_OE_TIMING_BACKEND) != 0
|
|
) return 1;
|
|
return 0;
|
|
}
|