summaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tools')
-rw-r--r--tools/guest-diag/fbcopy_pub.c504
1 files changed, 431 insertions, 73 deletions
diff --git a/tools/guest-diag/fbcopy_pub.c b/tools/guest-diag/fbcopy_pub.c
index a465617..ecb38da 100644
--- a/tools/guest-diag/fbcopy_pub.c
+++ b/tools/guest-diag/fbcopy_pub.c
@@ -12,9 +12,15 @@
*
* Usage (guest, as root):
* ./fbcopy_pub test one solid red frame (pipeline check)
- * ./fbcopy_pub find motion-scan BAR1, print hot offsets
+ * ./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)
+ * 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
@@ -26,6 +32,7 @@
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>
+#include <errno.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <time.h>
@@ -93,16 +100,22 @@ static const char *find_vgpu_resource1(void)
}
/*
- * The vfio mdev region mmap is limited (256 MB EINVAL, 64 MB OK): open the
- * resource once and map windows no larger than this via map_window().
+ * 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_window(int fd, size_t off, size_t len)
+static unsigned char *map_vram(int fd, size_t want, size_t *got)
{
- if (len > CHUNK_MAX)
- return MAP_FAILED;
- return mmap(NULL, len, PROT_READ, MAP_SHARED, fd, (off_t)off);
+ 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)
@@ -111,11 +124,49 @@ static double t_ms(const struct timespec *a, const struct timespec *b)
(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 = "/sys/bus/pci/devices/0000:00:03.0/resource2";
- const char *res1 = find_vgpu_resource1();
+ 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;
@@ -157,6 +208,22 @@ int main(int argc, char **argv)
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);
@@ -186,51 +253,50 @@ int main(int argc, char **argv)
if (!strcmp(mode, "find")) {
/*
- * Sparse motion scan: sample 256 B of every 4 KB page twice,
- * 300 ms apart; score pages by differing bytes and report the
- * hottest pages plus per-16MB moving counts. The framebuffer
- * is the contiguous ~4 MB block with the most motion. The
- * region is mapped one <=64 MB window at a time (the vfio
- * mdev mmap refuses the full 256 MB).
+ * 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]
*/
- size_t pages = map_size >> 12;
- size_t cpages = CHUNK_MAX >> 12;
- unsigned char *s0, *s1;
- uint32_t *score;
- size_t p, chunk;
- int top;
-
- s0 = malloc(cpages * 256);
- s1 = malloc(cpages * 256);
- score = calloc(pages, sizeof(uint32_t));
- if (!s0 || !s1 || !score)
+ 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 (chunk = 0; chunk * CHUNK_MAX < map_size; chunk++) {
- size_t off = chunk * CHUNK_MAX;
- size_t len = map_size - off;
- size_t np = 0;
+ for (p = 0; p < pages; p++)
+ memcpy(s0 + p * 256, w + (p << 12), 256);
+ for (pass = 1; pass < passes; pass++) {
struct timespec a, b;
- unsigned char *w;
+ uint64_t moved = 0;
- if (len > CHUNK_MAX)
- len = CHUNK_MAX;
- w = map_window(vram_fd, off, len);
- if (w == MAP_FAILED) {
- printf("CHUNK_MAP_FAIL off=0x%zx\n", off);
- return 1;
- }
- np = len >> 12;
- for (p = 0; p < np; p++)
- memcpy(s0 + p * 256, w + (p << 12), 256);
+ usleep((useconds_t)interval_ms * 1000);
clock_gettime(CLOCK_MONOTONIC, &a);
- usleep(300000);
- for (p = 0; p < np; p++)
+ for (p = 0; p < pages; p++)
memcpy(s1 + p * 256, w + (p << 12), 256);
clock_gettime(CLOCK_MONOTONIC, &b);
- munmap(w, len);
- printf("SCAN chunk=0x%zx pages=%zu dt=%.0fms\n",
- off, np, t_ms(&a, &b));
- for (p = 0; p < np; p++) {
+ for (p = 0; p < pages; p++) {
unsigned char *x = s0 + p * 256;
unsigned char *y = s1 + p * 256;
uint32_t d = 0;
@@ -239,38 +305,266 @@ int main(int argc, char **argv)
for (j = 0; j < 256; j++)
if (x[j] != y[j])
d++;
- score[(off >> 12) + p] = 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;
+ uint64_t s = 0, c = 0;
size_t j;
- for (j = i; j < i + 4096; j++)
- s += score[j];
- if (s)
- printf("BLOCK off=0x%zx moving_bytes=%llu\n",
- i << 12, (unsigned long long)s);
+ 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 (score[p] > bs) {
- bs = score[p];
+ if (motion[p] > bs) {
+ bs = motion[p];
best = p;
}
if (!bs)
break;
printf("HOT off=0x%zx score=%u\n", best << 12, bs);
- score[best] = 0;
+ 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;
@@ -282,24 +576,18 @@ int main(int argc, char **argv)
unsigned long long need = off +
(unsigned long long)(h - 1) * pitch +
(unsigned long long)w * 4;
- size_t aligned = (size_t)off & ~0xfffUL;
- size_t win = (size_t)(need - aligned);
+ size_t wlen = 0;
unsigned char *wmap;
int f, y, x;
- if (need > map_size) {
- printf("OFF_RANGE_FAIL need=0x%llx size=0x%zx\n",
- need, map_size);
- return 1;
- }
- if (win > CHUNK_MAX) {
- printf("WIN_TOO_BIG 0x%zx\n", win);
+ wmap = map_vram(vram_fd, map_size, &wlen);
+ if (!wmap) {
+ printf("WIN_MAP_FAIL len=0x%zx\n", wlen);
return 1;
}
- wmap = map_window(vram_fd, aligned, win);
- if (wmap == MAP_FAILED) {
- printf("WIN_MAP_FAIL off=0x%zx len=0x%zx\n",
- aligned, win);
+ 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",
@@ -314,7 +602,7 @@ int main(int argc, char **argv)
uint64_t t0 = now_ns();
unsigned char *dst = bar + FRAME_OFFSET +
sizeof(struct frame_hdr);
- unsigned char *src = wmap + ((size_t)off - aligned);
+ unsigned char *src = wmap + (size_t)off;
for (y = 0; y < h; y++) {
unsigned char *s = src + (size_t)y * pitch;
@@ -340,6 +628,76 @@ int main(int argc, char **argv)
return 0;
}
- printf("USAGE: fbcopy_pub test|find|pub <off> [n] [fps] [w] [h] [pitch]\n");
+ 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;
}