summaryrefslogtreecommitdiffstats
path: root/tools/guest-diag/fbcopy_pub.c
diff options
context:
space:
mode:
Diffstat (limited to 'tools/guest-diag/fbcopy_pub.c')
-rw-r--r--tools/guest-diag/fbcopy_pub.c703
1 files changed, 703 insertions, 0 deletions
diff --git a/tools/guest-diag/fbcopy_pub.c b/tools/guest-diag/fbcopy_pub.c
new file mode 100644
index 0000000..ecb38da
--- /dev/null
+++ b/tools/guest-diag/fbcopy_pub.c
@@ -0,0 +1,703 @@
+// SPDX-License-Identifier: BSD-3-Clause
+/*
+ * fbcopy_pub.c - capture the vGPU framebuffer WITHOUT X and publish frames
+ * into the kvmfr ivshmem BAR.
+ *
+ * The X server's screen has 1280x720 pixels in the emulated VRAM, which the
+ * guest maps as the vGPU's BAR1 (resource1). The X SHM readback path can
+ * stall behind the X server's own push wait; this tool reads the framebuffer
+ * pixels straight out of the BAR1 mapping instead (host RAM backing, no GPU
+ * or X round-trip), swizzles BGRA -> RGBA and writes the kvmfr frame
+ * protocol the host reader already decodes.
+ *
+ * Usage (guest, as root):
+ * ./fbcopy_pub test one solid red frame (pipeline check)
+ * ./fbcopy_pub find [passes] [ms] motion-scan first 64 MB of BAR1
+ * ./fbcopy_pub findp [off] [sz] [p] [ms]
+ * pread motion-scan of any BAR1 range
+ * (mmap only reaches the first 64 MB)
+ * ./fbcopy_pub pub <off> [n] [fps] [w] [h] [pitch]
+ * copy w*h from BAR1@off (pitch),
+ * mmap path (off < 64 MB)
+ * ./fbcopy_pub pubp <off> [n] [fps] [w] [h] [pitch]
+ * same but pread path (any offset)
+ *
+ * Find/build on the host (same glibc as the guest): gcc -O2 -o fbcopy_pub
+ * fbcopy_pub.c
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <errno.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <time.h>
+
+#define FRAME_OFFSET 0x100000UL
+#define HDR_MAGIC 0x4b564d46u /* "FMVK" */
+#define DEF_W 1280
+#define DEF_H 720
+#define DEF_PITCH 5120 /* xorg-vgpu.conf: 1280*4 rounded */
+
+struct frame_hdr {
+ uint32_t magic;
+ uint32_t version;
+ uint32_t width;
+ uint32_t height;
+ uint32_t stride;
+ uint32_t format; /* 0 = RGBA8 */
+ uint32_t seq;
+ uint32_t reserved;
+ uint64_t timestamp_ns;
+};
+
+static uint64_t now_ns(void)
+{
+ struct timespec ts;
+
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ return (uint64_t)ts.tv_sec * 1000000000ull + ts.tv_nsec;
+}
+
+static const char *find_vgpu_resource1(void)
+{
+ static char path[320];
+ DIR *d = opendir("/sys/bus/pci/devices");
+ struct dirent *de;
+
+ if (!d)
+ return NULL;
+ while ((de = readdir(d))) {
+ char p[340], buf[32];
+ FILE *f;
+ int n;
+
+ if (de->d_name[0] == '.')
+ continue;
+ snprintf(p, sizeof(p), "/sys/bus/pci/devices/%s/vendor",
+ de->d_name);
+ f = fopen(p, "r");
+ if (!f)
+ continue;
+ n = fread(buf, 1, sizeof(buf) - 1, f);
+ fclose(f);
+ if (n <= 0)
+ continue;
+ buf[n] = 0;
+ if (strtoul(buf, NULL, 16) != 0x10de)
+ continue;
+ snprintf(path, sizeof(path),
+ "/sys/bus/pci/devices/%s/resource1", de->d_name);
+ closedir(d);
+ return path;
+ }
+ closedir(d);
+ return NULL;
+}
+
+/*
+ * The vfio mdev region mmap is limited: only file offset 0 with len <= 64 MB
+ * works (nonzero offsets and 256 MB both EINVAL). Map the first 64 MB once at
+ * offset 0 and index into the mapping for every access.
+ */
+#define CHUNK_MAX (64UL << 20)
+
+static unsigned char *map_vram(int fd, size_t want, size_t *got)
+{
+ size_t len = want > CHUNK_MAX ? CHUNK_MAX : want;
+ unsigned char *p;
+
+ p = mmap(NULL, len, PROT_READ, MAP_SHARED, fd, 0);
+ if (p == MAP_FAILED)
+ return NULL;
+ *got = len;
+ return p;
+}
+
+static double t_ms(const struct timespec *a, const struct timespec *b)
+{
+ return (double)(b->tv_sec - a->tv_sec) * 1e3 +
+ (double)(b->tv_nsec - a->tv_nsec) / 1e6;
+}
+
+/*
+ * Find the largest contiguous run of "dense" pages (>= 64 of 256 sampled
+ * bytes nonzero) - an image in VRAM looks like one long dense run. Small
+ * gaps (<= 16 pages) are tolerated.
+ */
+static void report_cluster(const uint32_t *nz, size_t pages,
+ unsigned long long base_off, const char *tag)
+{
+ size_t p, run_start = 0, last_dense = 0, best_start = 0, best_len = 0;
+ int in_run = 0;
+
+ for (p = 0; p <= pages; p++) {
+ int dense = p < pages && nz[p] >= 64;
+
+ if (dense) {
+ if (!in_run) {
+ run_start = p;
+ in_run = 1;
+ }
+ last_dense = p;
+ } else if (in_run && p - last_dense > 16) {
+ size_t len = last_dense - run_start + 1;
+
+ if (len > best_len) {
+ best_len = len;
+ best_start = run_start;
+ }
+ in_run = 0;
+ }
+ }
+ if (best_len >= 8)
+ printf("%s_CLUSTER off=0x%llx pages=%zu bytes=%zu\n", tag,
+ base_off + ((unsigned long long)best_start << 12),
+ best_len, best_len << 12);
+}
+
+int main(int argc, char **argv)
+{
+ const char *mode = argc > 1 ? argv[1] : "find";
+ const char *bar_path = getenv("FBCOPY_BAR") ? getenv("FBCOPY_BAR") :
+ "/sys/bus/pci/devices/0000:00:03.0/resource2";
+ const char *res1 = getenv("FBCOPY_RES1") ? getenv("FBCOPY_RES1") :
+ find_vgpu_resource1();
+ struct stat st;
+ size_t map_size, i;
+ unsigned char *bar;
+ struct frame_hdr *hdr;
+ volatile uint32_t *seqp;
+ int fd, vram_fd;
+
+ setvbuf(stdout, NULL, _IONBF, 0);
+ if (!res1) {
+ printf("NO_VGPU\n");
+ return 1;
+ }
+ printf("VGPU_RES1 %s\n", res1);
+ if (stat(res1, &st) || !st.st_size) {
+ printf("STAT_FAIL\n");
+ return 1;
+ }
+ map_size = (size_t)st.st_size;
+ printf("VRAM_SIZE 0x%zx (%zu MB)\n", map_size, map_size >> 20);
+ vram_fd = open(res1, O_RDONLY | O_SYNC);
+ if (vram_fd < 0) {
+ perror(res1);
+ return 1;
+ }
+
+ /* kvmfr BAR next: every mode needs it except find. */
+ fd = open(bar_path, O_RDWR | O_SYNC);
+ if (fd < 0) {
+ perror(bar_path);
+ return 1;
+ }
+ bar = (unsigned char *)mmap(NULL, 128UL << 20, PROT_READ | PROT_WRITE,
+ MAP_SHARED, fd, 0);
+ close(fd);
+ if (bar == MAP_FAILED) {
+ perror("mmap kvmfr");
+ return 1;
+ }
+ hdr = (struct frame_hdr *)(bar + FRAME_OFFSET);
+ seqp = &hdr->seq;
+
+ if (!strcmp(mode, "zero")) {
+ /* Reset the kvmfr header seq so a reader seeding from it will
+ * accept the next pub run's frame 1. */
+ hdr->magic = HDR_MAGIC;
+ hdr->version = 1;
+ hdr->width = DEF_W;
+ hdr->height = DEF_H;
+ hdr->stride = DEF_W * 4;
+ hdr->format = 0;
+ hdr->timestamp_ns = now_ns();
+ __sync_synchronize();
+ *seqp = 0;
+ printf("SEQ_ZEROED\n");
+ return 0;
+ }
+
+ if (!strcmp(mode, "test")) {
+ unsigned char *dst = bar + FRAME_OFFSET +
+ sizeof(struct frame_hdr);
+ size_t n = (size_t)DEF_W * DEF_H * 4;
+
+ hdr->magic = HDR_MAGIC;
+ hdr->version = 1;
+ hdr->width = DEF_W;
+ hdr->height = DEF_H;
+ hdr->stride = DEF_W * 4;
+ hdr->format = 0;
+ for (i = 0; i < n; i += 4) {
+ dst[i + 0] = (unsigned char)(argc > 2 ? atoi(argv[2])
+ : 255);
+ dst[i + 1] = (unsigned char)(argc > 3 ? atoi(argv[3])
+ : 0);
+ dst[i + 2] = (unsigned char)(argc > 4 ? atoi(argv[4])
+ : 0);
+ dst[i + 3] = 0xff;
+ }
+ hdr->timestamp_ns = now_ns();
+ __sync_synchronize();
+ *seqp = 1;
+ printf("TEST_FRAME_PUBLISHED\n");
+ return 0;
+ }
+
+ if (!strcmp(mode, "find")) {
+ /*
+ * Motion + content scan of the first 64 MB of BAR1 (the only
+ * window the vfio mdev mmap accepts). Sample 256 B of every
+ * 4 KB page over `passes` passes `interval_ms` apart; score
+ * pages by total differing bytes (motion) and by nonzero
+ * density (content). Rendering can be slower than one frame
+ * per second, so the interval defaults to 3 s and is
+ * configurable: find [passes] [interval_ms]
+ */
+ int passes = argc > 2 ? atoi(argv[2]) : 3;
+ int interval_ms = argc > 3 ? atoi(argv[3]) : 3000;
+ size_t scan_size = map_size > CHUNK_MAX ? CHUNK_MAX : map_size;
+ size_t pages = scan_size >> 12;
+ unsigned char *s0, *s1, *tmp, *w;
+ uint32_t *motion, *nz;
+ size_t p, wlen;
+ int top, pass;
+
+ if (passes < 2)
+ passes = 2;
+ w = map_vram(vram_fd, scan_size, &wlen);
+ if (!w) {
+ printf("CHUNK_MAP_FAIL\n");
+ return 1;
+ }
+ printf("SCAN_WINDOW 0x%zx pages=%zu passes=%d interval=%dms\n",
+ wlen, pages, passes, interval_ms);
+ s0 = malloc(pages * 256);
+ s1 = malloc(pages * 256);
+ motion = calloc(pages, sizeof(uint32_t));
+ nz = calloc(pages, sizeof(uint32_t));
+ if (!s0 || !s1 || !motion || !nz)
+ return 1;
+ for (p = 0; p < pages; p++)
+ memcpy(s0 + p * 256, w + (p << 12), 256);
+ for (pass = 1; pass < passes; pass++) {
+ struct timespec a, b;
+ uint64_t moved = 0;
+
+ usleep((useconds_t)interval_ms * 1000);
+ clock_gettime(CLOCK_MONOTONIC, &a);
+ for (p = 0; p < pages; p++)
+ memcpy(s1 + p * 256, w + (p << 12), 256);
+ clock_gettime(CLOCK_MONOTONIC, &b);
+ for (p = 0; p < pages; p++) {
+ unsigned char *x = s0 + p * 256;
+ unsigned char *y = s1 + p * 256;
+ uint32_t d = 0;
+ int j;
+
+ for (j = 0; j < 256; j++)
+ if (x[j] != y[j])
+ d++;
+ motion[p] += d;
+ moved += d;
+ }
+ printf("PASS %d moved_bytes=%llu dt=%.0fms\n",
+ pass, (unsigned long long)moved, t_ms(&a, &b));
+ tmp = s0; s0 = s1; s1 = tmp;
+ }
+ /* nonzero density of the last sample */
+ for (p = 0; p < pages; p++) {
+ unsigned char *x = s0 + p * 256;
+ uint32_t c = 0;
+ int j;
+
+ for (j = 0; j < 256; j++)
+ if (x[j])
+ c++;
+ nz[p] = c;
+ }
+ report_cluster(nz, pages, 0, "FIND");
+ /* per-16MB summary */
+ for (i = 0; i + 4096 <= pages; i += 4096) {
+ uint64_t s = 0, c = 0;
+ size_t j;
+
+ for (j = i; j < i + 4096; j++) {
+ s += motion[j];
+ c += nz[j];
+ }
+ if (s || c)
+ printf("BLOCK off=0x%zx moving_bytes=%llu nonzero_bytes=%llu\n",
+ i << 12, (unsigned long long)s,
+ (unsigned long long)c);
+ }
+ for (top = 0; top < 16; top++) {
+ size_t best = 0;
+ uint32_t bs = 0;
+
+ for (p = 0; p < pages; p++)
+ if (motion[p] > bs) {
+ bs = motion[p];
+ best = p;
+ }
+ if (!bs)
+ break;
+ printf("HOT off=0x%zx score=%u\n", best << 12, bs);
+ motion[best] = 0;
+ }
+ for (top = 0; top < 16; top++) {
+ size_t best = 0;
+ uint32_t bs = 0;
+
+ for (p = 0; p < pages; p++)
+ if (nz[p] > bs) {
+ bs = nz[p];
+ best = p;
+ }
+ if (!bs)
+ break;
+ printf("CONTENT off=0x%zx nonzero=%u\n",
+ best << 12, bs);
+ nz[best] = 0;
+ }
+ printf("FIND_DONE\n");
+ return 0;
+ }
+
+ if (!strcmp(mode, "probe")) {
+ /*
+ * Ground truth on what BAR1 access actually works: try mmap
+ * windows at several offsets/sizes and pread at several
+ * offsets, report each result. Read-only.
+ */
+ struct {
+ unsigned long long off, len;
+ } mm[] = {
+ { 0, 64UL << 20 },
+ { 0, 256UL << 20 },
+ { 0, 4UL << 20 },
+ { 4UL << 20, 4UL << 20 },
+ { 64UL << 20, 64UL << 20 },
+ { 128UL << 20, 4UL << 20 },
+ { 192UL << 20, 4UL << 20 },
+ { 60UL << 20, 8UL << 20 },
+ };
+ unsigned long long po[] = {
+ 0, 0x100000, 4UL << 20, 60UL << 20,
+ 64UL << 20, 128UL << 20, 252UL << 20,
+ };
+ unsigned char buf[64];
+ size_t k;
+
+ for (k = 0; k < sizeof(mm) / sizeof(mm[0]); k++) {
+ unsigned char *p;
+
+ if (mm[k].off + mm[k].len > map_size) {
+ printf("MMAP off=0x%llx len=0x%llx SKIP(>bar)\n",
+ mm[k].off, mm[k].len);
+ continue;
+ }
+ errno = 0;
+ p = mmap(NULL, (size_t)mm[k].len, PROT_READ,
+ MAP_SHARED, vram_fd, (off_t)mm[k].off);
+ if (p == MAP_FAILED)
+ printf("MMAP off=0x%llx len=0x%llx FAIL errno=%d\n",
+ mm[k].off, mm[k].len, errno);
+ else {
+ printf("MMAP off=0x%llx len=0x%llx OK first=%02x%02x%02x%02x\n",
+ mm[k].off, mm[k].len, p[0], p[1], p[2], p[3]);
+ munmap(p, (size_t)mm[k].len);
+ }
+ }
+ for (k = 0; k < sizeof(po) / sizeof(po[0]); k++) {
+ ssize_t r;
+
+ errno = 0;
+ r = pread(vram_fd, buf, 16, (off_t)po[k]);
+ if (r != 16)
+ printf("PREAD off=0x%llx FAIL r=%zd errno=%d\n",
+ po[k], r, errno);
+ else
+ printf("PREAD off=0x%llx OK %02x%02x%02x%02x\n",
+ po[k], buf[0], buf[1], buf[2], buf[3]);
+ }
+ printf("PROBE_DONE\n");
+ return 0;
+ }
+
+ if (!strcmp(mode, "findp")) {
+ /*
+ * Same motion + content scan as `find`, but via pread so the
+ * whole 256 MB BAR is reachable (mmap stops at 64 MB).
+ * findp [start_off] [size] [passes] [interval_ms]
+ */
+ unsigned long long start = argc > 2 ?
+ strtoull(argv[2], NULL, 0) : 0;
+ unsigned long long size = argc > 3 ?
+ strtoull(argv[3], NULL, 0) : map_size - start;
+ int passes = argc > 4 ? atoi(argv[4]) : 3;
+ int interval_ms = argc > 5 ? atoi(argv[5]) : 3000;
+ size_t pages = size >> 12;
+ unsigned char *s0, *s1, *tmp;
+ uint32_t *motion, *nz;
+ size_t p;
+ int top, pass;
+
+ if (passes < 2)
+ passes = 2;
+ if (start + size > map_size) {
+ printf("RANGE_FAIL start=0x%llx size=0x%llx bar=0x%zx\n",
+ start, size, map_size);
+ return 1;
+ }
+ printf("SCANP start=0x%llx size=0x%llx pages=%zu passes=%d interval=%dms\n",
+ start, size, pages, passes, interval_ms);
+ s0 = malloc(pages * 256);
+ s1 = malloc(pages * 256);
+ motion = calloc(pages, sizeof(uint32_t));
+ nz = calloc(pages, sizeof(uint32_t));
+ if (!s0 || !s1 || !motion || !nz)
+ return 1;
+ for (pass = 0; pass < passes; pass++) {
+ struct timespec a, b;
+ uint64_t moved = 0;
+
+ if (pass)
+ usleep((useconds_t)interval_ms * 1000);
+ clock_gettime(CLOCK_MONOTONIC, &a);
+ for (p = 0; p < pages; p++) {
+ unsigned char *dst = pass ? s1 : s0;
+ ssize_t r = pread(vram_fd, dst + p * 256, 256,
+ (off_t)(start + (p << 12)));
+
+ if (r != 256) {
+ printf("PREAD_FAIL off=0x%llx r=%zd errno=%d\n",
+ start + (p << 12), r, errno);
+ return 1;
+ }
+ }
+ clock_gettime(CLOCK_MONOTONIC, &b);
+ if (pass) {
+ for (p = 0; p < pages; p++) {
+ unsigned char *x = s0 + p * 256;
+ unsigned char *y = s1 + p * 256;
+ uint32_t d = 0;
+ int j;
+
+ for (j = 0; j < 256; j++)
+ if (x[j] != y[j])
+ d++;
+ motion[p] += d;
+ moved += d;
+ }
+ printf("PASS %d moved_bytes=%llu dt=%.0fms\n",
+ pass, (unsigned long long)moved,
+ t_ms(&a, &b));
+ tmp = s0; s0 = s1; s1 = tmp;
+ } else {
+ printf("PASS 0 sampled dt=%.0fms\n",
+ t_ms(&a, &b));
+ }
+ }
+ for (p = 0; p < pages; p++) {
+ unsigned char *x = s0 + p * 256;
+ uint32_t c = 0;
+ int j;
+
+ for (j = 0; j < 256; j++)
+ if (x[j])
+ c++;
+ nz[p] = c;
+ }
+ report_cluster(nz, pages, start, "FINDP");
+ for (i = 0; i + 4096 <= pages; i += 4096) {
+ uint64_t s = 0, c = 0;
+ size_t j;
+
+ for (j = i; j < i + 4096; j++) {
+ s += motion[j];
+ c += nz[j];
+ }
+ if (s || c)
+ printf("BLOCK off=0x%llx moving_bytes=%llu nonzero_bytes=%llu\n",
+ start + ((unsigned long long)i << 12),
+ (unsigned long long)s,
+ (unsigned long long)c);
+ }
+ for (top = 0; top < 16; top++) {
+ size_t best = 0;
+ uint32_t bs = 0;
+
+ for (p = 0; p < pages; p++)
+ if (motion[p] > bs) {
+ bs = motion[p];
+ best = p;
+ }
+ if (!bs)
+ break;
+ printf("HOT off=0x%llx score=%u\n",
+ start + ((unsigned long long)best << 12), bs);
+ motion[best] = 0;
+ }
+ for (top = 0; top < 16; top++) {
+ size_t best = 0;
+ uint32_t bs = 0;
+
+ for (p = 0; p < pages; p++)
+ if (nz[p] > bs) {
+ bs = nz[p];
+ best = p;
+ }
+ if (!bs)
+ break;
+ printf("CONTENT off=0x%llx nonzero=%u\n",
+ start + ((unsigned long long)best << 12), bs);
+ nz[best] = 0;
+ }
+ printf("FINDP_DONE\n");
+ return 0;
+ }
+
+ if (!strcmp(mode, "pub")) {
+ unsigned long long off = argc > 2 ?
+ strtoull(argv[2], NULL, 0) : 0;
+ int frames = argc > 3 ? atoi(argv[3]) : 30;
+ int fps = argc > 4 ? atoi(argv[4]) : 5;
+ int w = argc > 5 ? atoi(argv[5]) : DEF_W;
+ int h = argc > 6 ? atoi(argv[6]) : DEF_H;
+ int pitch = argc > 7 ? atoi(argv[7]) : DEF_PITCH;
+ unsigned long long need = off +
+ (unsigned long long)(h - 1) * pitch +
+ (unsigned long long)w * 4;
+ size_t wlen = 0;
+ unsigned char *wmap;
+ int f, y, x;
+
+ wmap = map_vram(vram_fd, map_size, &wlen);
+ if (!wmap) {
+ printf("WIN_MAP_FAIL len=0x%zx\n", wlen);
+ return 1;
+ }
+ if (need > wlen) {
+ printf("OFF_RANGE_FAIL need=0x%llx window=0x%zx\n",
+ need, wlen);
+ return 1;
+ }
+ printf("PUB off=0x%llx frames=%d fps=%d %dx%d pitch=%d\n",
+ off, frames, fps, w, h, pitch);
+ hdr->magic = HDR_MAGIC;
+ hdr->version = 1;
+ hdr->width = (uint32_t)w;
+ hdr->height = (uint32_t)h;
+ hdr->stride = (uint32_t)w * 4;
+ hdr->format = 0;
+ for (f = 0; f < frames; f++) {
+ uint64_t t0 = now_ns();
+ unsigned char *dst = bar + FRAME_OFFSET +
+ sizeof(struct frame_hdr);
+ unsigned char *src = wmap + (size_t)off;
+
+ for (y = 0; y < h; y++) {
+ unsigned char *s = src + (size_t)y * pitch;
+ unsigned char *d = dst + (size_t)y * w * 4;
+
+ for (x = 0; x < w; x++) {
+ d[4 * x + 0] = s[4 * x + 2];
+ d[4 * x + 1] = s[4 * x + 1];
+ d[4 * x + 2] = s[4 * x + 0];
+ d[4 * x + 3] = 0xff;
+ }
+ }
+ hdr->timestamp_ns = now_ns();
+ __sync_synchronize();
+ *seqp = (uint32_t)(f + 1);
+ __sync_synchronize();
+ printf("FRAME seq=%d ms=%.1f\n", f + 1,
+ (double)(now_ns() - t0) / 1e6);
+ if (fps > 0)
+ usleep((useconds_t)(1000000 / fps));
+ }
+ printf("FRAME_DONE frames=%d\n", f);
+ return 0;
+ }
+
+ if (!strcmp(mode, "pubp")) {
+ unsigned long long off = argc > 2 ?
+ strtoull(argv[2], NULL, 0) : 0;
+ int frames = argc > 3 ? atoi(argv[3]) : 30;
+ int fps = argc > 4 ? atoi(argv[4]) : 5;
+ int w = argc > 5 ? atoi(argv[5]) : DEF_W;
+ int h = argc > 6 ? atoi(argv[6]) : DEF_H;
+ int pitch = argc > 7 ? atoi(argv[7]) : DEF_PITCH;
+ unsigned long long span = (unsigned long long)(h - 1) * pitch +
+ (unsigned long long)w * 4;
+ unsigned long long base = off & ~0xfffULL;
+ size_t len = (size_t)((off - base) + span);
+ unsigned char *buf;
+ int f, y, x;
+
+ len = (len + 0xfff) & ~0xfffUL;
+ if (off + span > map_size) {
+ printf("OFF_RANGE_FAIL need=0x%llx bar=0x%zx\n",
+ off + span, map_size);
+ return 1;
+ }
+ buf = malloc(len);
+ if (!buf)
+ return 1;
+ printf("PUBP off=0x%llx frames=%d fps=%d %dx%d pitch=%d len=0x%zx\n",
+ off, frames, fps, w, h, pitch, len);
+ hdr->magic = HDR_MAGIC;
+ hdr->version = 1;
+ hdr->width = (uint32_t)w;
+ hdr->height = (uint32_t)h;
+ hdr->stride = (uint32_t)w * 4;
+ hdr->format = 0;
+ for (f = 0; f < frames; f++) {
+ uint64_t t0 = now_ns();
+ unsigned char *dst = bar + FRAME_OFFSET +
+ sizeof(struct frame_hdr);
+ unsigned char *src;
+ ssize_t r = pread(vram_fd, buf, len, (off_t)base);
+
+ if (r != (ssize_t)len) {
+ printf("PREAD_FAIL off=0x%llx r=%zd errno=%d\n",
+ base, r, errno);
+ return 1;
+ }
+ src = buf + (size_t)(off - base);
+ for (y = 0; y < h; y++) {
+ unsigned char *s = src + (size_t)y * pitch;
+ unsigned char *d = dst + (size_t)y * w * 4;
+
+ for (x = 0; x < w; x++) {
+ d[4 * x + 0] = s[4 * x + 2];
+ d[4 * x + 1] = s[4 * x + 1];
+ d[4 * x + 2] = s[4 * x + 0];
+ d[4 * x + 3] = 0xff;
+ }
+ }
+ hdr->timestamp_ns = now_ns();
+ __sync_synchronize();
+ *seqp = (uint32_t)(f + 1);
+ __sync_synchronize();
+ printf("FRAME seq=%d ms=%.1f\n", f + 1,
+ (double)(now_ns() - t0) / 1e6);
+ if (fps > 0)
+ usleep((useconds_t)(1000000 / fps));
+ }
+ printf("FRAME_DONE frames=%d\n", f);
+ return 0;
+ }
+
+ printf("USAGE: fbcopy_pub test|find|findp|pub|pubp "
+ "<off> [n] [fps] [w] [h] [pitch]\n");
+ return 1;
+}