// retroDE_ps2 — ps2_sh3_multitex (Ch355 Brick 1 host two-group RUNTIME-STAGING flow)
//
// Composites two authentic SH3 draws with DIFFERENT textures/CLUTs (A=idx19562, B=idx89761) into ONE LPDDR
// framebuffer (320x381) via scene-level texture rebind + staged-list retriggering:
//   preclear ONCE -> fill+CRC tex A -> STREAM list A -> GO A + fresh drain -> REFILL+CRC tex B -> STREAM list B ->
//   GO B + frame_drained high->low->high (anti-stale) -> enable scanout after the 2nd fresh drain.
// Requires the frame_drained diagnostic bit (0x02C[6]) — bundled since Ch354's fit.
//
// Build on the board:  gcc -O2 -o ps2_sh3_multitex ps2_sh3_multitex.c
// Run (after fit+boot): sudo ./ps2_sh3_multitex sh3_mtA_tex_lpddr.mem sh3_mtB_tex_lpddr.mem feeder_sh3_mtA.mem feeder_sh3_mtB.mem
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <ctype.h>

#define OFF_LPDDR_CTRL   0x018   // W: [0]arm [1]canary [2]video_src [3]scanout_lb
#define OFF_LPDDR_FBBASE 0x01C
#define OFF_LPDDR_STATUS 0x02C   // R: [0]idle [6]frame_drained (STABLE ordered ack)
#define OFF_LPDDR_BYTES  0x030
#define OFF_WRADDR       0x04C
#define OFF_WRDATA       0x050
#define OFF_TEX_FILL     0x054   // W[0] arm fill ; R[0] fill_done [2] write_pending
#define OFF_TEX_BEATS    0x058
#define OFF_TEX_BYTES    0x05C
#define OFF_TEX_RDERRS   0x068
#define OFF_WR_ERRS      0x06C
#define OFF_TEX_CRC      0x070
#define OFF_STG_STATUS   0x0D8   // R[0] feeder ready ; W reset staging write address
#define OFF_STG_LO       0x0DC   // W low32 ; R current staging addr
#define OFF_STG_HI       0x0E4   // W high32 (commits {hi,lo}, ++addr) ; R records emitted
#define OFF_GO           0x0E8   // W[0] trigger feeder
#define FRAME_DRAINED    0x40    // 0x02C[6]
#define IDLE_BIT         0x01
#define WR_PENDING       0x04    // 0x054[2]
#define READY_BIT        0x01

#define N_TEX   65536            // 512x512 PSMT8 / 4
#define TEX_BYTES 262144
#define N_BEATS 8192
#define STG_MAX 2048
#define FB_WORDS (320*381)       // 121920 ; bytes = 0x77100

typedef struct { volatile uint8_t *base; int dry; } br_t;
static void     wr32(br_t*b,int o,uint32_t v){ if(!b->dry) *(volatile uint32_t*)(b->base+o)=v; }
static uint32_t rd32(br_t*b,int o){ return b->dry?0:*(volatile uint32_t*)(b->base+o); }

// NOTE: buffer must exceed the longest banner line; skip any line whose first non-space char isn't a hex digit
// (comments/blank) — a too-small buffer that splits a long comment must NOT parse the tail as a data word.
static int load32(const char*p, uint32_t*a, int n){
    FILE*f=fopen(p,"r"); if(!f){fprintf(stderr,"open %s: %s\n",p,strerror(errno));return -1;}
    int k=0; char ln[512];
    while(k<n && fgets(ln,sizeof ln,f)){ char*s=ln; while(*s==' '||*s=='\t')s++; if(!isxdigit((unsigned char)*s))continue; a[k++]=(uint32_t)strtoul(s,NULL,16);}
    fclose(f); return k;
}
static int load64(const char*p, uint64_t*a, int n){
    FILE*f=fopen(p,"r"); if(!f){fprintf(stderr,"open %s: %s\n",p,strerror(errno));return -1;}
    int k=0; char ln[512];
    while(k<n && fgets(ln,sizeof ln,f)){ char*s=ln; while(*s==' '||*s=='\t')s++; if(!isxdigit((unsigned char)*s))continue; a[k++]=(uint64_t)strtoull(s,NULL,16);}
    fclose(f); return k;
}

