// ============================================================================ // lpddr_dump.c — Ch319 Brick 3 — HPS reads FPGA-private LPDDR4B back THROUGH // THE HPS BRIDGE (never /dev/mem of the framebuffer itself). // // mmaps ONLY the PS2 HPS-bridge register window (the same window ps2_status.sh // uses), then drives the LPDDR4B read-probe one 32-bit word at a time: // write LPDDR_RDADDR (0x03C) = byte addr -> sets address + triggers a read // poll LPDDR_STATUS (0x02C) bit3 (rd_pending) until 0 // read LPDDR_RDATA (0x03C) -> the 32-bit word // // Output: // default : raw little-endian bytes to stdout (pipe to md5sum / save .bin) // --ppm W H: decode PSMCT16 (RGB5A1) -> binary PPM (P6) on stdout // // Disarm the writer first (the FB must be static while dumping): // busybox devmem 0x40000018 w 0x2 // // Build on the HPS: gcc -O2 -o lpddr_dump lpddr_dump.c // Usage: // sudo ./lpddr_dump 0 8192 > fb.bin ; md5sum fb.bin # acceptance (expect 3b12baff...) // sudo ./lpddr_dump --ppm 64 64 0 > fb.ppm # screen-dump (64x64 PSMCT16) // Env: PS2_BRIDGE_BASE (default 0x40000000). // ============================================================================ #include #include #include #include #include #include #include #define OFF_LPDDR_STATUS 0x02C // bit3 = rd_pending #define OFF_LPDDR_RDPORT 0x03C // write = addr+trigger, read = data #define MAP_SPAN 0x1000 static volatile uint32_t *g_reg; static uint32_t rd_word(uint32_t byte_addr) { long spin = 0; g_reg[OFF_LPDDR_RDPORT/4] = byte_addr; // set addr + trigger read while (g_reg[OFF_LPDDR_STATUS/4] & 0x8) { // wait rd_pending -> 0 if (++spin > 100000000L) { fprintf(stderr, "lpddr_dump: TIMEOUT waiting for read @0x%x\n", byte_addr); exit(2); } } return g_reg[OFF_LPDDR_RDPORT/4]; } int main(int argc, char **argv) { const char *base_env = getenv("PS2_BRIDGE_BASE"); unsigned long bridge_base = base_env ? strtoul(base_env, NULL, 0) : 0x40000000UL; int ppm = 0, ai = 1, w = 0, h = 0; if (argc > ai && strcmp(argv[ai], "--ppm") == 0) { ppm = 1; ai++; if (argc < ai + 3) { fprintf(stderr, "usage: %s --ppm W H START\n", argv[0]); return 1; } w = atoi(argv[ai++]); h = atoi(argv[ai++]); } if (argc <= ai) { fprintf(stderr, "usage: %s [--ppm W H] START [LEN]\n", argv[0]); return 1; } uint32_t start = (uint32_t)strtoul(argv[ai++], NULL, 0); uint32_t len = ppm ? (uint32_t)(w * h * 2) : (argc > ai ? (uint32_t)strtoul(argv[ai], NULL, 0) : 8192); int fd = open("/dev/mem", O_RDWR | O_SYNC); if (fd < 0) { perror("/dev/mem"); return 1; } void *map = mmap(NULL, MAP_SPAN, PROT_READ | PROT_WRITE, MAP_SHARED, fd, bridge_base); if (map == MAP_FAILED) { perror("mmap bridge"); return 1; } g_reg = (volatile uint32_t *)map; if (ppm) printf("P6\n%d %d\n255\n", w, h); // Read in 32-bit words; LEN is byte count (word-aligned up). for (uint32_t a = 0; a < len; a += 4) { uint32_t word = rd_word(start + a); uint8_t b[4] = { word & 0xff, (word >> 8) & 0xff, (word >> 16) & 0xff, (word >> 24) & 0xff }; if (!ppm) { fwrite(b, 1, 4, stdout); } else { // two PSMCT16 (RGB5A1) pixels per word, little-endian halfwords. for (int p = 0; p < 2; p++) { uint16_t px = b[p*2] | (b[p*2+1] << 8); uint8_t r = ((px >> 0) & 0x1f) << 3; uint8_t g = ((px >> 5) & 0x1f) << 3; uint8_t bl= ((px >> 10) & 0x1f) << 3; uint8_t rgb[3] = { r, g, bl }; fwrite(rgb, 1, 3, stdout); } } } munmap(map, MAP_SPAN); close(fd); return 0; }