diff options
Diffstat (limited to 'tools/guest-diag/fbcopy_pub.c')
| -rw-r--r-- | tools/guest-diag/fbcopy_pub.c | 504 |
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; } |