// preclear the whole 320x381 FB once (write-probe), poll write_pending, check BRESP
static int preclear_fb(br_t*b){
    wr32(b, OFF_WRADDR, 0);
    for (unsigned i=0;i<FB_WORDS;i++){ wr32(b, OFF_WRDATA, 0);
        if(!b->dry){int g=0; while((rd32(b,OFF_TEX_FILL)&WR_PENDING)&&g<2000000)g++;} }
    uint32_t e=rd32(b,OFF_WR_ERRS);
    printf("[mt] preclear FB %u words (0..0x%x)  wr_bresp_errs=%u\n", FB_WORDS, FB_WORDS*4, e);
    return e?-1:0;
}
// upload a texture to LPDDR (write-probe), then arm a FRESH cache fill and verify crc/beats/bytes/rd_errs.
static int upload_and_fill(br_t*b, const char*nm, uint32_t*tex, uint32_t exp_crc){
    wr32(b, OFF_WRADDR, 0x200000u);
    for (int i=0;i<N_TEX;i++){ wr32(b, OFF_WRDATA, tex[i]);
        if(!b->dry){int g=0; while((rd32(b,OFF_TEX_FILL)&WR_PENDING)&&g<2000000)g++;} }
    uint32_t werr=rd32(b,OFF_WR_ERRS);
    // FRESH fill: arm, then require fill_done low->high (cache rearm), not a lingering high.
    wr32(b, OFF_TEX_FILL, 0x1);
    if(!b->dry){ int g=0; while((rd32(b,OFF_TEX_FILL)&0x1)&&g<2000)g++;         // drops (busy)
                 g=0; while(!(rd32(b,OFF_TEX_FILL)&0x1)&&g<400000)g++; }        // rises (done)
    uint32_t crc=rd32(b,OFF_TEX_CRC), beats=rd32(b,OFF_TEX_BEATS), bytes=rd32(b,OFF_TEX_BYTES), rderr=rd32(b,OFF_TEX_RDERRS);
    printf("[mt] tex %s: upload wr_errs=%u ; fill crc=0x%08x (exp 0x%08x) beats=%u (exp %d) bytes=%u (exp %d) rd_errs=%u\n",
           nm, werr, crc, exp_crc, beats, N_BEATS, bytes, TEX_BYTES, rderr);
    if(b->dry) return 0;
    if(werr || crc!=exp_crc || beats!=N_BEATS || bytes!=TEX_BYTES || rderr){ fprintf(stderr,"  FAIL tex %s fill mismatch\n", nm); return -1; }
    return 0;
}
static int wait_ready(br_t*b){ if(b->dry)return 0; for(int i=0;i<3000000;i++) if(rd32(b,OFF_STG_STATUS)&READY_BIT) return 0;
    fprintf(stderr,"  FAIL: feeder never reached C_READY\n"); return -1; }
// stream a 64-bit staging list over the bridge; report words + the 12-bit address bound.
static int stream_list(br_t*b, const char*nm, uint64_t*list, int nwords){
    if (nwords > 4095){ fprintf(stderr,"  FAIL list %s %d words exceeds 12-bit bridge addr\n", nm, nwords); return -1; }
    if (wait_ready(b)) return -1;                     // feeder in C_READY before staging
    wr32(b, OFF_STG_STATUS, 0);                       // reset staging write address to 0 (value MUST be 0 — matches ps2_feeder)
    for (int i=0;i<nwords;i++){ wr32(b, OFF_STG_LO, (uint32_t)(list[i]&0xFFFFFFFF)); wr32(b, OFF_STG_HI, (uint32_t)(list[i]>>32)); }
    if (wait_ready(b)) return -1;                     // staging accepted
    uint32_t addr=rd32(b,OFF_STG_LO);
    printf("[mt] staged list %s: %d words written, staged_addr=%u (exp %d, < 4096)\n", nm, nwords, addr, nwords);
    if (!b->dry && addr!=(uint32_t)nwords) fprintf(stderr,"  warn: staged_addr=%u != %d (addressing off)\n", addr, nwords);
    return 0;
}
// GO + await a fresh ordered drain. expect_stale: frame_drained is HIGH from a prior scene -> require high->low->high.
// After the drain, the feeder returns to C_READY (whole scene drained); records_emitted then reads the tri count.
static int go_await_drain(br_t*b, const char*nm, int exp_records, int expect_stale){
    wr32(b, OFF_GO, 0x1);
    if(b->dry){ printf("[mt] scene %s: GO (dry-run)\n", nm); return 0; }
    int g;
    if (expect_stale){ g=0; while((rd32(b,OFF_LPDDR_STATUS)&FRAME_DRAINED)&&g<40000000)g++;   // wait the FALL (fresh render)
        if (rd32(b,OFF_LPDDR_STATUS)&FRAME_DRAINED){ fprintf(stderr,"  FAIL %s: frame_drained never fell — STALE drain\n", nm); return -1; } }
    g=0; while(!(rd32(b,OFF_LPDDR_STATUS)&FRAME_DRAINED)&&g<40000000)g++;                      // wait the RISE (drained)
    if (wait_ready(b)) return -1;                                                             // feeder back to C_READY
    uint32_t fd=rd32(b,OFF_LPDDR_STATUS)&FRAME_DRAINED, by=rd32(b,OFF_LPDDR_BYTES), recs=rd32(b,OFF_STG_HI);
    printf("[mt] scene %s: GO -> fresh drain frame_drained=%u, records_emitted=%u (exp %d), FB beats written=%u\n",
           nm, fd?1:0, recs, exp_records, by);
    if (fd==0 || by==0){ fprintf(stderr,"  FAIL %s: empty render (frame_drained=%u beats=%u)\n", nm, fd?1:0, by); return -1; }
    if (!b->dry && (int)recs!=exp_records) fprintf(stderr,"  warn %s: records=%u != %d\n", nm, recs, exp_records);
    return 0;
}

