summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--kernel/gsp_phys_booter.c30
-rw-r--r--kernel/gsp_sema_service.c125
-rw-r--r--tools/guest-diag/fbcopy_pub.c504
3 files changed, 577 insertions, 82 deletions
diff --git a/kernel/gsp_phys_booter.c b/kernel/gsp_phys_booter.c
index 2f3202e..534a443 100644
--- a/kernel/gsp_phys_booter.c
+++ b/kernel/gsp_phys_booter.c
@@ -1668,6 +1668,20 @@ enum gsp_phys_rmcall_buf {
#define GSP_PHYS_RMCALL_TIMEOUT_MS 2000u
#define GSP_PHYS_RMCALL_POLL_MS 5u
/*
+ * SESSION 148 perf (termbin review, verified in-tree): every RPC reply wait
+ * polled with msleep(5). With several quanta per round trip and ~8
+ * serialized PDE_UPDATE forwards per forwarded GR launch, the 5 ms quantum
+ * dominated per-launch latency (~95% of the ~189 ms host service time per
+ * launch). Wait with sub-millisecond quanta instead: the reply is observed
+ * within one quantum, cutting each round trip to ~1 ms. POLL_MS remains for
+ * the poll accounting prints.
+ */
+#define GSP_PHYS_RMCALL_POLL_US 150u
+static inline void gsp_phys_rmcall_wait_quantum(void)
+{
+ usleep_range(GSP_PHYS_RMCALL_POLL_US, GSP_PHYS_RMCALL_POLL_US + 200u);
+}
+/*
* GR launch release poll window. A CE copy completes in single-digit ms, but
* a GR launch can force the engine's first context switch for the channel;
* the FECS ctxsw path is measured in fractions of a second, so the window is
@@ -15371,7 +15385,7 @@ static int gsp_phys_rmcall_gr_sem_push_locked(void __iomem *bar0)
gsp_phys_status_drain(NULL);
return -ETIMEDOUT;
}
- msleep(GSP_PHYS_RMCALL_POLL_MS);
+ gsp_phys_rmcall_wait_quantum();
}
gpget = get_unaligned_le32((u8 *)userd + gsp_phys_rmcall_userd_ctl_off() + GSP_PHYS_RMCALL_USERD_GPGET_OFF);
@@ -15471,7 +15485,7 @@ static void gsp_phys_rmcall_ctx_image_probe(void __iomem *bar0)
offs[k], poll);
break;
}
- msleep(GSP_PHYS_RMCALL_POLL_MS);
+ gsp_phys_rmcall_wait_quantum();
}
if (poll != GSP_PHYS_RMCALL_SEM_PAYLOAD)
break;
@@ -15572,7 +15586,7 @@ static int gsp_phys_rmcall_ce_copy_va(void __iomem *bar0, u64 src_va,
*sem_out = poll;
return -ETIMEDOUT;
}
- msleep(GSP_PHYS_RMCALL_POLL_MS);
+ gsp_phys_rmcall_wait_quantum();
}
}
@@ -15755,7 +15769,7 @@ int gsp_phys_backend_ce_copy_submit(u64 src_va, u64 dst_va, u64 sem_va,
gsp_phys_work_timed_out);
return -ETIMEDOUT;
}
- msleep(GSP_PHYS_RMCALL_POLL_MS);
+ gsp_phys_rmcall_wait_quantum();
}
/*
@@ -16274,7 +16288,7 @@ static int gsp_phys_guest_ce_copy_common(u64 src_hpa, u32 src_off, u64 dst_hpa,
ret = -ETIMEDOUT;
goto out;
}
- msleep(GSP_PHYS_RMCALL_POLL_MS);
+ gsp_phys_rmcall_wait_quantum();
}
/*
@@ -21235,7 +21249,7 @@ gr_page_unmapped:
ret = -ETIMEDOUT;
goto out;
}
- msleep(GSP_PHYS_RMCALL_POLL_MS);
+ gsp_phys_rmcall_wait_quantum();
}
gsp_phys_gr_ok++;
@@ -21640,7 +21654,7 @@ gr_page_unmapped:
}
if (time_after_eq(jiffies, dl))
break;
- msleep(GSP_PHYS_RMCALL_POLL_MS);
+ gsp_phys_rmcall_wait_quantum();
}
pr_info("vgsp-gr: outcome retry %u: fresh ack %s (qmd_rel=0x%08x want=0x%08x ch_rel=0x%08x after %u ms)\n",
nw_attempts,
@@ -22693,7 +22707,7 @@ static int gsp_phys_rmcall_stage4(void __iomem *bar0)
gsp_phys_status_drain(NULL);
return -ETIMEDOUT;
}
- msleep(GSP_PHYS_RMCALL_POLL_MS);
+ gsp_phys_rmcall_wait_quantum();
}
if (memcmp(gsp_phys_chunk_page_va(src, 0),
diff --git a/kernel/gsp_sema_service.c b/kernel/gsp_sema_service.c
index 9c72b4b..3a24550 100644
--- a/kernel/gsp_sema_service.c
+++ b/kernel/gsp_sema_service.c
@@ -681,6 +681,18 @@ static u8 svc_guest_reset_req; /* m177: guest RM reload; wiped on svc thread */
static u8 svc_sess_flush_forced; /* execute the next flush past the live-channel skip
* (ISSUE-0002 client-session boundary aid; see
* gsp_sema_service_sess_flush_force) */
+/*
+ * SESSION 148 perf tracing: sema_trace_h selects one channel handle; the
+ * pump then logs a compact state line for it once a second, and
+ * svc_counter_publish logs why it skipped (last_get gate / arming gate) or
+ * the value it wrote. This is the instrument for locating the 22.4 s/frame
+ * glxgears pacer without guessing at windows.
+ */
+unsigned long long gsp_sema_trace_h;
+module_param_named(sema_trace_h, gsp_sema_trace_h, ullong, 0644);
+MODULE_PARM_DESC(sema_trace_h,
+ "Trace one channel handle (0x...; 0=off): 1/s state + publish gates");
+static unsigned long svc_perf_last;
static u8 svc_sess_active; /* a channel of a session has registered (until flush) */
static u32 svc_sess_gen; /* m176: bumped by every executed session flush;
* stamps channels/scrubbers so per-session learned
@@ -993,6 +1005,8 @@ struct sema_svc {
u64 entry_norel_log;
u64 ops_denied; /* work refused: implausible stream */
u64 false_trans; /* resolvable VA mapped to non-push */
+ u64 rescue_map_ok; /* SESSION 148: content-verified
+ * map-rescue successes (GR + CE) */
u64 sem_write_reresolve; /* writes re-resolved after read-back */
u64 pool_alt_applied; /* pool releases fanned to a
* displaced pin (ISSUE-0002) */
@@ -18193,6 +18207,65 @@ static int svc_rescue_push_page(struct nv_vgpu_device *vgpu,
u32 ncand = 0, i;
/*
+ * SESSION 148 perf (measured on r843): content-verified full-chain
+ * rescue, usable by GR channels too. The CE-only gate below refused
+ * every GR push outright, yet the same svc_lookup the diagnostic
+ * probe uses resolves the stalled entries correctly (X's frame
+ * pushes at 0x120000514 -> vidmem shadow pa=0x200514 with plausible
+ * stream content) while the pump's own read went through a stale
+ * shadowing translation, failed plausibility, and skipped the entry
+ * - one full retry window per push, the measured 22.4 s/frame
+ * glxgears cadence. Resolve via svc_lookup, verify the bytes parse
+ * as a method stream, then pin the verified page so the pump's
+ * re-read lands on it. Cheap: one chain lookup + one page read.
+ */
+ if (entry_va && entry_len && entry_len <= SEMA_PAGE_SIZE) {
+ u64 mpa = 0;
+ int map = SVC_APERTURE_SYS;
+ u8 *vb = kmalloc(entry_len, GFP_KERNEL);
+ int lrc = -ENOMEM, rrc = -ENOMEM;
+ bool plaus = false;
+
+ if (vb) {
+ lrc = svc_lookup(vgpu, entry_va, &mpa, &map);
+ if (!lrc)
+ rrc = svc_read_ap(vgpu, mpa, map, vb,
+ entry_len);
+ if (!rrc)
+ plaus = svc_method_stream_plausible(vb,
+ entry_len);
+ if (gsp_sema_trace_h &&
+ (u32)ch->h_object == (u32)gsp_sema_trace_h &&
+ time_after(jiffies, svc_perf_last)) {
+ svc_perf_last = jiffies + HZ;
+ pr_info("sema-perf: maprescue h=0x%x va=0x%llx len=%u lookup=%d pa=0x%llx ap=%d read=%d plaus=%d\n",
+ ch->h_object, entry_va, entry_len,
+ lrc, mpa, map, rrc, plaus);
+ }
+ if (!lrc && !rrc && plaus) {
+ svc_pin_store(entry_va &
+ ~(u64)(SEMA_PAGE_SIZE - 1),
+ mpa &
+ ~(u64)(SEMA_PAGE_SIZE - 1),
+ map);
+ svc_lat_store(entry_va &
+ ~(u64)(SEMA_PAGE_SIZE - 1),
+ mpa &
+ ~(u64)(SEMA_PAGE_SIZE - 1),
+ map);
+ svc.rescue_map_ok++;
+ if (svc.rescue_map_ok <= 8)
+ pr_info("sema-svc: rescue via map h=0x%x va=0x%llx -> pa=0x%llx ap=%d (verified stream)\n",
+ ch->h_object, entry_va, mpa,
+ map);
+ kfree(vb);
+ return 0;
+ }
+ kfree(vb);
+ }
+ }
+
+ /*
* The matcher needs only the channel rank (slot) and the release-push
* shape; it does NOT need the pool semaphore translation that
* expected_sem_payload is learned from. Requiring the learned payload
@@ -18496,6 +18569,16 @@ static void svc_counter_publish(struct nv_vgpu_device *vgpu,
*/
u64 pa = ch->release_pa ? ch->release_pa : ch->counter_pa;
+ if (gsp_sema_trace_h && (u32)ch->h_object == (u32)gsp_sema_trace_h &&
+ time_after(jiffies, svc_perf_last)) {
+ svc_perf_last = jiffies + HZ;
+ pr_info("sema-perf: pub h=0x%x put=%u last=%u armed=%d pa=0x%llx cnt=%u gate=%s\n",
+ ch->h_object, put, (u32)ch->last_get, ch->gr_pub_armed,
+ pa, ch->counter_last,
+ !pa ? "no-pa" :
+ (ch->last_get != put) ? "outstanding" :
+ (!ch->gr_pub_armed ? "unarmed" : "publish"));
+ }
if (!pa)
return;
/*
@@ -26893,8 +26976,23 @@ static void svc_pump_channels(struct nv_vgpu_device *vgpu)
svc_fwd_hold.ch_handle == ch->h_object) &&
!svc_read_ap(vgpu,
ch->userd_pa + SVC_USERD_GP_PUT,
- ch->userd_as, &pput, sizeof(pput)))
+ ch->userd_as, &pput, sizeof(pput))) {
+ if (gsp_sema_trace_h &&
+ (u32)ch->h_object ==
+ (u32)gsp_sema_trace_h &&
+ time_after(jiffies, svc_perf_last)) {
+ svc_perf_last = jiffies + HZ;
+ pr_info("sema-perf: pass h=0x%x pput=%u last=%u armed=%d cnt=%u sub=%llu held=%llu starved=%llu\n",
+ ch->h_object, pput,
+ (u32)ch->last_get,
+ ch->gr_pub_armed,
+ ch->counter_last,
+ ch->submissions,
+ svc.gpfifo_held,
+ svc.gpfifo_starved);
+ }
svc_counter_publish(vgpu, ch, pput);
+ }
}
/*
@@ -28979,6 +29077,31 @@ static void svc_pump_channels(struct nv_vgpu_device *vgpu)
idx, va, len);
svc_log_stalled_va(va);
/*
+ * GR stale-skip clock: the
+ * rescue-failure skip gate below
+ * requires defer_since, yet every
+ * GR arming site is gated on a
+ * nonzero expected_sem_payload. A
+ * zero-expectation GR entry (its
+ * release was already delivered)
+ * therefore never satisfied the
+ * gate and was re-read on every
+ * pass while the channel ring
+ * stalled behind it (live
+ * evidence: one 96-byte entry
+ * re-read 362 times over ~18 s;
+ * ~8 s of ring stall per frame
+ * with frames at 0.045 FPS). Arm
+ * the clock at first sighting; the
+ * skip window itself stays
+ * governed by gsp_sema_gr_grace_ms.
+ */
+ if (!ch->engine_type &&
+ !ch->expected_sem_payload &&
+ !ch->defer_since)
+ ch->defer_since = jiffies -
+ msecs_to_jiffies(gsp_sema_gr_grace_ms + 1);
+ /*
* Open the cheap-wait window
* for this entry (see the
* loop-top guard) so rewind
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;
}