int main(int argc, char**argv){
    unsigned long base=0x40000000UL; int dry=0; char*env=getenv("PS2_BRIDGE_BASE"); if(env) base=strtoul(env,NULL,0);
    const char *fa="sh3_mtA_tex_lpddr.mem",*fb="sh3_mtB_tex_lpddr.mem",*la="feeder_sh3_mtA.mem",*lb="feeder_sh3_mtB.mem";
    int ai=1;
    for (int i=1;i<argc;i++){ if(!strcmp(argv[i],"--dry-run"))dry=1;
        else if(!strcmp(argv[i],"--base")&&i+1<argc)base=strtoul(argv[++i],NULL,0);
        else if(argv[i][0]!='-'){ if(ai==1)fa=argv[i]; else if(ai==2)fb=argv[i]; else if(ai==3)la=argv[i]; else if(ai==4)lb=argv[i]; ai++; } }
    static uint32_t texA[N_TEX], texB[N_TEX]; static uint64_t listA[STG_MAX], listB[STG_MAX];
    if (load32(fa,texA,N_TEX)!=N_TEX||load32(fb,texB,N_TEX)!=N_TEX){fprintf(stderr,"texture load failed\n");return 1;}
    int na=load64(la,listA,STG_MAX), nb=load64(lb,listB,STG_MAX);
    if(na<8||nb<8){fprintf(stderr,"list load failed\n");return 1;}
    // trim trailing zero padding to the actual list length (header word0 low16 = ntris; words = 7 + ntris*9)
    int ntA=(int)(listA[0]&0xFFFF), ntB=(int)(listB[0]&0xFFFF); int nwA=7+ntA*9, nwB=7+ntB*9;
    uint32_t crcA=0,crcB=0; for(int i=0;i<N_TEX;i++){crcA+=texA[i];crcB+=texB[i];}
    printf("[mt] texA sum32=0x%08x (%d tris/%d words)  texB sum32=0x%08x (%d tris/%d words)  bridge 0x%lx%s\n",
           crcA,ntA,nwA, crcB,ntB,nwB, base, dry?" DRY":"");

    br_t br={0,dry}; int fd=-1; void*map=NULL;
    if(!dry){ fd=open("/dev/mem",O_RDWR|O_SYNC); if(fd<0){perror("open /dev/mem (root?)");return 1;}
        map=mmap(NULL,0x2000,PROT_READ|PROT_WRITE,MAP_SHARED,fd,(off_t)base);
        if(map==MAP_FAILED){perror("mmap");close(fd);return 1;} br.base=map; }

    // (1) wait feeder ready (setup + relocated-CLUT bootlet done)
    if(!dry){ int g=0; while(!(rd32(&br,OFF_STG_STATUS)&READY_BIT)&&g<20000000)g++; }
    printf("[mt] feeder ready=%d\n", dry?1:((rd32(&br,OFF_STG_STATUS)&READY_BIT)?1:0));
    int rc=0;
    wr32(&br, OFF_LPDDR_FBBASE, 0);                                    // FB base 0
    // (2) preclear ONCE
    rc|=preclear_fb(&br);
    // (3) scene A: fill tex A, stream list A, arm writer, GO A, fresh drain (A is first -> not stale)
    rc|=upload_and_fill(&br,"A",texA,crcA);
    rc|=stream_list(&br,"A",listA,nwA);
    wr32(&br, OFF_LPDDR_CTRL, 0x1);                                    // arm writer (video_src OFF)
    rc|=go_await_drain(&br,"A",ntA,0);
    // (4) scene B: REFILL tex B (cache rearm), stream list B, GO B, high->low->high fresh drain (stale from A)
    rc|=upload_and_fill(&br,"B",texB,crcB);
    rc|=stream_list(&br,"B",listB,nwB);
    rc|=go_await_drain(&br,"B",ntB,1);
    // (5) enable scanout ONLY after the 2nd fresh drain
    wr32(&br, OFF_LPDDR_CTRL, 0x1|0x4);                                // arm + video_src
    printf("[mt] video_src=1 -> HDMI sources the LPDDR line-buffer scanout (320x381, A+B composited)\n");

    if(!dry){ munmap(map,0x2000); close(fd); }
    printf("[mt] DONE rc=%d %s\n", rc, rc?"(a gate failed above)":"(all gates passed)");
    return rc?1:0;
}
