--- /sys/src/9/bcm64/MEMORYMAP Thu Jan 1 00:00:00 1970 +++ /sys/src/9/bcm64/MEMORYMAP Sun Apr 5 22:11:17 2026 @@ -0,0 +1,16 @@ +VA PA usage + +00 0000 0000 (u pages) user address space +01 0000 0000 (l2 tables) direct mapping for l2 ptes +01 4000 0000 00 0000 0000 uncached mapping for low 1GB +01 8000 0000 unused +02 0000 0000 00 0000 0000 physical ram 0G-4G KZERO +03 0000 0000 01 0000 0000 physical ram 4G-8G +04 0000 0000 02 0000 0000 physical ram 8G-12G +05 0000 0000 03 0000 0000 physical ram 12G-16G +06 0000 0000 10 0000 0000 VIRTIO first half - local bus +06 8000 0000 1C 0000 0000 VIRTIO second half - RP1 pci window +07 0000 0000 framebuffer + +10 0000 0000 10 0000 0000 identity mapping for local bus +10 8000 0000 top of virtual space --- /sys/src/9/bcm64/arch.c Thu Jan 1 00:00:00 1970 +++ /sys/src/9/bcm64/arch.c Tue Mar 24 19:44:52 2026 @@ -0,0 +1,206 @@ +#include "u.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#include "../port/error.h" + +#include "../port/tos32.h" +#include "tos.h" +#include "ureg.h" + +/* + * A lot of this stuff doesn't belong here + * but this is a convenient dumping ground for + * later sorting into the appropriate buckets. + */ + +/* Give enough context in the ureg to produce a kernel stack for + * a sleeping process + */ +void +setkernur(Ureg* ureg, Proc* p) +{ + ureg->pc = p->sched.pc; + ureg->sp = p->sched.sp+4; + ureg->link = PTR2UINT(sched); +} + +/* + * called in syscallfmt.c, sysfile.c, sysproc.c + */ +void +validalign(uintptr addr, unsigned align) +{ + /* + * Plan 9 is a 32-bit O/S, and the hardware it runs on + * does not usually have instructions which move 64-bit + * quantities directly, synthesizing the operations + * with 32-bit move instructions. Therefore, the compiler + * (and hardware) usually only enforce 32-bit alignment, + * if at all. + * + * Take this out if the architecture warrants it. + */ + if(align == sizeof(vlong)) + align = sizeof(long); + + /* + * Check align is a power of 2, then addr alignment. + */ + if((align != 0 && !(align & (align-1))) && !(addr & (align-1))) + return; + postnote(up, 1, "sys: odd address", NDebug); + error(Ebadarg); + /*NOTREACHED*/ +} + +/* go to user space */ +void +kexit(Ureg*) +{ + uvlong t; + + if(!up->compat32){ + Tos *tos; + + /* precise time accounting, kernel exit */ + tos = (Tos*)(USTKTOP-sizeof(Tos)); + cycles(&t); + tos->kcycles += t - up->kentry; + tos->pcycles = up->pcycles; + tos->cyclefreq = m->cpuhz; + tos->pid = up->pid; + + /* make visible immediately to user proc */ + cachedwbinvse(tos, sizeof *tos); + }else{ + Tos32 *tos; + + /* precise time accounting, kernel exit */ + tos = (Tos32*)(USTKTOP-sizeof(Tos32)); + cycles(&t); + tos->kcycles += t - up->kentry; + tos->pcycles = up->pcycles; + tos->cyclefreq = m->cpuhz; + tos->pid = up->pid; + + /* make visible immediately to user proc */ + cachedwbinvse(tos, sizeof *tos); + } +} + +/* + * return the userpc the last exception happened at + */ +ulong +userpc(void) +{ + Ureg *ureg = up->dbgreg; + return ureg->pc; +} + +/* This routine must save the values of registers the user is not permitted + * to write from devproc and then restore the saved values before returning. + */ +void +setregisters(Ureg* ureg, char* pureg, char* uva, int n) +{ + USED(ureg, pureg, uva, n); +} + +/* + * this is the body for all kproc's + */ +static void +linkproc(void) +{ + spllo(); + up->kpfun(up->kparg); + pexit("kproc exiting", 0); +} + +/* + * setup stack and initial PC for a new kernel proc. This is architecture + * dependent because of the starting stack location + */ +void +kprocchild(Proc *p, void (*func)(void*), void *arg) +{ + p->sched.pc = PTR2UINT(linkproc); + p->sched.sp = PTR2UINT(p->kstack+KSTACK); + + p->kpfun = func; + p->kparg = arg; +} + +/* + * pc output by dumpaproc + */ +ulong +dbgpc(Proc* p) +{ + Ureg *ureg; + + ureg = p->dbgreg; + if(ureg == 0) + return 0; + + return ureg->pc; +} + +/* + * set mach dependent process state for a new process + */ +void +procsetup(Proc* p) +{ + fpusysprocsetup(p); +} + +/* + * Save the mach dependent part of the process state. + */ +void +procsave(Proc* p) +{ + uvlong t; + + cycles(&t); + p->pcycles += t; + +// TODO: save and restore VFPv3 FP state once 5[cal] know the new registers. + fpuprocsave(p); + /* + * Prevent the following scenario: + * pX sleeps on cpuA, leaving its page tables in mmul1 + * pX wakes up on cpuB, and exits, freeing its page tables + * pY on cpuB allocates a freed page table page and overwrites with data + * cpuA takes an interrupt, and is now running with bad page tables + * In theory this shouldn't hurt because only user address space tables + * are affected, and mmuswitch will clear mmul1 before a user process is + * dispatched. But empirically it correlates with weird problems, eg + * resetting of the core clock at 0x4000001C which confuses local timers. + */ + if(conf.nmach > 1) + mmuswitch(nil); +} + +void +procrestore(Proc* p) +{ + uvlong t; + + if(p->kp) + return; + cycles(&t); + p->pcycles -= t; + + fpuprocrestore(p); +} + +int +userureg(Ureg *ureg) +{ + return (ureg->psr & 0xF) == 0; +} --- /sys/src/9/bcm64/archbcm5.c Thu Jan 1 00:00:00 1970 +++ /sys/src/9/bcm64/archbcm5.c Tue Mar 24 19:47:48 2026 @@ -0,0 +1,255 @@ +/* + * bcm2712 (raspberry pi 5 architecture-specific stuff + */ + +#include "u.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#include "../port/error.h" +#include "io.h" +#include "ureg.h" +#include "ureg32.h" + +#include "../port/netif.h" +#include "etherif.h" + +typedef struct Mbox Mbox; +typedef struct Mboxes Mboxes; + +#define POWERREGS (VIRTIO+0x7d200000) + +Soc soc = { + .pcispace = 0x1C00000000ull, +}; + +enum { + Wdogfreq = 65536, + Wdogtime = 10, /* seconds, ≤ 15 */ +}; + +/* + * Power management / watchdog registers + */ +enum { + Rstc = 0x1c>>2, + Password = 0x5A<<24, + CfgMask = 0x03<<4, + CfgReset = 0x02<<4, + Rsts = 0x20>>2, + Wdog = 0x24>>2, +}; + +static Lock startlock[MAXMACH]; + +void +archreset(void) +{ + fpon(); +} + +void +archreboot(void) +{ + u32int *r; + + r = (u32int*)POWERREGS; + r[Wdog] = Password | 1; + r[Rstc] = Password | (r[Rstc] & ~CfgMask) | CfgReset; + coherence(); + for(;;) + ; +} + +void +wdogfeed(void) +{ + u32int *r; + + r = (u32int*)POWERREGS; + r[Wdog] = Password | (Wdogtime * Wdogfreq); + r[Rstc] = Password | (r[Rstc] & ~CfgMask) | CfgReset; +} + +void +wdogoff(void) +{ + u32int *r; + + r = (u32int*)POWERREGS; + r[Rstc] = Password | (r[Rstc] & ~CfgMask); +} + + +char * +cputype2name(char *buf, int size) +{ + u32int r; + uint part; + char *p; + + r = cpidget(); /* main id register */ + assert((r >> 24) == 'A'); + part = (r >> 4) & MASK(12); + switch(part){ + case 0xc07: + p = seprint(buf, buf + size, "Cortex-A7"); + break; + case 0xd03: + p = seprint(buf, buf + size, "Cortex-A53"); + break; + case 0xd08: + p = seprint(buf, buf + size, "Cortex-A72"); + break; + case 0xd0b: + p = seprint(buf, buf + size, "Cortex-A76"); + break; + default: + p = seprint(buf, buf + size, "Unknown-%#x", part); + break; + } + seprint(p, buf + size, " r%ldp%ld", + (r >> 20) & MASK(4), r & MASK(4)); + return buf; +} + +void +cpuidprint(void) +{ + char name[64]; + + cputype2name(name, sizeof name); + delay(50); /* let uart catch up */ + print("cpu%d: %dMHz ARM %s\n", m->machno, m->cpumhz, name); +} + +int +getncpus(void) +{ + int n, max; + char *p; + + n = 4; + if(n > MAXMACH) + n = MAXMACH; + p = getconf("*ncpu"); + if(p && (max = atoi(p)) > 0 && n > max) + n = max; + return n; +} + +int +startcpus(uint ncpu) +{ + int i, timeout; + extern uintptr startcpu(int); + + for(i = 1; i < ncpu; i++) + lock(&startlock[i]); + for(i = 1; i < ncpu; i++) + if(canlock(&startlock[i])) + print("wtf? lock%d\n", i); + cachedwbse(startlock, sizeof startlock); + for(i = 1; i < ncpu; i++){ + startcpu(i); + timeout = 100000000; + while(!canlock(&startlock[i])) + if(--timeout == 0) + return i; + unlock(&startlock[i]); + } + return ncpu; +} + +void +rdbstart(void) +{ + wdogoff(); + rdb(); +} + +uchar ether0mac[Eaddrlen]; + +int +archether(unsigned ctlrno, Ether *ether) +{ + switch(ctlrno){ + case 0: + ether->type = "gem"; + memmove(ether->ea, ether0mac, Eaddrlen); + break; + case 1: + ether->type = "4330"; + break; + default: + return 0; + } + ether->ctlrno = ctlrno; + ether->irq = -1; + ether->nopt = 0; + ether->maxmtu = 9014; + return 1; +} + +int +cmpswap(long *addr, long old, long new) +{ + return cas((ulong*)addr, old, new); +} + +void +cpustart(int cpu) +{ + void machon(int); + + up = nil; + machinit(); + trapinit(); + clockinit(); + mmuinit1(); + timersinit(); + cpuidprint(); + archreset(); + machon(m->machno); + unlock(&startlock[cpu]); + schedinit(); + panic("schedinit returned"); +} + +static void +_ureg64to32(void *a, Ureg *src) +{ + int i; + Ureg32 *dst; + + dst = a; + for(i = 0; i < 15; i++) + ((u32int*)&dst->r0)[i] = ((u64int*)&src->r0)[i]; + dst->type = src->type; + dst->psr = src->psr; + dst->pc = src->pc; +} + +static void +_ureg32to64(Ureg *dst, void *a) +{ + int i; + Ureg32 *src; + + src = a; + for(i = 0; i < 15; i++) + ((u64int*)&dst->r0)[i] = ((u32int*)&src->r0)[i]; + dst->type = src->type; + dst->psr = src->psr; + dst->pc = src->pc; +} + +void +archbcm5link(void) +{ + ureg64to32 = _ureg64to32; + ureg32to64 = _ureg32to64; + ureg32size = sizeof(Ureg32); + addclock0link(wdogfeed, HZ); +} --- /sys/src/9/bcm64/cache.v8.s Thu Jan 1 00:00:00 1970 +++ /sys/src/9/bcm64/cache.v8.s Sat Jul 26 10:10:32 2025 @@ -0,0 +1,211 @@ +/* + * instruction cache operations + */ + +#define CCSIDR_EL1 SYSARG5(3,1,0,0,0) +#define CSSELR_EL1 SYSARG5(3,2,0,0,0) + +TEXT cacheiinvse(SB), 1, $-4 + MOVWU len+8(FP), R2 + ADD R0, R2 + + MRS DAIF, R11 + MSR $0x2, DAIFSet + MOVWU $1, R10 + MSR R10, SPR(CSSELR_EL1) + ISB $SY + MRS SPR(CCSIDR_EL1), R4 + + ANDW $7, R4 + ADDW $4, R4 // log2(linelen) + LSL R4, R10 + LSR R4, R0 + LSL R4, R0 + +_iinvse: + IC R0, 3,7,5,1 // IVAU + ADD R10, R0 + CMP R0, R2 + BGT _iinvse + DSB $NSH + ISB $SY + MSR R11, DAIF + RETURN + +TEXT cacheiinv(SB), 1, $-4 + IC R0, 0,7,5,0 // IALLU + DSB $NSH + ISB $SY + RETURN + +TEXT cacheuwbinv(SB), 1, $0 + BL cachedwbinv(SB) + BL cacheiinv(SB) + RETURN + +/* + * data cache operations + */ +TEXT cachedwbse(SB), 1, $-4 + MOV LR, R29 + BL cachedva<>(SB) +TEXT dccvac(SB), 1, $-4 + DC R0, 3,7,10,1 // CVAC + RETURN + +TEXT cacheduwbse(SB), 1, $-4 + MOV LR, R29 + BL cachedva<>(SB) +TEXT dccvau(SB), 1, $-4 + DC R0, 3,7,11,1 // CVAU + RETURN + +TEXT cachedinvse(SB), 1, $-4 + MOV LR, R29 + BL cachedva<>(SB) +TEXT dcivac(SB), 1, $-4 + DC R0, 0,7,6,1 // IVAC + RETURN + +TEXT cachedwbinvse(SB), 1, $-4 + MOV LR, R29 + BL cachedva<>(SB) +TEXT dccivac(SB), 1, $-4 + DC R0, 3,7,14,1 // CIVAC + RETURN + +TEXT cachedva<>(SB), 1, $-4 + MOV LR, R1 + MOVWU len+8(FP), R2 + ADD R0, R2 + + MRS DAIF, R11 + MSR $0x2, DAIFSet + MOVWU $0, R10 + MSR R10, SPR(CSSELR_EL1) + ISB $SY + MRS SPR(CCSIDR_EL1), R4 + + ANDW $7, R4 + ADDW $4, R4 // log2(linelen) + MOVWU $1, R10 + LSL R4, R10 + LSR R4, R0 + LSL R4, R0 + + DSB $SY + ISB $SY +_cachedva: + BL (R1) + ADD R10, R0 + CMP R0, R2 + BGT _cachedva + DSB $SY + ISB $SY + MSR R11, DAIF + RET R29 + +/* + * l1 cache operations + */ +TEXT cachedwb(SB), 1, $-4 + MOVWU $0, R0 +_cachedwb: + MOV LR, R29 + BL cachedsw<>(SB) +TEXT dccsw(SB), 1, $-4 + DC R0, 0,7,10,2 // CSW + RETURN + +TEXT cachedinv(SB), 1, $-4 + MOVWU $0, R0 +_cachedinv: + MOV LR, R29 + BL cachedsw<>(SB) +TEXT dcisw(SB), 1, $-4 + DC R0, 0,7,6,2 // ISW + RETURN + +TEXT cachedwbinv(SB), 1, $-4 + MOVWU $0, R0 +_cachedwbinv: + MOV LR, R29 + BL cachedsw<>(SB) +TEXT dccisw(SB), 1, $-4 + DC R0, 0,7,14,2 // CISW + RETURN + +/* + * l2 cache operations + */ +TEXT l2cacheuwb(SB), 1, $-4 + MOVWU $1, R0 + B _cachedwb +TEXT l2cacheuinv(SB), 1, $-4 + MOVWU $1, R0 + B _cachedinv +TEXT l2cacheuwbinv(SB), 1, $-4 + MOVWU $1, R0 + B _cachedwbinv + +TEXT cachesize(SB), 1, $-4 + MRS DAIF, R11 + MSR $0x2, DAIFSet + MSR R0, SPR(CSSELR_EL1) + ISB $SY + MRS SPR(CCSIDR_EL1), R0 + MSR R11, DAIF + RETURN + +TEXT cachedsw<>(SB), 1, $-4 + MOV LR, R1 + + MRS DAIF, R11 + MSR $0x2, DAIFSet + ADDW R0, R0, R8 + MSR R8, SPR(CSSELR_EL1) + ISB $SY + MRS SPR(CCSIDR_EL1), R4 + + LSR $3, R4, R7 + ANDW $1023, R7 // lastway + ADDW $1, R7, R5 // #ways + + LSR $13, R4, R2 + ANDW $32767, R2 // lastset + ADDW $1, R2 // #sets + + ANDW $7, R4 + ADDW $4, R4 // log2(linelen) + + MOVWU $32, R3 // wayshift = 32 - log2(#ways) +_countlog2ways: + CBZ R7, _loop // lastway == 0? + LSR $1, R7 // lastway >>= 1 + SUB $1, R3 // wayshift-- + B _countlog2ways +_loop: + DSB $SY + ISB $SY +_nextway: + MOVWU $0, R6 // set +_nextset: + LSL R3, R7, R0 // way<machno != 0) + panic("cpu%d: unexpected system timer interrupt", m->machno); + tn = (Systimers*)SYSTIMERS; + /* dismiss interrupt */ + tn->c3 = tn->clo - 1; + tn->cs = 1<<3; + timerintr(ureg, 0); +} + +static void +localclockintr(Ureg *ureg, void *) +{ + if(m->machno == 0) + panic("cpu0: Unexpected local generic timer interrupt"); + cpwrtimerphysctl(Imask|Enable); + timerintr(ureg, 0); +} + +void +clockshutdown(void) +{ + if(cpuserver) + wdogfeed(); + else + wdogoff(); +} + +void +clockinit(void) +{ + Systimers *tn; + u32int t0, t1, tstart; + //u64int p0, p1; + + /* generic timer supported */ + cpwrtimerphysctl(Imask); + + tn = (Systimers*)SYSTIMERS; + tstart = tn->clo; + do{ + t0 = lcycles(); + }while(tn->clo == tstart); + //p0 = cprdtimerval(); + do{ + t1 = lcycles(); + //p1 = cprdtimerval(); + }while(tn->clo - tstart < 10000); + //print("in 10ms, timer incremented %llud\n", p1 - p0); + t1 -= t0; + m->cpuhz = 100 * t1; + m->cpumhz = (m->cpuhz + Mhz/2 - 1) / Mhz; + m->cyclefreq = m->cpuhz; + if(m->machno == 0){ + tn->c3 = tn->clo - 1; + intrenable(IRQtimer3, clockintr, nil, 0, "clock"); + }else{ + intrenable(IRQcntpns, localclockintr, nil, 0, "clock"); + } +} + +void +timerset(uvlong next) +{ + Systimers *tn; + uvlong now; + long period; + + now = fastticks(nil); + period = next - now; + if(period < MinPeriod) + period = MinPeriod; + else if(period > MaxPeriod) + period = MaxPeriod; + if(m->machno > 0){ + cpwrtimerphysval(54*period); + cpwrtimerphysctl(Enable); + }else{ + tn = (Systimers*)SYSTIMERS; + tn->c3 = tn->clo + period; + } +} + +uvlong +fastticks(uvlong *hz) +{ + Systimers *tn; + ulong lo, hi; + uvlong now; + + if(hz) + *hz = SystimerFreq; + tn = (Systimers*)SYSTIMERS; + do{ + hi = tn->chi; + lo = tn->clo; + }while(tn->chi != hi); + now = (uvlong)hi<<32 | lo; + return now; +} + +ulong +perfticks(void) +{ + return fastticks(nil); +} + +ulong +µs(void) +{ + if(SystimerFreq != 1*Mhz) + return fastticks2us(fastticks(nil)); + return ((Systimers*)SYSTIMERS)->clo; +} + +void +microdelay(int n) +{ + Systimers *tn; + u32int now, diff; + + diff = n + 1; + tn = (Systimers*)SYSTIMERS; + now = tn->clo; + while(tn->clo - now < diff) + ; +} + +void +delay(int n) +{ + while(--n >= 0) + microdelay(1000); +} --- /sys/src/9/bcm64/dat.h Thu Jan 1 00:00:00 1970 +++ /sys/src/9/bcm64/dat.h Sat Mar 21 17:19:50 2026 @@ -0,0 +1,346 @@ +/* + * Time. + * + * HZ should divide 1000 evenly, ideally. + * 100, 125, 200, 250 and 333 are okay. + */ +#define HZ 100 /* clock frequency */ +#define MS2HZ (1000/HZ) /* millisec per clock tick */ +#define TK2SEC(t) ((t)/HZ) /* ticks to seconds */ + +enum { + Mhz = 1000 * 1000, +}; + +typedef struct Conf Conf; +typedef struct Confmem Confmem; +typedef struct FPsave FPsave; +typedef struct I2Cdev I2Cdev; +typedef struct ISAConf ISAConf; +typedef struct Label Label; +typedef struct Lock Lock; +typedef struct Memcache Memcache; +typedef struct MMMU MMMU; +typedef struct Mach Mach; +typedef struct Notsave Notsave; +typedef struct Page Page; +typedef struct PhysUart PhysUart; +typedef struct PMMU PMMU; +typedef struct Proc Proc; +typedef u32int PTE; +typedef u64int LPTE; +typedef struct Soc Soc; +typedef struct Uart Uart; +typedef struct Ureg Ureg; +typedef uvlong Tval; + +#pragma incomplete Ureg + +#define MAXSYSARG 5 /* for mount(fd, mpt, flag, arg, srv) */ + +/* + * parameters for sysproc.c + */ +#define AOUT_MAGIC (R_MAGIC) +#define AOUT_MAGIC_COMPAT32 (E_MAGIC) + +struct Lock +{ + ulong key; + u32int sr; + uintptr pc; + Proc* p; + Mach* m; + int isilock; +}; + +struct Label +{ + uintptr sp; + uintptr pc; +}; + +enum { + Maxfpregs = 32, /* could be 16 or 32, see Mach.fpnregs */ + Nfpctlregs = 16, +}; + +/* + * emulated or vfp3 floating point + */ +struct FPsave +{ + ulong status; + ulong control; + /* + * vfp3 with ieee fp regs; uvlong is sufficient for hardware but + * each must be able to hold an Internal from fpi.h for sw emulation. + */ + ulong regs[Maxfpregs][3]; + + int fpstate; + uintptr pc; /* of failed fp instr. */ +}; + +/* + * FPsave.fpstate + */ +enum +{ + FPinit, + FPactive, + FPinactive, + FPemu, + FPnotestart, + + /* bits or'd with the state */ + FPillegal= 0x100, + /* state repeated 3 bits up for note handler */ + FPnoteshift = 3, + FPnotemask = 7<<3, +}; + +struct Confmem +{ + uvlong base; + usize npage; + uvlong limit; + uvlong kbase; + uvlong klimit; +}; + +struct Conf +{ + ulong nmach; /* processors */ + ulong nproc; /* processes */ + Confmem mem[2]; /* physical memory */ + Confmem himem; /* physical memory above 4GiB */ + ulong npage; /* total physical pages of memory */ + usize upages; /* user page pool */ + ulong copymode; /* 0 is copy on write, 1 is copy on reference */ + ulong ialloc; /* max interrupt time allocation in bytes */ + ulong pipeqsize; /* size in bytes of pipe queues */ + ulong nimage; /* number of page cache image headers */ + ulong nswap; /* number of swap pages */ + int nswppo; /* max # of pageouts per segment pass */ + ulong hz; /* processor cycle freq */ + ulong mhz; + int monitor; /* flag */ +}; + +struct I2Cdev { + int salen; + int addr; + int tenbit; +}; + +/* + * GPIO + */ +enum { + Input = 0x0, + Output = 0x1, + Alt0 = 0x4, + Alt1 = 0x5, + Alt2 = 0x6, + Alt3 = 0x7, + Alt4 = 0x3, + Alt5 = 0x2, +}; + + + +/* + * things saved in the Proc structure during a notify + */ +struct Notsave { + int emptiness; +}; + +/* + * MMU stuff in Mach. + */ +struct MMMU +{ + union { + PTE* mmul1; /* l1 for this processor */ + LPTE* mmull1; /* l1 for this processor (long descriptors) */ + }; + int mmul1lo; + int mmul1hi; + int mmupid; + union { + PTE* kmapl2; /* l2 for section containing kmap area and vectors */ + LPTE* kmapll2;/* as above (long descriptors) */ + }; +}; + +/* + * MMU stuff in proc + */ +#define NCOLOR 1 /* 1 level cache, don't worry about VCE's */ +#define NKMAPS 4 +struct PMMU +{ + Page* mmul2; + Page* mmul2cache; /* free mmu pages */ + int nkmap; + union { + PTE kmaptab[NKMAPS]; + LPTE kmapltab[NKMAPS]; + }; + ulong trapaddr[4]; +}; + +#include "../port/portdat.h" + +struct Mach +{ + int machno; /* physical id of processor */ + uintptr splpc; /* pc of last caller to splhi */ + + Proc* proc; /* current process */ + + MMMU; + int flushmmu; /* flush current proc mmu state */ + + ulong ticks; /* of the clock since boot time */ + Label sched; /* scheduler wakeup */ + Lock alarmlock; /* access to alarm list */ + void* alarm; /* alarms bound to this clock */ + + Proc* readied; /* for runproc */ + ulong schedticks; /* next forced context switch */ + + int cputype; + ulong delayloop; + + /* stats */ + int tlbfault; + int tlbpurge; + int pfault; + int cs; + int syscall; + int load; + int intr; + uvlong fastclock; /* last sampled value */ + ulong spuriousintr; + int lastintr; + int ilockdepth; + Perf perf; /* performance counters */ + + + int cpumhz; + uvlong cpuhz; /* speed of cpu */ + uvlong cyclefreq; /* Frequency of user readable cycle counter */ + + /* vfp2 or vfp3 fpu */ + int havefp; + int havefpvalid; + int fpon; + int fpconfiged; + int fpnregs; + ulong fpscr; /* sw copy */ + int fppid; /* pid of last fault */ + uintptr fppc; /* addr of last fault */ + int fpcnt; /* how many consecutive at that addr */ + + /* save areas for exceptions, hold R0-R4 */ + u32int sfiq[5]; + u32int sirq[5]; + u32int sund[5]; + u32int sabt[5]; + u32int smon[5]; /* probably not needed */ + u32int ssys[5]; + + int stack[1]; +}; + +/* + * Fake kmap. + */ +typedef void KMap; +#define VA(k) ((uintptr)(k)) +//#define kmap(p) (KMap*)((p)->pa|kseg0) +extern KMap* kmap(Page*); +extern void kunmap(KMap*); + +struct +{ + Lock; + int machs; /* bitmap of active CPUs */ + int exiting; /* shutdown */ + int ispanic; /* shutdown in response to a panic */ +}active; + +extern register Mach* m; /* R10 */ +extern register Proc* up; /* R9 */ +extern uintptr kseg0; +extern Mach* machaddr[MAXMACH]; +extern uvlong memsize; +extern int normalprint; + +/* + * a parsed plan9.ini line + */ +#define NISAOPT 8 + +struct ISAConf { + char *type; + ulong port; + int irq; + ulong dma; + ulong mem; + ulong size; + ulong freq; + + int nopt; + char *opt[NISAOPT]; +}; + +#define MACHP(n) (machaddr[n]) + +/* + * Horrid. But the alternative is 'defined'. + */ +#ifdef _DBGC_ +#define DBGFLG (dbgflg[_DBGC_]) +#else +#define DBGFLG (0) +#endif /* _DBGC_ */ + +int vflag; +extern char dbgflg[256]; + +#define dbgprint print /* for now */ + +/* + * hardware info about a device + */ +typedef struct { + ulong port; + int size; +} Devport; + +struct DevConf +{ + ulong intnum; /* interrupt number */ + char *type; /* card type, malloced */ + int nports; /* Number of ports */ + Devport *ports; /* The ports themselves */ +}; + +struct Soc { /* SoC dependent configuration */ + uintptr dramsize; + uintptr physio; + uintptr busdram; + uintptr busio; + uintptr armlocal; + uintptr pcispace; + uint oscfreq; + uint emmc2freq; + uint dstepping; + u32int l1ptedramattrs; + u32int l2ptedramattrs; +}; +extern Soc soc; --- /sys/src/9/bcm64/devarch.c Thu Jan 1 00:00:00 1970 +++ /sys/src/9/bcm64/devarch.c Mon Feb 23 16:37:24 2026 @@ -0,0 +1,186 @@ +#include "u.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#include "../port/error.h" +#include "io.h" + +enum { + Qdir = 0, + Qbase, + + Qmax = 16, +}; + +typedef long Rdwrfn(Chan*, void*, long, vlong); + +static Rdwrfn *readfn[Qmax]; +static Rdwrfn *writefn[Qmax]; + +static Dirtab archdir[Qmax] = { + ".", { Qdir, 0, QTDIR }, 0, 0555, +}; + +Lock archwlock; /* the lock is only for changing archdir */ +int narchdir = Qbase; + +/* + * Add a file to the #P listing. Once added, you can't delete it. + * You can't add a file with the same name as one already there, + * and you get a pointer to the Dirtab entry so you can do things + * like change the Qid version. Changing the Qid path is disallowed. + */ +Dirtab* +addarchfile(char *name, int perm, Rdwrfn *rdfn, Rdwrfn *wrfn) +{ + int i; + Dirtab d; + Dirtab *dp; + + memset(&d, 0, sizeof d); + strcpy(d.name, name); + d.perm = perm; + + lock(&archwlock); + if(narchdir >= Qmax){ + unlock(&archwlock); + return nil; + } + + for(i=0; iqid.path){ + case Qdir: + return devdirread(c, a, n, archdir, narchdir, devgen); + + default: + if(c->qid.path < narchdir && (fn = readfn[c->qid.path])) + return fn(c, a, n, offset); + error(Eperm); + break; + } + + return 0; +} + +static long +archwrite(Chan *c, void *a, long n, vlong offset) +{ + Rdwrfn *fn; + + if(c->qid.path < narchdir && (fn = writefn[c->qid.path])) + return fn(c, a, n, offset); + error(Eperm); + + return 0; +} + +void archinit(void); + +Dev archdevtab = { + 'P', + "arch", + + devreset, + archinit, + devshutdown, + archattach, + archwalk, + archstat, + archopen, + devcreate, + archclose, + archread, + devbread, + archwrite, + devbwrite, + devremove, + devwstat, +}; + +static long +cputyperead(Chan*, void *a, long n, vlong offset) +{ + char name[64], str[128]; + + cputype2name(name, sizeof name); + snprint(str, sizeof str, "ARM %s %d\n", name, m->cpumhz); + return readstr(offset, a, n, str); +} + +static long +cputempread(Chan*, void *a, long n, vlong offset) +{ + char str[16]; + + snprint(str, sizeof str, "%ud\n", (getcputemp()+500)/1000); + return readstr(offset, a, n, str); +} + +extern uvlong getserial(void); + +static long +cpuserread(Chan*, void *a, long n, vlong offset) +{ + char str[16]; + + snprint(str, sizeof str, "%16.16llux", getserial()); + return readstr(offset, a, n, str); +} + +void +archinit(void) +{ + addarchfile("cputype", 0444, cputyperead, nil); + //addarchfile("cputemp", 0444, cputempread, nil); + //addarchfile("serial", 0444, cpuserread, nil)->length = 16; +} --- /sys/src/9/bcm64/devether.c Thu Jan 1 00:00:00 1970 +++ /sys/src/9/bcm64/devether.c Tue Mar 24 21:29:06 2026 @@ -0,0 +1,530 @@ +#include "u.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#include "io.h" +#include "../port/error.h" + +#include "../port/netif.h" +#include "etherif.h" + +extern int archether(unsigned ctlno, Ether *ether); + +static Ether *etherxx[MaxEther]; + +Chan* +etherattach(char* spec) +{ + int ctlrno; + char *p; + Chan *chan; + + ctlrno = 0; + if(spec && *spec){ + ctlrno = strtoul(spec, &p, 0); + if((ctlrno == 0 && p == spec) || *p != 0) + error(Ebadarg); + if(ctlrno < 0 || ctlrno >= MaxEther) + error(Ebadarg); + } + if(etherxx[ctlrno] == 0) + error(Enodev); + + chan = devattach('l', spec); + if(waserror()){ + chanfree(chan); + nexterror(); + } + chan->dev = ctlrno; + if(etherxx[ctlrno]->attach) + etherxx[ctlrno]->attach(etherxx[ctlrno]); + poperror(); + return chan; +} + +static Walkqid* +etherwalk(Chan* chan, Chan* nchan, char** name, int nname) +{ + return netifwalk(etherxx[chan->dev], chan, nchan, name, nname); +} + +static int +etherstat(Chan* chan, uchar* dp, int n) +{ + return netifstat(etherxx[chan->dev], chan, dp, n); +} + +static Chan* +etheropen(Chan* chan, int omode) +{ + return netifopen(etherxx[chan->dev], chan, omode); +} + +static void +ethercreate(Chan*, char*, int, ulong) +{ +} + +static void +etherclose(Chan* chan) +{ + netifclose(etherxx[chan->dev], chan); +} + +static long +etherread(Chan* chan, void* buf, long n, vlong off) +{ + Ether *ether; + ulong offset = off; + + ether = etherxx[chan->dev]; + if((chan->qid.type & QTDIR) == 0 && ether->ifstat){ + /* + * With some controllers it is necessary to reach + * into the chip to extract statistics. + */ + if(NETTYPE(chan->qid.path) == Nifstatqid) + return ether->ifstat(ether, buf, n, offset); + else if(NETTYPE(chan->qid.path) == Nstatqid) + ether->ifstat(ether, buf, 0, offset); + } + + return netifread(ether, chan, buf, n, offset); +} + +static Block* +etherbread(Chan* chan, long n, ulong offset) +{ + return netifbread(etherxx[chan->dev], chan, n, offset); +} + +static int +etherwstat(Chan* chan, uchar* dp, int n) +{ + return netifwstat(etherxx[chan->dev], chan, dp, n); +} + +static void +etherrtrace(Netfile* f, Etherpkt* pkt, int len) +{ + int i, n; + Block *bp; + + if(qwindow(f->in) <= 0) + return; + if(len > 58) + n = 58; + else + n = len; + bp = iallocb(64); + if(bp == nil) + return; + memmove(bp->wp, pkt->d, n); + i = TK2MS(MACHP(0)->ticks); + bp->wp[58] = len>>8; + bp->wp[59] = len; + bp->wp[60] = i>>24; + bp->wp[61] = i>>16; + bp->wp[62] = i>>8; + bp->wp[63] = i; + bp->wp += 64; + qpass(f->in, bp); +} + +Block* +etheriq(Ether* ether, Block* bp, int fromwire) +{ + Etherpkt *pkt; + ushort type; + int len, multi, tome, fromme; + Netfile **ep, *f, **fp, *fx; + Block *xbp; + + ether->inpackets++; + + pkt = (Etherpkt*)bp->rp; + len = BLEN(bp); + type = (pkt->type[0]<<8)|pkt->type[1]; + fx = 0; + ep = ðer->f[Ntypes]; + + multi = pkt->d[0] & 1; + /* check for valid multicast addresses */ + if(multi && memcmp(pkt->d, ether->bcast, sizeof(pkt->d)) != 0 && + ether->prom == 0){ + if(!activemulti(ether, pkt->d, sizeof(pkt->d))){ + if(fromwire){ + freeb(bp); + bp = 0; + } + return bp; + } + } + /* is it for me? */ + tome = memcmp(pkt->d, ether->ea, sizeof(pkt->d)) == 0; + fromme = memcmp(pkt->s, ether->ea, sizeof(pkt->s)) == 0; + + /* + * Multiplex the packet to all the connections which want it. + * If the packet is not to be used subsequently (fromwire != 0), + * attempt to simply pass it into one of the connections, thereby + * saving a copy of the data (usual case hopefully). + */ + for(fp = ether->f; fp < ep; fp++){ + if((f = *fp) != nil && (f->type == type || f->type < 0) && + (tome || multi || f->prom)){ + /* Don't want to hear bridged packets */ + if(f->bridge && !fromwire && !fromme) + continue; + if(!f->headersonly){ + if(fromwire && fx == 0) + fx = f; + else if(xbp = iallocb(len)){ + memmove(xbp->wp, pkt, len); + xbp->wp += len; + if(qpass(f->in, xbp) < 0) + ether->soverflows++; + } + else + ether->soverflows++; + } + else + etherrtrace(f, pkt, len); + } + } + + if(fx){ + if(qpass(fx->in, bp) < 0) + ether->soverflows++; + return 0; + } + if(fromwire){ + freeb(bp); + return 0; + } + return bp; +} + +static int +etheroq(Ether* ether, Block* bp) +{ + int len, loopback, s; + Etherpkt *pkt; + + ether->outpackets++; + + /* + * Check if the packet has to be placed back onto the input queue, + * i.e. if it's a loopback or broadcast packet or the interface is + * in promiscuous mode. + * If it's a loopback packet indicate to etheriq that the data isn't + * needed and return, etheriq will pass-on or free the block. + * To enable bridging to work, only packets that were originated + * by this interface are fed back. + */ + pkt = (Etherpkt*)bp->rp; + len = BLEN(bp); + loopback = memcmp(pkt->d, ether->ea, sizeof(pkt->d)) == 0; + if(loopback || memcmp(pkt->d, ether->bcast, sizeof(pkt->d)) == 0 || ether->prom){ + s = splhi(); + etheriq(ether, bp, 0); + splx(s); + } + + if(!loopback){ + qbwrite(ether->oq, bp); + if(ether->transmit != nil) + ether->transmit(ether); + } else + freeb(bp); + + return len; +} + +static long +etherwrite(Chan* chan, void* buf, long n, vlong) +{ + Ether *ether; + Block *bp; + int nn, onoff; + Cmdbuf *cb; + + ether = etherxx[chan->dev]; + if(NETTYPE(chan->qid.path) != Ndataqid) { + nn = netifwrite(ether, chan, buf, n); + if(nn >= 0) + return nn; + cb = parsecmd(buf, n); + if(cb->f[0] && strcmp(cb->f[0], "nonblocking") == 0){ + if(cb->nf <= 1) + onoff = 1; + else + onoff = atoi(cb->f[1]); + qnoblock(ether->oq, onoff); + free(cb); + return n; + } + free(cb); + if(ether->ctl!=nil) + return ether->ctl(ether,buf,n); + + error(Ebadctl); + } + + if(n > ether->maxmtu) + error(Etoobig); + if(n < ether->minmtu) + error(Etoosmall); + + bp = allocb(n); + if(waserror()){ + freeb(bp); + nexterror(); + } + memmove(bp->rp, buf, n); + memmove(bp->rp+Eaddrlen, ether->ea, Eaddrlen); + poperror(); + bp->wp += n; + + return etheroq(ether, bp); +} + +static long +etherbwrite(Chan* chan, Block* bp, ulong) +{ + Ether *ether; + long n; + + n = BLEN(bp); + if(NETTYPE(chan->qid.path) != Ndataqid){ + if(waserror()) { + freeb(bp); + nexterror(); + } + n = etherwrite(chan, bp->rp, n, 0); + poperror(); + freeb(bp); + return n; + } + ether = etherxx[chan->dev]; + + if(n > ether->maxmtu){ + freeb(bp); + error(Etoobig); + } + if(n < ether->minmtu){ + freeb(bp); + error(Etoosmall); + } + + return etheroq(ether, bp); +} + +static struct { + char* type; + int (*reset)(Ether*); +} cards[MaxEther+1]; + +void +addethercard(char* t, int (*r)(Ether*)) +{ + static int ncard; + + if(ncard == MaxEther) + panic("too many ether cards"); + cards[ncard].type = t; + cards[ncard].reset = r; + ncard++; +} + +int +parseether(uchar *to, char *from) +{ + char nip[4]; + char *p; + int i; + + p = from; + for(i = 0; i < Eaddrlen; i++){ + if(*p == 0) + return -1; + nip[0] = *p++; + if(*p == 0) + return -1; + nip[1] = *p++; + nip[2] = 0; + to[i] = strtoul(nip, 0, 16); + if(*p == ':') + p++; + } + return 0; +} + +static void +etherreset(void) +{ + Ether *ether; + int i, n, ctlrno; + char name[KNAMELEN], buf[128]; + + for(ether = 0, ctlrno = 0; ctlrno < MaxEther; ctlrno++){ + if(ether == 0) + ether = malloc(sizeof(Ether)); + memset(ether, 0, sizeof(Ether)); + ether->ctlrno = ctlrno; + ether->mbps = 10; + ether->minmtu = ETHERMINTU; + ether->maxmtu = ETHERMAXTU; + + if(archether(ctlrno, ether) <= 0) + continue; + + if(isaconfig("ether", ctlrno, ether) == 0){ +// free(ether); +// return nil; + continue; + } + for(n = 0; cards[n].type; n++){ + if(cistrcmp(cards[n].type, ether->type)) + continue; + for(i = 0; i < ether->nopt; i++) + if(cistrncmp(ether->opt[i], "ea=", 3) == 0){ + if(parseether(ether->ea, + ðer->opt[i][3]) == -1) + memset(ether->ea, 0, Eaddrlen); + } else if(cistrcmp(ether->opt[i], + "100BASE-TXFD") == 0) + ether->mbps = 100; + if(cards[n].reset(ether)) + break; + snprint(name, sizeof(name), "ether%d", ctlrno); + + if(ether->interrupt != nil && ether->irq >= 0) + intrenable(ether->irq, ether->interrupt, + ether, 0, name); + + i = snprint(buf, sizeof buf, + "#l%d: %s: %dMbps port %#lux irq %d", + ctlrno, ether->type, ether->mbps, ether->port, + ether->irq); + if(ether->mem) + i += snprint(buf+i, sizeof buf - i, + " addr %#p", PADDR(ether->mem)); + if(ether->size) + i += snprint(buf+i, sizeof buf - i, + " size %#luX", ether->size); + i += snprint(buf+i, sizeof buf - i, + ": %2.2ux%2.2ux%2.2ux%2.2ux%2.2ux%2.2ux", + ether->ea[0], ether->ea[1], ether->ea[2], + ether->ea[3], ether->ea[4], ether->ea[5]); + snprint(buf+i, sizeof buf - i, "\n"); + iprint("%s", buf); /* it may be too early for print */ + + if(ether->mbps >= 1000) + netifinit(ether, name, Ntypes, 4*1024*1024); + else if(ether->mbps >= 100) + netifinit(ether, name, Ntypes, 1024*1024); + else + netifinit(ether, name, Ntypes, 65*1024); + if(ether->oq == 0) + ether->oq = qopen(ether->limit, Qmsg, 0, 0); + if(ether->oq == 0) + panic("etherreset %s", name); + ether->alen = Eaddrlen; + memmove(ether->addr, ether->ea, Eaddrlen); + memset(ether->bcast, 0xFF, Eaddrlen); + + etherxx[ctlrno] = ether; + ether = 0; + break; + } + } + if(ether) + free(ether); +} + +static void +ethershutdown(void) +{ + Ether *ether; + int i; + + for(i = 0; i < MaxEther; i++){ + ether = etherxx[i]; + if(ether == nil) + continue; + if(ether->shutdown == nil) { + print("#l%d: no shutdown function\n", i); + continue; + } + (*ether->shutdown)(ether); + } +} + + +#define POLY 0xedb88320 + +/* really slow 32 bit crc for ethers */ +ulong +ethercrc(uchar *p, int len) +{ + int i, j; + ulong crc, b; + + crc = 0xffffffff; + for(i = 0; i < len; i++){ + b = *p++; + for(j = 0; j < 8; j++){ + crc = (crc>>1) ^ (((crc^b) & 1) ? POLY : 0); + b >>= 1; + } + } + return crc; +} + +void +dumpoq(Queue *oq) +{ + if (oq == nil) + print("no outq! "); + else if (qisclosed(oq)) + print("outq closed "); + else if (qfull(oq)) + print("outq full "); + else + print("outq %d ", qlen(oq)); +} + +void +dumpnetif(Netif *netif) +{ + print("netif %s ", netif->name); + print("limit %d mbps %d link %d ", + netif->limit, netif->mbps, netif->link); + print("inpkts %lld outpkts %lld errs %d\n", + netif->inpackets, netif->outpackets, + netif->crcs + netif->oerrs + netif->frames + netif->overflows + + netif->buffs + netif->soverflows); +} + +Dev etherdevtab = { + 'l', + "ether", + + etherreset, + devinit, + ethershutdown, + etherattach, + etherwalk, + etherstat, + etheropen, + ethercreate, + etherclose, + etherread, + etherbread, + etherwrite, + etherbwrite, + devremove, + etherwstat, +}; --- /sys/src/9/bcm64/devroot.c Thu Jan 1 00:00:00 1970 +++ /sys/src/9/bcm64/devroot.c Wed Oct 22 08:51:28 2025 @@ -0,0 +1,262 @@ +#include "u.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#include "../port/error.h" + +enum +{ + Qdir = 0, + Qboot = 0x1000, + + Nrootfiles = 32, + Nbootfiles = 32, +}; + +typedef struct Dirlist Dirlist; +struct Dirlist +{ + uint base; + Dirtab *dir; + uchar **data; + int ndir; + int mdir; +}; + +static Dirtab rootdir[Nrootfiles] = { + "#/", {Qdir, 0, QTDIR}, 0, DMDIR|0555, + "boot", {Qboot, 0, QTDIR}, 0, DMDIR|0555, +}; +static uchar *rootdata[Nrootfiles]; +static Dirlist rootlist = +{ + 0, + rootdir, + rootdata, + 2, + Nrootfiles +}; + +static Dirtab bootdir[Nbootfiles] = { + "boot", {Qboot, 0, QTDIR}, 0, DMDIR|0555, +}; +static uchar *bootdata[Nbootfiles]; +static Dirlist bootlist = +{ + Qboot, + bootdir, + bootdata, + 1, + Nbootfiles +}; + +/* + * add a file to the list + */ +static void +addlist(Dirlist *l, char *name, uchar *contents, ulong len, int perm) +{ + Dirtab *d; + + if(l->ndir >= l->mdir) + panic("too many root files"); + l->data[l->ndir] = contents; + d = &l->dir[l->ndir]; + strcpy(d->name, name); + d->length = len; + d->perm = perm; + d->qid.type = 0; + d->qid.vers = 0; + d->qid.path = ++l->ndir + l->base; + if(perm & DMDIR) + d->qid.type |= QTDIR; +} + +/* + * add a root file + */ +void +addbootfile(char *name, uchar *contents, ulong len) +{ + addlist(&bootlist, name, contents, len, 0555); +} + +/* + * add a root directory + */ +static void +addrootdir(char *name) +{ + addlist(&rootlist, name, nil, 0, DMDIR|0555); +} + +static void +rootreset(void) +{ + addrootdir("bin"); + addrootdir("dev"); + addrootdir("env"); + addrootdir("fd"); + addrootdir("mnt"); + addrootdir("net"); + addrootdir("net.alt"); + addrootdir("proc"); + addrootdir("root"); + addrootdir("srv"); + addrootdir("home"); +} + +static Chan* +rootattach(char *spec) +{ + return devattach('/', spec); +} + +static int +rootgen(Chan *c, char *name, Dirtab*, int, int s, Dir *dp) +{ + int t; + Dirtab *d; + Dirlist *l; + + switch((int)c->qid.path){ + case Qdir: + if(s == DEVDOTDOT){ + devdir(c, (Qid){Qdir, 0, QTDIR}, "#/", 0, eve, 0555, dp); + return 1; + } + return devgen(c, name, rootlist.dir, rootlist.ndir, s, dp); + case Qboot: + if(s == DEVDOTDOT){ + devdir(c, (Qid){Qdir, 0, QTDIR}, "#/", 0, eve, 0555, dp); + return 1; + } + return devgen(c, name, bootlist.dir, bootlist.ndir, s, dp); + default: + if(s == DEVDOTDOT){ + if((int)c->qid.path < Qboot) + devdir(c, (Qid){Qdir, 0, QTDIR}, "#/", 0, eve, 0555, dp); + else + devdir(c, (Qid){Qboot, 0, QTDIR}, "#/", 0, eve, 0555, dp); + return 1; + } + if(s != 0) + return -1; + if((int)c->qid.path < Qboot){ + t = c->qid.path-1; + l = &rootlist; + }else{ + t = c->qid.path - Qboot - 1; + l = &bootlist; + } + if(t >= l->ndir) + return -1; +if(t < 0){ +print("rootgen %llud %d %d\n", c->qid.path, s, t); +panic("whoops"); +} + d = &l->dir[t]; + devdir(c, d->qid, d->name, d->length, eve, d->perm, dp); + return 1; + } +} + +static Walkqid* +rootwalk(Chan *c, Chan *nc, char **name, int nname) +{ + return devwalk(c, nc, name, nname, nil, 0, rootgen); +} + +static int +rootstat(Chan *c, uchar *dp, int n) +{ + return devstat(c, dp, n, nil, 0, rootgen); +} + +static Chan* +rootopen(Chan *c, int omode) +{ + return devopen(c, omode, nil, 0, devgen); +} + +/* + * sysremove() knows this is a nop + */ +static void +rootclose(Chan*) +{ +} + +static long +rootread(Chan *c, void *buf, long n, vlong off) +{ + ulong t; + Dirtab *d; + Dirlist *l; + uchar *data; + ulong offset = off; + + t = c->qid.path; + switch(t){ + case Qdir: + case Qboot: + return devdirread(c, buf, n, nil, 0, rootgen); + } + + if(t= l->ndir) + error(Egreg); + + d = &l->dir[t]; + data = l->data[t]; + if(offset >= d->length) + return 0; + if(offset+n > d->length) + n = d->length - offset; +#ifdef asdf +print("[%d] kaddr %.8ulx base %.8ulx offset %ld (%.8ulx), n %d %.8ulx %.8ulx %.8ulx\n", + t, buf, data, offset, offset, n, + ((ulong*)(data+offset))[0], + ((ulong*)(data+offset))[1], + ((ulong*)(data+offset))[2]); +#endif asdf + memmove(buf, data+offset, n); + return n; +} + +static long +rootwrite(Chan*, void*, long, vlong) +{ + error(Egreg); + return 0; +} + +Dev rootdevtab = { + '/', + "root", + + rootreset, + devinit, + devshutdown, + rootattach, + rootwalk, + rootstat, + rootopen, + devcreate, + rootclose, + rootread, + devbread, + rootwrite, + devbwrite, + devremove, + devwstat, +}; + --- /sys/src/9/bcm64/ether4330.c Thu Jan 1 00:00:00 1970 +++ /sys/src/9/bcm64/ether4330.c Tue Mar 10 14:11:33 2026 @@ -0,0 +1,2405 @@ +/* + * Broadcom bcm4330 wifi (sdio interface) + */ + +#include "u.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#include "io.h" +#include "../port/error.h" +#include "../port/netif.h" +#include "../port/sd.h" + +SDio *sdwifilink; + +#include "etherif.h" +#define CACHELINESZ 64 /* temp */ + +enum{ + SDIODEBUG = 0, + SBDEBUG = 0, + EVENTDEBUG = 0, + VARDEBUG = 0, + FWDEBUG = 0, + + Corescansz = 512, + Uploadsz = 2048, + + Wifichan = 0, /* default channel */ + Firmwarecmp = 1, + + ARMcm3 = 0x82A, + ARM7tdmi = 0x825, + ARMcr4 = 0x83E, + + Fn0 = 0, + Fn1 = 1, + Fn2 = 2, + Fbr1 = 0x100, + Fbr2 = 0x200, + + /* CCCR */ + Ioenable = 0x02, + Ioready = 0x03, + Intenable = 0x04, + Intpend = 0x05, + Ioabort = 0x06, + Busifc = 0x07, + Capability = 0x08, + Blksize = 0x10, + Highspeed = 0x13, + + /* SDIOCommands */ + GO_IDLE_STATE = 0, + SEND_RELATIVE_ADDR = 3, + IO_SEND_OP_COND = 5, + SELECT_CARD = 7, + VOLTAGE_SWITCH = 11, + IO_RW_DIRECT = 52, + IO_RW_EXTENDED = 53, + + /* SELECT_CARD args */ + Rcashift = 16, + + /* SEND_OP_COND args */ + Hcs = 1<<30, /* host supports SDHC & SDXC */ + V3_3 = 3<<20, /* 3.2-3.4 volts */ + V2_8 = 3<<15, /* 2.7-2.9 volts */ + V2_0 = 1<<8, /* 2.0-2.1 volts */ + S18R = 1<<24, /* switch to 1.8V request */ + + /* Sonics Silicon Backplane (access to cores on chip) */ + Sbwsize = 0x8000, + Sb32bit = 0x8000, + Sbaddr = 0x1000a, + Enumbase = 0x18000000, + Framectl= 0x1000d, + Rfhalt = 0x01, + Wfhalt = 0x02, + Clkcsr = 0x1000e, + ForceALP = 0x01, /* active low-power clock */ + ForceHT = 0x02, /* high throughput clock */ + ForceILP = 0x04, /* idle low-power clock */ + ReqALP = 0x08, + ReqHT = 0x10, + Nohwreq = 0x20, + ALPavail = 0x40, + HTavail = 0x80, + Pullups = 0x1000f, + Wfrmcnt = 0x10019, + Rfrmcnt = 0x1001b, + + /* core control regs */ + Ioctrl = 0x408, + Resetctrl = 0x800, + + /* socram regs */ + Coreinfo = 0x00, + Bankidx = 0x10, + Bankinfo = 0x40, + Bankpda = 0x44, + + /* armcr4 regs */ + Cr4Cap = 0x04, + Cr4Bankidx = 0x40, + Cr4Bankinfo = 0x44, + Cr4Cpuhalt = 0x20, + + /* chipcommon regs */ + Gpiopullup = 0x58, + Gpiopulldown = 0x5c, + Chipctladdr = 0x650, + Chipctldata = 0x654, + + /* sdio core regs */ + Intstatus = 0x20, + Fcstate = 1<<4, + Fcchange = 1<<5, + FrameInt = 1<<6, + MailboxInt = 1<<7, + Intmask = 0x24, + Sbmbox = 0x40, + Sbmboxdata = 0x48, + Hostmboxdata= 0x4c, + Fwready = 0x80, + + /* wifi control commands */ + GetVar = 262, + SetVar = 263, + + /* status */ + Disconnected= 0, + Connecting, + Connected, +}; + +typedef struct Ctlr Ctlr; + +enum{ + Wpa = 1, + Wep = 2, + Wpa2 = 3, + WNameLen = 32, + WNKeys = 4, + WKeyLen = 32, + WMinKeyLen = 5, + WMaxKeyLen = 13, +}; + +typedef struct WKey WKey; +struct WKey +{ + ushort len; + char dat[WKeyLen]; +}; + +struct Ctlr { + Ether* edev; + QLock cmdlock; + QLock pktlock; + QLock tlock; + QLock alock; + Lock txwinlock; + Rendez cmdr; + Rendez joinr; + int joinstatus; + int cryptotype; + int chanid; + char essid[WNameLen + 1]; + WKey keys[WNKeys]; + Block *rsp; + Block *scanb; + int scansecs; + int status; + int chipid; + int chiprev; + int armcore; + char *regufile; + union { + u32int i; + uchar c[4]; + } resetvec; + ulong chipcommon; + ulong armctl; + ulong armregs; + ulong d11ctl; + ulong socramregs; + ulong socramctl; + ulong sdregs; + int sdiorev; + int socramrev; + ulong socramsize; + ulong rambase; + short reqid; + uchar fcmask; + uchar txwindow; + uchar txseq; + uchar rxseq; +}; + +enum{ + CMauth, + CMchannel, + CMcrypt, + CMessid, + CMkey1, + CMkey2, + CMkey3, + CMkey4, + CMrxkey, + CMrxkey0, + CMrxkey1, + CMrxkey2, + CMrxkey3, + CMtxkey, + CMdebug, + CMjoin, +}; + +static Cmdtab cmds[] = { + {CMauth, "auth", 2}, + {CMchannel, "channel", 2}, + {CMcrypt, "crypt", 2}, + {CMessid, "essid", 2}, + {CMkey1, "key1", 2}, + {CMkey2, "key1", 2}, + {CMkey3, "key1", 2}, + {CMkey4, "key1", 2}, + {CMrxkey, "rxkey", 3}, + {CMrxkey0, "rxkey0", 3}, + {CMrxkey1, "rxkey1", 3}, + {CMrxkey2, "rxkey2", 3}, + {CMrxkey3, "rxkey3", 3}, + {CMtxkey, "txkey", 3}, + {CMdebug, "debug", 2}, + {CMjoin, "join", 5}, +}; + +typedef struct Sdpcm Sdpcm; +typedef struct Cmd Cmd; +struct Sdpcm { + uchar len[2]; + uchar lenck[2]; + uchar seq; + uchar chanflg; + uchar nextlen; + uchar doffset; + uchar fcmask; + uchar window; + uchar version; + uchar pad; +}; +#define SdpcmSize 12 + +struct Cmd { + uchar cmd[4]; + uchar len[4]; + uchar flags[2]; + uchar id[2]; + uchar status[4]; +}; + +static char config40181[] = "bcmdhd.cal.40181"; +static char config40183[] = "bcmdhd.cal.40183.26MHz"; + +struct { + int chipid; + int chiprev; + char *fwfile; + char *cfgfile; + char *regufile; +} firmware[] = { + { 0x4330, 3, "fw_bcm40183b1.bin", config40183, 0 }, + { 0x4330, 4, "fw_bcm40183b2.bin", config40183, 0 }, + { 43362, 0, "fw_bcm40181a0.bin", config40181, 0 }, + { 43362, 1, "fw_bcm40181a2.bin", config40181, 0 }, + { 43430, 1, "brcmfmac43430-sdio.bin", "brcmfmac43430-sdio.txt", 0 }, + { 43430, 2, "brcmfmac43436-sdio.bin", "brcmfmac43436-sdio.txt", "brcmfmac43436-sdio.clm_blob" }, +// { 0x4345, 6, "cyfmac43455-sdio.bin", "brcmfmac43455-sdio.txt", "cyfmac43455-sdio.clm_blob" }, + { 0x4345, 6, "brcmfmac43455-sdio.bin", "brcmfmac43455-sdio.txt", "brcmfmac43455-sdio.clm_blob" }, +}; + +static QLock sdiolock; +static int iodebug; + +extern int sdwificardintr(int); + +static void etherbcmintr(void *); +static void bcmevent(Ctlr*, uchar*, int); +static void wlscanresult(Ether*, uchar*, int); +static void wlsetvar(Ctlr*, char*, void*, int); + +static uchar* +put2(uchar *p, short v) +{ + p[0] = v; + p[1] = v >> 8; + return p + 2; +} + +static uchar* +put4(uchar *p, long v) +{ + p[0] = v; + p[1] = v >> 8; + p[2] = v >> 16; + p[3] = v >> 24; + return p + 4; +} + +static ushort +get2(uchar *p) +{ + return p[0] | p[1]<<8; +} + +static ulong +get4(uchar *p) +{ + return p[0] | p[1]<<8 | p[2]<<16 | p[3]<<24; +} + +static void +dump(char *s, void *a, int n) +{ + int i; + uchar *p; + + p = a; + print("%s:", s); + for(i = 0; i < n; i++) + print("%c%2.2x", i&15? ' ' : '\n', *p++); + print("\n"); +} + +/* + * SDIO communication with dongle + */ +static ulong +sdiocmd_locked(int cmd, ulong arg) +{ + u32int resp[4]; + + sdwifilink->cmd(cmd, arg, resp); + return resp[0]; +} + +static ulong +sdiocmd(int cmd, ulong arg) +{ + ulong r; + + qlock(&sdiolock); + if(waserror()){ + if(SDIODEBUG) print("sdiocmd error: cmd %d arg %lux\n", cmd, arg); + qunlock(&sdiolock); + nexterror(); + } + r = sdiocmd_locked(cmd, arg); + qunlock(&sdiolock); + poperror(); + return r; + +} + +static ulong +trysdiocmd(int cmd, ulong arg) +{ + ulong r; + + if(waserror()) + return 0; + r = sdiocmd(cmd, arg); + poperror(); + return r; +} + +static int +sdiord(int fn, int addr) +{ + int r; + + r = sdiocmd(IO_RW_DIRECT, (0<<31)|((fn&7)<<28)|((addr&0x1FFFF)<<9)); + if(r & 0xCF00){ + print("ether4330: sdiord(%x, %x) fail: %2.2ux %2.2ux\n", fn, addr, (r>>8)&0xFF, r&0xFF); + error(Eio); + } + return r & 0xFF; +} + +static void +sdiowr(int fn, int addr, int data) +{ + int r; + int retry; + + r = 0; + for(retry = 0; retry < 10; retry++){ + r = sdiocmd(IO_RW_DIRECT, (1<<31)|((fn&7)<<28)|((addr&0x1FFFF)<<9)|(data&0xFF)); + if((r & 0xCF00) == 0) + return; + } + print("ether4330: sdiowr(%x, %x, %x) fail: %2.2ux %2.2ux\n", fn, addr, data, (r>>8)&0xFF, r&0xFF); + error(Eio); +} + +static void +sdiorwext(int fn, int write, void *a, int len, int addr, int incr) +{ + int bsize, blk, bcount, m; + + bsize = fn == Fn2? 512 : 64; + while(len > 0){ + if(len >= 511*bsize){ + blk = 1; + bcount = 511; + m = bcount*bsize; + }else if(len > bsize){ + blk = 1; + bcount = len/bsize; + m = bcount*bsize; + }else{ + blk = 0; + bcount = len; + m = bcount; + } + qlock(&sdiolock); + if(waserror()){ + print("ether4330: sdiorwext fail: %s\n", up->errstr); + qunlock(&sdiolock); + nexterror(); + } + if(blk) + sdwifilink->iosetup(write, a, bsize, bcount); + else + sdwifilink->iosetup(write, a, bcount, 1); + sdiocmd_locked(IO_RW_EXTENDED, + write<<31 | (fn&7)<<28 | blk<<27 | incr<<26 | (addr&0x1FFFF)<<9 | (bcount&0x1FF)); + sdwifilink->io(write, a, m); + qunlock(&sdiolock); + poperror(); + len -= m; + a = (char*)a + m; + if(incr) + addr += m; + } +} + +static void +sdioset(int fn, int addr, int bits) +{ + sdiowr(fn, addr, sdiord(fn, addr) | bits); +} + +static void +sdioinit(void) +{ + ulong ocr, rca; + int i; + uchar alts[2][6] = { + { Alt4, Alt4, Alt4, Alt3, Alt4, Alt3 }, /* C0, C1 stepping */ + { Alt1, Alt1, Alt1, Alt1, Alt1, Alt1 }, /* D0 stepping */ + }; + + /* connect sdhci to wifi */ + for(i = 30; i <= 35; i++){ + gpiosel(i, alts[soc.dstepping][i-30]); + if(i == 30) + gpiopulloff(i); + else + gpiopullup(i); + } + /* wlan on */ + gpiosel(28, Output); + gpioout(28,0); + delay(10); + gpioout(28,1); + delay(200); + + sdwifilink->init(); + sdwifilink->enable(); + sdiocmd(GO_IDLE_STATE, 0); + ocr = trysdiocmd(IO_SEND_OP_COND, 0); + i = 0; + while((ocr & (1<<31)) == 0){ + if(++i > 5){ + print("ether4330: no response to sdio access: ocr = %lux\n", ocr); + error(Eio); + } + ocr = trysdiocmd(IO_SEND_OP_COND, V3_3); + tsleep(&up->sleep, return0, nil, 100); + } + rca = sdiocmd(SEND_RELATIVE_ADDR, 0) >> Rcashift; + sdiocmd(SELECT_CARD, rca << Rcashift); + sdioset(Fn0, Highspeed, 2); + sdioset(Fn0, Busifc, 2); /* bus width 4 */ + sdiowr(Fn0, Fbr1+Blksize, 64); + sdiowr(Fn0, Fbr1+Blksize+1, 64>>8); + sdiowr(Fn0, Fbr2+Blksize, 512); + sdiowr(Fn0, Fbr2+Blksize+1, 512>>8); + sdioset(Fn0, Ioenable, 1<sleep, return0, nil, 100); + } +} + +static void +sdioreset(void) +{ + sdiowr(Fn0, Ioabort, 1<<3); /* reset */ +} + +static void +sdioabort(int fn) +{ + sdiowr(Fn0, Ioabort, fn); +} + +/* + * Chip register and memory access via SDIO + */ + +static void +cfgw(ulong off, int val) +{ + sdiowr(Fn1, off, val); +} + +static int +cfgr(ulong off) +{ + return sdiord(Fn1, off); +} + +static ulong +cfgreadl(int fn, ulong off) +{ + uchar cbuf[2*CACHELINESZ]; + uchar *p; + + p = (uchar*)ROUND((uintptr)cbuf, CACHELINESZ); + memset(p, 0, 4); + sdiorwext(fn, 0, p, 4, off|Sb32bit, 1); + if(SDIODEBUG) print("cfgreadl %lux: %2.2x %2.2x %2.2x %2.2x\n", off, p[0], p[1], p[2], p[3]); + return p[0] | p[1]<<8 | p[2]<<16 | p[3]<<24; +} + +static void +cfgwritel(int fn, ulong off, u32int data) +{ + uchar cbuf[2*CACHELINESZ]; + uchar *p; + int retry; + + p = (uchar*)ROUND((uintptr)cbuf, CACHELINESZ); + put4(p, data); + if(SDIODEBUG) print("cfgwritel %lux: %2.2x %2.2x %2.2x %2.2x\n", off, p[0], p[1], p[2], p[3]); + retry = 0; + while(waserror()){ + print("ether4330: cfgwritel retry %lux %ux\n", off, data); + sdioabort(fn); + if(++retry == 3) + nexterror(); + } + sdiorwext(fn, 1, p, 4, off|Sb32bit, 1); + poperror(); +} + +static void +sbwindow(ulong addr) +{ + addr &= ~(Sbwsize-1); + cfgw(Sbaddr, addr>>8); + cfgw(Sbaddr+1, addr>>16); + cfgw(Sbaddr+2, addr>>24); +} + +static void +sbrw(int fn, int write, uchar *buf, int len, ulong off) +{ + int n; + USED(fn); + + if(waserror()){ + print("ether4330: sbrw err off %lux len %ud\n", off, len); + nexterror(); + } + if(write){ + if(len >= 4){ + n = len; + n &= ~3; + sdiorwext(Fn1, write, buf, n, off|Sb32bit, 1); + off += n; + buf += n; + len -= n; + } + while(len > 0){ + sdiowr(Fn1, off|Sb32bit, *buf); + off++; + buf++; + len--; + } + }else{ + if(len >= 4){ + n = len; + n &= ~3; + sdiorwext(Fn1, write, buf, n, off|Sb32bit, 1); + off += n; + buf += n; + len -= n; + } + while(len > 0){ + *buf = sdiord(Fn1, off|Sb32bit); + off++; + buf++; + len--; + } + } + poperror(); +} + +static void +sbmem(int write, uchar *buf, int len, ulong off) +{ + ulong n; + + n = ROUNDUP(off, Sbwsize) - off; + if(n == 0) + n = Sbwsize; + while(len > 0){ + if(n > len) + n = len; + sbwindow(off); + sbrw(Fn1, write, buf, n, off & (Sbwsize-1)); + off += n; + buf += n; + len -= n; + n = Sbwsize; + } +} + +static void +packetrw(int write, uchar *buf, int len) +{ + int n; + int retry; + + n = 2048; + while(len > 0){ + if(n > len) + n = ROUND(len, 4); + retry = 0; + while(waserror()){ + sdioabort(Fn2); + if(++retry == 3) + nexterror(); + } + sdiorwext(Fn2, write, buf, n, Enumbase, 0); + poperror(); + buf += n; + len -= n; + } +} + +/* + * Configuration and control of chip cores via Silicon Backplane + */ + +static void +sbdisable(ulong regs, int pre, int ioctl) +{ + sbwindow(regs); + if((cfgreadl(Fn1, regs + Resetctrl) & 1) != 0){ + cfgwritel(Fn1, regs + Ioctrl, 3|ioctl); + cfgreadl(Fn1, regs + Ioctrl); + return; + } + cfgwritel(Fn1, regs + Ioctrl, 3|pre); + cfgreadl(Fn1, regs + Ioctrl); + cfgwritel(Fn1, regs + Resetctrl, 1); + microdelay(10); + while((cfgreadl(Fn1, regs + Resetctrl) & 1) == 0) + ; + cfgwritel(Fn1, regs + Ioctrl, 3|ioctl); + cfgreadl(Fn1, regs + Ioctrl); +} + +static void +sbreset(ulong regs, int pre, int ioctl) +{ + sbdisable(regs, pre, ioctl); + sbwindow(regs); + if(SBDEBUG) print("sbreset %#lux %#lux %#lux ->", regs, + cfgreadl(Fn1, regs+Ioctrl), cfgreadl(Fn1, regs+Resetctrl)); + while((cfgreadl(Fn1, regs + Resetctrl) & 1) != 0){ + cfgwritel(Fn1, regs + Resetctrl, 0); + microdelay(40); + } + cfgwritel(Fn1, regs + Ioctrl, 1|ioctl); + cfgreadl(Fn1, regs + Ioctrl); + if(SBDEBUG) print("%#lux %#lux\n", + cfgreadl(Fn1, regs+Ioctrl), cfgreadl(Fn1, regs+Resetctrl)); +} + +static void +corescan(Ctlr *ctl, ulong r) +{ + uchar *buf; + int i, coreid, corerev; + ulong addr; + + buf = sdmalloc(Corescansz); + if(buf == nil) + error(Enomem); + sbmem(0, buf, Corescansz, r); + coreid = 0; + corerev = 0; + for(i = 0; i < Corescansz; i += 4){ + switch(buf[i]&0xF){ + case 0xF: /* end */ + sdfree(buf); + return; + case 0x1: /* core info */ + if((buf[i+4]&0xF) != 0x1) + break; + coreid = (buf[i+1] | buf[i+2]<<8) & 0xFFF; + i += 4; + corerev = buf[i+3]; + break; + case 0x05: /* address */ + addr = buf[i+1]<<8 | buf[i+2]<<16 | buf[i+3]<<24; + addr &= ~0xFFF; + if(SBDEBUG) print("core %x %s %#lux\n", coreid, buf[i]&0xC0? "ctl" : "mem", addr); + switch(coreid){ + case 0x800: + if((buf[i] & 0xC0) == 0) + ctl->chipcommon = addr; + break; + case ARMcm3: + case ARM7tdmi: + case ARMcr4: + ctl->armcore = coreid; + if(buf[i] & 0xC0){ + if(ctl->armctl == 0) + ctl->armctl = addr; + }else{ + if(ctl->armregs == 0) + ctl->armregs = addr; + } + break; + case 0x80E: + if(buf[i] & 0xC0) + ctl->socramctl = addr; + else if(ctl->socramregs == 0) + ctl->socramregs = addr; + ctl->socramrev = corerev; + break; + case 0x829: + if((buf[i] & 0xC0) == 0) + ctl->sdregs = addr; + ctl->sdiorev = corerev; + break; + case 0x812: + if(buf[i] & 0xC0) + ctl->d11ctl = addr; + break; + } + } + } + sdfree(buf); +} + +static void +ramscan(Ctlr *ctl) +{ + ulong r, n, size; + int banks, i; + + if(ctl->armcore == ARMcr4){ + r = ctl->armregs; + sbwindow(r); + n = cfgreadl(Fn1, r + Cr4Cap); + if(SBDEBUG) print("cr4 banks %lux\n", n); + banks = ((n>>4) & 0xF) + (n & 0xF); + size = 0; + for(i = 0; i < banks; i++){ + cfgwritel(Fn1, r + Cr4Bankidx, i); + n = cfgreadl(Fn1, r + Cr4Bankinfo); + if(SBDEBUG) print("bank %d reg %lux size %lud\n", i, n, 8192 * ((n & 0x3F) + 1)); + size += 8192 * ((n & 0x3F) + 1); + } + ctl->socramsize = size; + ctl->rambase = 0x198000; + return; + } + if(ctl->socramrev <= 7 || ctl->socramrev == 12){ + print("ether4330: SOCRAM rev %d not supported\n", ctl->socramrev); + error(Eio); + } + sbreset(ctl->socramctl, 0, 0); + r = ctl->socramregs; + sbwindow(r); + n = cfgreadl(Fn1, r + Coreinfo); + if(SBDEBUG) print("socramrev %d coreinfo %lux\n", ctl->socramrev, n); + banks = (n>>4) & 0xF; + size = 0; + for(i = 0; i < banks; i++){ + cfgwritel(Fn1, r + Bankidx, i); + n = cfgreadl(Fn1, r + Bankinfo); + if(SBDEBUG) print("bank %d reg %lux size %lud\n", i, n, 8192 * ((n & 0x3F) + 1)); + size += 8192 * ((n & 0x3F) + 1); + } + ctl->socramsize = size; + ctl->rambase = 0; + if(ctl->chipid == 43430){ + cfgwritel(Fn1, r + Bankidx, 3); + cfgwritel(Fn1, r + Bankpda, 0); + } +} + +static void +sbinit(Ctlr *ctl) +{ + ulong r; + int chipid; + char buf[16]; + + sbwindow(Enumbase); + r = cfgreadl(Fn1, Enumbase); + chipid = r & 0xFFFF; + sprint(buf, chipid > 43000 ? "%d" : "%#x", chipid); + print("ether4330: chip %s rev %ld type %ld\n", buf, (r>>16)&0xF, (r>>28)&0xF); + switch(chipid){ + case 0x4330: + case 43362: + case 43430: + case 0x4345: + ctl->chipid = chipid; + ctl->chiprev = (r>>16)&0xF; + break; + default: + print("ether4330: chipid %#x (%d) not supported\n", chipid, chipid); + error(Eio); + } + r = cfgreadl(Fn1, Enumbase + 63*4); + corescan(ctl, r); + if(ctl->armctl == 0 || ctl->d11ctl == 0 || + (ctl->armcore == ARMcm3 && (ctl->socramctl == 0 || ctl->socramregs == 0))) + error("corescan didn't find essential cores\n"); + if(ctl->armcore == ARMcr4) + sbreset(ctl->armctl, Cr4Cpuhalt, Cr4Cpuhalt); + else + sbdisable(ctl->armctl, 0, 0); + sbreset(ctl->d11ctl, 8|4, 4); + ramscan(ctl); + if(SBDEBUG) print("ARM %#lux D11 %#lux SOCRAM %#lux%#lux %lud bytes @ %#lux\n", + ctl->armctl, ctl->d11ctl, ctl->socramctl, ctl->socramregs, ctl->socramsize, ctl->rambase); + cfgw(Clkcsr, 0); + microdelay(10); + if(SBDEBUG) print("chipclk: %x\n", cfgr(Clkcsr)); + cfgw(Clkcsr, Nohwreq | ReqALP); + while((cfgr(Clkcsr) & (HTavail|ALPavail)) == 0) + microdelay(10); + cfgw(Clkcsr, Nohwreq | ForceALP); + microdelay(65); + if(SBDEBUG) print("chipclk: %x\n", cfgr(Clkcsr)); + cfgw(Pullups, 0); + sbwindow(ctl->chipcommon); + cfgwritel(Fn1, ctl->chipcommon + Gpiopullup, 0); + cfgwritel(Fn1, ctl->chipcommon + Gpiopulldown, 0); + if(ctl->chipid != 0x4330 && ctl->chipid != 43362) + return; + cfgwritel(Fn1, ctl->chipcommon + Chipctladdr, 1); + if(cfgreadl(Fn1, ctl->chipcommon + Chipctladdr) != 1) + print("ether4330: can't set Chipctladdr\n"); + else{ + r = cfgreadl(Fn1, ctl->chipcommon + Chipctldata); + if(SBDEBUG) print("chipcommon PMU (%lux) %lux", cfgreadl(Fn1, ctl->chipcommon + Chipctladdr), r); + /* set SDIO drive strength >= 6mA */ + r &= ~0x3800; + if(ctl->chipid == 0x4330) + r |= 3<<11; + else + r |= 7<<11; + cfgwritel(Fn1, ctl->chipcommon + Chipctldata, r); + if(SBDEBUG) print("-> %lux (= %lux)\n", r, cfgreadl(Fn1, ctl->chipcommon + Chipctldata)); + } +} + +static void +sbenable(Ctlr *ctl) +{ + int i; + + if(SBDEBUG) print("enabling HT clock..."); + cfgw(Clkcsr, 0); + delay(1); + cfgw(Clkcsr, ReqHT); + for(i = 0; (cfgr(Clkcsr) & HTavail) == 0; i++){ + if(i == 50){ + print("ether4330: can't enable HT clock: csr %x\n", cfgr(Clkcsr)); + error(Eio); + } + tsleep(&up->sleep, return0, nil, 100); + } + cfgw(Clkcsr, cfgr(Clkcsr) | ForceHT); + delay(10); + if(SBDEBUG) print("chipclk: %x\n", cfgr(Clkcsr)); + sbwindow(ctl->sdregs); + cfgwritel(Fn1, ctl->sdregs + Sbmboxdata, 4 << 16); /* protocol version */ + cfgwritel(Fn1, ctl->sdregs + Intmask, FrameInt | MailboxInt | Fcchange); + sdioset(Fn0, Ioenable, 1<sleep, return0, nil, 100); + } + sdiowr(Fn0, Intenable, (1<genbuf, sizeof up->genbuf, "can't find %s in /boot or /sys/lib/firmware", file); + error(up->genbuf); + } + return c; +} + +static int +upload(Ctlr *ctl, char *file, int isconfig) +{ + Chan *c; + uchar *buf; + uchar *cbuf; + int off, n; + + buf = cbuf = nil; + c = findfirmware(file); + if(waserror()){ + cclose(c); + sdfree(buf); + sdfree(cbuf); + nexterror(); + } + buf = sdmalloc(Uploadsz); + if(buf == nil) + error(Enomem); + if(Firmwarecmp){ + cbuf = sdmalloc(Uploadsz); + if(cbuf == nil) + error(Enomem); + } + off = 0; + for(;;){ + n = devtab[c->type]->read(c, buf, Uploadsz, off); + if(n <= 0) + break; + if(isconfig){ + n = condense(buf, n); + off = ctl->socramsize - n - 4; + }else if(off == 0) + memmove(ctl->resetvec.c, buf, sizeof(ctl->resetvec.c)); + while(n&3) + buf[n++] = 0; + sbmem(1, buf, n, ctl->rambase + off); + if(isconfig) + break; + off += n; + } + if(Firmwarecmp){ + if(FWDEBUG) print("compare..."); + if(!isconfig) + off = 0; + for(;;){ + if(!isconfig){ + n = devtab[c->type]->read(c, buf, Uploadsz, off); + if(n <= 0) + break; + while(n&3) + buf[n++] = 0; + } + sbmem(0, cbuf, n, ctl->rambase + off); + if(memcmp(buf, cbuf, n) != 0){ + print("ether4330: firmware load failed offset %d\n", off); + error(Eio); + } + if(isconfig) + break; + off += n; + } + } + if(FWDEBUG) print("\n"); + poperror(); + cclose(c); + sdfree(buf); + sdfree(cbuf); + return n; +} + +/* + * Upload regulatory file (.clm) to firmware. + * Packet format is + * [2]flag [2]type [4]len [4]crc [len]data + */ +static void +reguload(Ctlr *ctl, char *file) +{ + Chan *c; + uchar *buf; + int off, n, flag; + enum { + Reguhdr = 2+2+4+4, + Regusz = 1400, + Regutyp = 2, + Flagclm = 1<<12, + Firstpkt= 1<<1, + Lastpkt = 1<<2, + }; + + buf = nil; + c = findfirmware(file); + if(waserror()){ + cclose(c); + free(buf); + nexterror(); + } + buf = malloc(Reguhdr+Regusz+1); + if(buf == nil) + error(Enomem); + put2(buf+2, Regutyp); + put2(buf+8, 0); + off = 0; + flag = Flagclm | Firstpkt; + while((flag&Lastpkt) == 0){ + n = devtab[c->type]->read(c, buf+Reguhdr, Regusz+1, off); + if(n <= 0) + break; + if(n == Regusz+1) + --n; + else{ + while(n&7) + buf[Reguhdr+n++] = 0; + flag |= Lastpkt; + } + put2(buf+0, flag); + put4(buf+4, n); + wlsetvar(ctl, "clmload", buf, Reguhdr + n); + off += n; + flag &= ~Firstpkt; + } + poperror(); + cclose(c); + free(buf); +} + +static void +fwload(Ctlr *ctl) +{ + uchar buf[4]; + uint i, n; + + i = 0; + while(firmware[i].chipid != ctl->chipid || + firmware[i].chiprev != ctl->chiprev){ + if(++i == nelem(firmware)){ + print("ether4330: no firmware for chipid %x (%d) chiprev %d\n", + ctl->chipid, ctl->chipid, ctl->chiprev); + error("no firmware"); + } + } + ctl->regufile = firmware[i].regufile; + cfgw(Clkcsr, ReqALP); + while((cfgr(Clkcsr) & ALPavail) == 0) + microdelay(10); + memset(buf, 0, 4); + sbmem(1, buf, 4, ctl->rambase + ctl->socramsize - 4); + if(FWDEBUG) print("firmware load %s...", firmware[i].fwfile); + upload(ctl, firmware[i].fwfile, 0); + if(FWDEBUG) print("config load..."); + n = upload(ctl, firmware[i].cfgfile, 1); + n /= 4; + n = (n & 0xFFFF) | (~n << 16); + put4(buf, n); + sbmem(1, buf, 4, ctl->rambase + ctl->socramsize - 4); + if(ctl->armcore == ARMcr4){ + sbwindow(ctl->sdregs); + cfgwritel(Fn1, ctl->sdregs + Intstatus, ~0); + if(ctl->resetvec.i != 0){ + if(SBDEBUG) print("%ux\n", ctl->resetvec.i); + sbmem(1, ctl->resetvec.c, sizeof(ctl->resetvec.c), 0); + } + sbreset(ctl->armctl, Cr4Cpuhalt, 0); + }else + sbreset(ctl->armctl, 0, 0); +} + +/* + * Communication of data and control packets + */ + +void +intwait(Ctlr *ctlr, int wait) +{ + ulong ints, mbox; + int i; + + if(waserror()) + return; + for(;;){ + sdwificardintr(wait); + sbwindow(ctlr->sdregs); + i = sdiord(Fn0, Intpend); + if(i == 0){ + if(0) print("INTS(%lux)\n", cfgreadl(Fn1, ctlr->sdregs + Intstatus)); + tsleep(&up->sleep, return0, 0, 10); + continue; + } + ints = cfgreadl(Fn1, ctlr->sdregs + Intstatus); + cfgwritel(Fn1, ctlr->sdregs + Intstatus, ints); + if(0) print("INTS: (%x) %lux -> %lux\n", i, ints, cfgreadl(Fn1, ctlr->sdregs + Intstatus)); + if(ints & MailboxInt){ + mbox = cfgreadl(Fn1, ctlr->sdregs + Hostmboxdata); + cfgwritel(Fn1, ctlr->sdregs + Sbmbox, 2); /* ack */ + if(mbox & 0x8) + print("ether4330: firmware ready\n"); + } + if(ints & FrameInt) + break; + } + poperror(); +} + +static Block* +wlreadpkt(Ctlr *ctl) +{ + Block *b; + Sdpcm *p; + int len, lenck; + + b = allocb(2048); + p = (Sdpcm*)b->wp; + qlock(&ctl->pktlock); + for(;;){ + packetrw(0, b->wp, SdpcmSize); + len = p->len[0] | p->len[1]<<8; + if(len == 0){ + freeb(b); + b = nil; + break; + } + lenck = p->lenck[0] | p->lenck[1]<<8; + if(lenck != (len ^ 0xFFFF) || + len < SdpcmSize || len > 2048){ + print("ether4330: wlreadpkt error len %.4x lenck %.4x\n", len, lenck); + cfgw(Framectl, Rfhalt); + while(cfgr(Rfrmcnt+1)) + ; + while(cfgr(Rfrmcnt)) + ; + continue; + } + if(len > SdpcmSize) + packetrw(0, b->wp + SdpcmSize, len - SdpcmSize); + b->wp += len; + break; + } + qunlock(&ctl->pktlock); + return b; +} + +static void +txstart(Ether *edev) +{ + Ctlr *ctl; + Sdpcm *p; + Block *b; + int len, off; + + ctl = edev->ctlr; + if(!canqlock(&ctl->tlock)) + return; + if(waserror()){ + qunlock(&ctl->tlock); + return; + } + for(;;){ + lock(&ctl->txwinlock); + if(ctl->txseq == ctl->txwindow){ + //print("f"); + unlock(&ctl->txwinlock); + break; + } + if(ctl->fcmask & 1<<2){ + //print("x"); + unlock(&ctl->txwinlock); + break; + } + unlock(&ctl->txwinlock); + b = qget(edev->oq); + if(b == nil) + break; + off = ((uintptr)b->rp & 3) + SdpcmSize; + b = padblock(b, off + 4); + len = BLEN(b); + p = (Sdpcm*)b->rp; + memset(p, 0, off); /* TODO: refactor dup code */ + put2(p->len, len); + put2(p->lenck, ~len); + p->chanflg = 2; + p->seq = ctl->txseq; + p->doffset = off; + put4(b->rp + off, 0x20); /* BDC header */ + if(iodebug) dump("send", b->rp, len); + qlock(&ctl->pktlock); + if(waserror()){ + if(iodebug) print("halt frame %x %x\n", cfgr(Wfrmcnt+1), cfgr(Wfrmcnt+1)); + cfgw(Framectl, Wfhalt); + while(cfgr(Wfrmcnt+1)) + ; + while(cfgr(Wfrmcnt)) + ; + qunlock(&ctl->pktlock); + nexterror(); + } + packetrw(1, b->rp, len); + ctl->txseq++; + poperror(); + qunlock(&ctl->pktlock); + freeb(b); + } + poperror(); + qunlock(&ctl->tlock); +} + +static void +rproc(void *a) +{ + Ether *edev; + Ctlr *ctl; + Block *b; + Sdpcm *p; + Cmd *q; + int flowstart; + int bdc; + + edev = a; + ctl = edev->ctlr; + flowstart = 0; + for(;;){ + if(flowstart){ + //print("F"); + flowstart = 0; + txstart(edev); + } + b = wlreadpkt(ctl); + if(b == nil){ + intwait(ctl, 1); + continue; + } + p = (Sdpcm*)b->rp; + if(p->window != ctl->txwindow || p->fcmask != ctl->fcmask){ + lock(&ctl->txwinlock); + if(p->window != ctl->txwindow){ + if(ctl->txseq == ctl->txwindow) + flowstart = 1; + ctl->txwindow = p->window; + } + if(p->fcmask != ctl->fcmask){ + if((p->fcmask & 1<<2) == 0) + flowstart = 1; + ctl->fcmask = p->fcmask; + } + unlock(&ctl->txwinlock); + } + switch(p->chanflg & 0xF){ + case 0: + if(iodebug) dump("rsp", b->rp, BLEN(b)); + if(BLEN(b) < SdpcmSize + sizeof(Cmd)) + break; + q = (Cmd*)(b->rp + SdpcmSize); + if((q->id[0] | q->id[1]<<8) != ctl->reqid) + break; + ctl->rsp = b; + wakeup(&ctl->cmdr); + continue; + case 1: + if(iodebug) dump("event", b->rp, BLEN(b)); + if(BLEN(b) > p->doffset + 4){ + bdc = 4 + (b->rp[p->doffset + 3] << 2); + if(BLEN(b) > p->doffset + bdc){ + b->rp += p->doffset + bdc; /* skip BDC header */ + bcmevent(ctl, b->rp, BLEN(b)); + break; + } + } + if(iodebug && BLEN(b) != p->doffset) + print("short event %ld %d\n", (long)BLEN(b), p->doffset); + break; + case 2: + if(iodebug) dump("packet", b->rp, BLEN(b)); + if(BLEN(b) > p->doffset + 4){ + bdc = 4 + (b->rp[p->doffset + 3] << 2); + if(BLEN(b) >= p->doffset + bdc + ETHERHDRSIZE){ + b->rp += p->doffset + bdc; /* skip BDC header */ + etheriq(edev, b, 1); + continue; + } + } + break; + default: + dump("ether4330: bad packet", b->rp, BLEN(b)); + break; + } + freeb(b); + } +} + +static void +linkdown(Ctlr *ctl) +{ + Ether *edev; + Netfile *f; + int i; + + edev = ctl->edev; + if(edev == nil || ctl->status != Connected) + return; + ctl->status = Disconnected; + /* send eof to aux/wpa */ + for(i = 0; i < edev->nfile; i++){ + f = edev->f[i]; + if(f == nil || f->in == nil || f->inuse == 0 || f->type != 0x888e) + continue; + qwrite(f->in, 0, 0); + } +} + +/* + * Command interface between host and firmware + */ + +static char *eventnames[] = { + [0] = "set ssid", + [1] = "join", + [2] = "start", + [3] = "auth", + [4] = "auth ind", + [5] = "deauth", + [6] = "deauth ind", + [7] = "assoc", + [8] = "assoc ind", + [9] = "reassoc", + [10] = "reassoc ind", + [11] = "disassoc", + [12] = "disassoc ind", + [13] = "quiet start", + [14] = "quiet end", + [15] = "beacon rx", + [16] = "link", + [17] = "mic error", + [18] = "ndis link", + [19] = "roam", + [20] = "txfail", + [21] = "pmkid cache", + [22] = "retrograde tsf", + [23] = "prune", + [24] = "autoauth", + [25] = "eapol msg", + [26] = "scan complete", + [27] = "addts ind", + [28] = "delts ind", + [29] = "bcnsent ind", + [30] = "bcnrx msg", + [31] = "bcnlost msg", + [32] = "roam prep", + [33] = "pfn net found", + [34] = "pfn net lost", + [35] = "reset complete", + [36] = "join start", + [37] = "roam start", + [38] = "assoc start", + [39] = "ibss assoc", + [40] = "radio", + [41] = "psm watchdog", + [44] = "probreq msg", + [45] = "scan confirm ind", + [46] = "psk sup", + [47] = "country code changed", + [48] = "exceeded medium time", + [49] = "icv error", + [50] = "unicast decode error", + [51] = "multicast decode error", + [52] = "trace", + [53] = "bta hci event", + [54] = "if", + [55] = "p2p disc listen complete", + [56] = "rssi", + [57] = "pfn scan complete", + [58] = "extlog msg", + [59] = "action frame", + [60] = "action frame complete", + [61] = "pre assoc ind", + [62] = "pre reassoc ind", + [63] = "channel adopted", + [64] = "ap started", + [65] = "dfs ap stop", + [66] = "dfs ap resume", + [67] = "wai sta event", + [68] = "wai msg", + [69] = "escan result", + [70] = "action frame off chan complete", + [71] = "probresp msg", + [72] = "p2p probreq msg", + [73] = "dcs request", + [74] = "fifo credit map", + [75] = "action frame rx", + [76] = "wake event", + [77] = "rm complete", + [78] = "htsfsync", + [79] = "overlay req", + [80] = "csa complete ind", + [81] = "excess pm wake event", + [82] = "pfn scan none", + [83] = "pfn scan allgone", + [84] = "gtk plumbed", + [85] = "assoc ind ndis", + [86] = "reassoc ind ndis", + [87] = "assoc req ie", + [88] = "assoc resp ie", + [89] = "assoc recreated", + [90] = "action frame rx ndis", + [91] = "auth req", + [92] = "tdls peer event", + [127] = "bcmc credit support" +}; + +static char* +evstring(uint event) +{ + static char buf[12]; + + if(event >= nelem(eventnames) || eventnames[event] == 0){ + /* not reentrant but only called from one kproc */ + snprint(buf, sizeof buf, "%d", event); + return buf; + } + return eventnames[event]; +} + +static void +bcmevent(Ctlr *ctl, uchar *p, int len) +{ + int flags; + long event, status, reason; + + if(len < ETHERHDRSIZE + 10 + 46) + return; + p += ETHERHDRSIZE + 10; /* skip bcm_ether header */ + len -= ETHERHDRSIZE + 10; + flags = nhgets(p + 2); + event = nhgets(p + 6); + status = nhgetl(p + 8); + reason = nhgetl(p + 12); + if(EVENTDEBUG) + print("ether4330: [%s] status %ld flags %#x reason %ld\n", + evstring(event), status, flags, reason); + switch(event){ + case 19: /* E_ROAM */ + if(status == 0) + break; + /* fall through */ + case 0: /* E_SET_SSID */ + ctl->joinstatus = 1 + status; + wakeup(&ctl->joinr); + break; + case 16: /* E_LINK */ + if(flags&1) /* link up */ + break; + /* fall through */ + case 5: /* E_DEAUTH */ + case 6: /* E_DEAUTH_IND */ + case 12: /* E_DISASSOC_IND */ + linkdown(ctl); + break; + case 26: /* E_SCAN_COMPLETE */ + break; + case 69: /* E_ESCAN_RESULT */ + wlscanresult(ctl->edev, p + 48, len - 48); + break; + default: + if(status){ + if(!EVENTDEBUG) + print("ether4330: [%s] error status %ld flags %#x reason %ld\n", + evstring(event), status, flags, reason); + dump("event", p, len); + } + } +} + +static int +joindone(void *a) +{ + return ((Ctlr*)a)->joinstatus; +} + +static int +waitjoin(Ctlr *ctl) +{ + int n; + + sleep(&ctl->joinr, joindone, ctl); + n = ctl->joinstatus; + ctl->joinstatus = 0; + return n - 1; +} + +static int +cmddone(void *a) +{ + return ((Ctlr*)a)->rsp != nil; +} + +static void +wlcmd(Ctlr *ctl, int write, int op, void *data, int dlen, void *res, int rlen) +{ + Block *b; + Sdpcm *p; + Cmd *q; + int len, tlen; + + if(write) + tlen = dlen + rlen; + else + tlen = MAX(dlen, rlen); + len = SdpcmSize + sizeof(Cmd) + tlen; + b = allocb(len); + qlock(&ctl->cmdlock); + if(waserror()){ + freeb(b); + qunlock(&ctl->cmdlock); + nexterror(); + } + memset(b->wp, 0, len); + qlock(&ctl->pktlock); + p = (Sdpcm*)b->wp; + put2(p->len, len); + put2(p->lenck, ~len); + p->seq = ctl->txseq; + p->doffset = SdpcmSize; + b->wp += SdpcmSize; + + q = (Cmd*)b->wp; + put4(q->cmd, op); + put4(q->len, tlen); + put2(q->flags, write? 2 : 0); + put2(q->id, ++ctl->reqid); + put4(q->status, 0); + b->wp += sizeof(*q); + + if(dlen > 0) + memmove(b->wp, data, dlen); + if(write) + memmove(b->wp + dlen, res, rlen); + b->wp += tlen; + + if(iodebug) dump("cmd", b->rp, len); + packetrw(1, b->rp, len); + ctl->txseq++; + qunlock(&ctl->pktlock); + freeb(b); + b = nil; + USED(b); + sleep(&ctl->cmdr, cmddone, ctl); + b = ctl->rsp; + ctl->rsp = nil; + assert(b != nil); + p = (Sdpcm*)b->rp; + q = (Cmd*)(b->rp + p->doffset); + if(q->status[0] | q->status[1] | q->status[2] | q->status[3]){ + print("ether4330: cmd %d error status %ld\n", op, get4(q->status)); + dump("ether4330: cmd error", b->rp, BLEN(b)); + error("wlcmd error"); + } + if(!write) + memmove(res, q + 1, rlen); + freeb(b); + qunlock(&ctl->cmdlock); + poperror(); +} + +static void +wlcmdint(Ctlr *ctl, int op, int val) +{ + uchar buf[4]; + + put4(buf, val); + wlcmd(ctl, 1, op, buf, 4, nil, 0); +} + +static void +wlgetvar(Ctlr *ctl, char *name, void *val, int len) +{ + wlcmd(ctl, 0, GetVar, name, strlen(name) + 1, val, len); +} + +static void +wlsetvar(Ctlr *ctl, char *name, void *val, int len) +{ + if(VARDEBUG){ + char buf[32]; + snprint(buf, sizeof buf, "wlsetvar %s:", name); + dump(buf, val, len > 32? 32 : len); + } + wlcmd(ctl, 1, SetVar, name, strlen(name) + 1, val, len); +} + +static void +wlsetint(Ctlr *ctl, char *name, int val) +{ + uchar buf[4]; + + put4(buf, val); + wlsetvar(ctl, name, buf, 4); +} + +static void +wlwepkey(Ctlr *ctl, int i) +{ + uchar params[164]; + uchar *p; + + memset(params, 0, sizeof params); + p = params; + p = put4(p, i); /* index */ + p = put4(p, ctl->keys[i].len); + memmove(p, ctl->keys[i].dat, ctl->keys[i].len); + p += 32 + 18*4; /* keydata, pad */ + if(ctl->keys[i].len == WMinKeyLen) + p = put4(p, 1); /* algo = WEP1 */ + else + p = put4(p, 3); /* algo = WEP128 */ + put4(p, 2); /* flags = Primarykey */ + + wlsetvar(ctl, "wsec_key", params, sizeof params); +} + +static void +memreverse(char *dst, char *src, int len) +{ + src += len; + while(len-- > 0) + *dst++ = *--src; +} + +static void +wlwpakey(Ctlr *ctl, int id, uvlong iv, uchar *ea) +{ + uchar params[164]; + uchar *p; + int pairwise; + + if(id == CMrxkey) + return; + pairwise = (id == CMrxkey || id == CMtxkey); + memset(params, 0, sizeof params); + p = params; + if(pairwise) + p = put4(p, 0); + else + p = put4(p, id - CMrxkey0); /* group key id */ + p = put4(p, ctl->keys[0].len); + memmove((char*)p, ctl->keys[0].dat, ctl->keys[0].len); + p += 32 + 18*4; /* keydata, pad */ + if(ctl->cryptotype == Wpa) + p = put4(p, 2); /* algo = TKIP */ + else + p = put4(p, 4); /* algo = AES_CCM */ + if(pairwise) + p = put4(p, 0); + else + p = put4(p, 2); /* flags = Primarykey */ + p += 3*4; + p = put4(p, 0); //pairwise); /* iv initialised */ + p += 4; + p = put4(p, iv>>16); /* iv high */ + p = put2(p, iv&0xFFFF); /* iv low */ + p += 2 + 2*4; /* align, pad */ + if(pairwise) + memmove(p, ea, Eaddrlen); + + wlsetvar(ctl, "wsec_key", params, sizeof params); +} + +static void +wljoin(Ctlr *ctl, char *ssid, int chan) +{ + uchar params[72]; + uchar *p; + int n; + + if(chan != 0) + chan |= 0x2b00; /* 20Mhz channel width */ + p = params; + n = strlen(ssid); + n = MIN(n, 32); + p = put4(p, n); + memmove(p, ssid, n); + memset(p + n, 0, 32 - n); + p += 32; + p = put4(p, 0xff); /* scan type */ + if(chan != 0){ + p = put4(p, 2); /* num probes */ + p = put4(p, 120); /* active time */ + p = put4(p, 390); /* passive time */ + }else{ + p = put4(p, -1); /* num probes */ + p = put4(p, -1); /* active time */ + p = put4(p, -1); /* passive time */ + } + p = put4(p, -1); /* home time */ + memset(p, 0xFF, Eaddrlen); /* bssid */ + p += Eaddrlen; + p = put2(p, 0); /* pad */ + if(chan != 0){ + p = put4(p, 1); /* num chans */ + p = put2(p, chan); /* chan spec */ + p = put2(p, 0); /* pad */ + assert(p == params + sizeof(params)); + }else{ + p = put4(p, 0); /* num chans */ + assert(p == params + sizeof(params) - 4); + } + + wlsetvar(ctl, "join", params, chan? sizeof params : sizeof params - 4); + ctl->status = Connecting; + switch(waitjoin(ctl)){ + case 0: + ctl->status = Connected; + break; + case 3: + ctl->status = Disconnected; + error("wifi join: network not found"); + case 1: + ctl->status = Disconnected; + error("wifi join: failed"); + default: + ctl->status = Disconnected; + error("wifi join: error"); + } +} + +static void +wlscanstart(Ctlr *ctl) +{ + /* version[4] action[2] sync_id[2] ssidlen[4] ssid[32] bssid[6] bss_type[1] + scan_type[1] nprobes[4] active_time[4] passive_time[4] home_time[4] + nchans[2] nssids[2] chans[nchans][2] ssids[nssids][32] */ + /* hack - this is only correct on a little-endian cpu */ + static uchar params[4+2+2+4+32+6+1+1+4*4+2+2+14*2+32+4] = { + 1,0,0,0, + 1,0, + 0x34,0x12, + 0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0xff,0xff,0xff,0xff,0xff,0xff, + 2, + 0, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 14,0, + 1,0, + 0x01,0x2b,0x02,0x2b,0x03,0x2b,0x04,0x2b,0x05,0x2e,0x06,0x2e,0x07,0x2e, + 0x08,0x2b,0x09,0x2b,0x0a,0x2b,0x0b,0x2b,0x0c,0x2b,0x0d,0x2b,0x0e,0x2b, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + }; + + wlcmdint(ctl, 49, 0); /* PASSIVE_SCAN */ + wlsetvar(ctl, "escan", params, sizeof params); +} + +static uchar* +gettlv(uchar *p, uchar *ep, int tag) +{ + int len; + + while(p + 1 < ep){ + len = p[1]; + if(p + 2 + len > ep) + return nil; + if(p[0] == tag) + return p; + p += 2 + len; + } + return nil; +} + +static void +addscan(Block *bp, uchar *p, int len) +{ + char bssid[24]; + char *auth, *auth2; + uchar *t, *et; + int ielen; + static uchar wpaie1[4] = { 0x00, 0x50, 0xf2, 0x01 }; + + snprint(bssid, sizeof bssid, ";bssid=%E", p + 8); + if(strstr((char*)bp->rp, bssid) != nil) + return; + bp->wp = (uchar*)seprint((char*)bp->wp, (char*)bp->lim, + "ssid=%.*s%s;signal=%d;noise=%d;chan=%d", + p[18], (char*)p+19, bssid, + (short)get2(p+78), (signed char)p[80], + get2(p+72) & 0xF); + auth = auth2 = ""; + if(get2(p + 16) & 0x10) + auth = ";wep"; + ielen = get4(p + 0x78); + if(ielen > 0){ + t = p + get4(p + 0x74); + et = t + ielen; + if(et > p + len) + return; + if(gettlv(t, et, 0x30) != nil){ + auth = ""; + auth2 = ";wpa2"; + } + while((t = gettlv(t, et, 0xdd)) != nil){ + if(t[1] > 4 && memcmp(t+2, wpaie1, 4) == 0){ + auth = ";wpa"; + break; + } + t += 2 + t[1]; + } + } + bp->wp = (uchar*)seprint((char*)bp->wp, (char*)bp->lim, + "%s%s\n", auth, auth2); +} + + +static void +wlscanresult(Ether *edev, uchar *p, int len) +{ + Ctlr *ctlr; + Netfile **ep, *f, **fp; + Block *bp; + int nbss, i; + + ctlr = edev->ctlr; + if(get4(p) > len) + return; + /* TODO: more syntax checking */ + bp = ctlr->scanb; + if(bp == nil) + ctlr->scanb = bp = allocb(8192); + nbss = get2(p+10); + p += 12; + len -= 12; + if(0) dump("SCAN", p, len); + if(nbss){ + addscan(bp, p, len); + return; + } + i = edev->scan; + ep = &edev->f[Ntypes]; + for(fp = edev->f; fp < ep && i > 0; fp++){ + f = *fp; + if(f == nil || f->scan == 0) + continue; + if(i == 1) + qpass(f->in, bp); + else + qpass(f->in, copyblock(bp, BLEN(bp))); + i--; + } + if(i) + freeb(bp); + ctlr->scanb = nil; +} + +static void +lproc(void *a) +{ + Ether *edev; + Ctlr *ctlr; + int secs; + + edev = a; + ctlr = edev->ctlr; + secs = 0; + for(;;){ + tsleep(&up->sleep, return0, 0, 1000); + if(ctlr->scansecs){ + if(secs == 0){ + if(waserror()) + ctlr->scansecs = 0; + else{ + wlscanstart(ctlr); + poperror(); + } + secs = ctlr->scansecs; + } + --secs; + }else + secs = 0; + } +} + +static void +wlinit(Ether *edev, Ctlr *ctlr) +{ + uchar ea[Eaddrlen]; + uchar eventmask[16]; + char version[128]; + char *p; + static uchar keepalive[12] = {1, 0, 11, 0, 0xd8, 0xd6, 0, 0, 0, 0, 0, 0}; + + wlgetvar(ctlr, "cur_etheraddr", ea, Eaddrlen); + memmove(edev->ea, ea, Eaddrlen); + memmove(edev->addr, ea, Eaddrlen); + print("ether4330: addr %E\n", edev->ea); + wlsetint(ctlr, "assoc_listen", 10); + if(ctlr->chipid == 43430 || ctlr->chipid == 0x4345) + wlcmdint(ctlr, 0x56, 0); /* powersave off */ + else + wlcmdint(ctlr, 0x56, 2); /* powersave FAST */ + wlsetint(ctlr, "bus:txglom", 0); + wlsetint(ctlr, "bcn_timeout", 10); + wlsetint(ctlr, "assoc_retry_max", 3); + if(ctlr->chipid == 0x4330){ + wlsetint(ctlr, "btc_wire", 4); + wlsetint(ctlr, "btc_mode", 1); + wlsetvar(ctlr, "mkeep_alive", keepalive, 11); + } + memset(eventmask, 0xFF, sizeof eventmask); +#define ENABLE(n) eventmask[n/8] |= 1<<(n%8) +#define DISABLE(n) eventmask[n/8] &= ~(1<<(n%8)) + DISABLE(40); /* E_RADIO */ + DISABLE(44); /* E_PROBREQ_MSG */ + DISABLE(54); /* E_IF */ + DISABLE(71); /* E_PROBRESP_MSG */ + DISABLE(20); /* E_TXFAIL */ + DISABLE(124); /* ? */ + wlsetvar(ctlr, "event_msgs", eventmask, sizeof eventmask); + wlcmdint(ctlr, 0xb9, 0x28); /* SET_SCAN_CHANNEL_TIME */ + wlcmdint(ctlr, 0xbb, 0x28); /* SET_SCAN_UNASSOC_TIME */ + wlcmdint(ctlr, 0x102, 0x82); /* SET_SCAN_PASSIVE_TIME */ + wlcmdint(ctlr, 2, 0); /* UP */ + memset(version, 0, sizeof version); + wlgetvar(ctlr, "ver", version, sizeof version - 1); + if((p = strchr(version, '\n')) != nil) + *p = '\0'; + if(0) print("ether4330: %s\n", version); + wlsetint(ctlr, "roam_off", 1); + wlcmdint(ctlr, 0x14, 1); /* SET_INFRA 1 */ + wlcmdint(ctlr, 10, 0); /* SET_PROMISC */ + //wlcmdint(ctlr, 0x8e, 0); /* SET_BAND 0 */ + //wlsetint(ctlr, "wsec", 1); + wlcmdint(ctlr, 2, 1); /* UP */ + ctlr->keys[0].len = WMinKeyLen; + //wlwepkey(ctlr, 0); +} + +/* + * Plan 9 driver interface + */ + +static long +etherbcmifstat(Ether* edev, void* a, long n, ulong offset) +{ + Ctlr *ctlr; + char *p; + int l; + static char *cryptoname[4] = { + [0] "off", + [Wep] "wep", + [Wpa] "wpa", + [Wpa2] "wpa2", + }; + /* these strings are known by aux/wpa */ + static char* connectstate[] = { + [Disconnected] = "unassociated", + [Connecting] = "connecting", + [Connected] = "associated", + }; + + ctlr = edev->ctlr; + if(ctlr == nil) + return 0; + p = malloc(READSTR); + l = 0; + + l += snprint(p+l, READSTR-l, "channel: %d\n", ctlr->chanid); + l += snprint(p+l, READSTR-l, "essid: %s\n", ctlr->essid); + l += snprint(p+l, READSTR-l, "crypt: %s\n", cryptoname[ctlr->cryptotype]); + l += snprint(p+l, READSTR-l, "oq: %d\n", qlen(edev->oq)); + l += snprint(p+l, READSTR-l, "txwin: %d\n", ctlr->txwindow); + l += snprint(p+l, READSTR-l, "txseq: %d\n", ctlr->txseq); + l += snprint(p+l, READSTR-l, "status: %s\n", connectstate[ctlr->status]); + USED(l); + n = readstr(offset, a, n, p); + free(p); + return n; +} + +static void +etherbcmtransmit(Ether *edev) +{ + Ctlr *ctlr; + + ctlr = edev->ctlr; + if(ctlr == nil) + return; + txstart(edev); +} + +static int +parsehex(char *buf, int buflen, char *a) +{ + int i, k, n; + + k = 0; + for(i = 0;k < buflen && *a; i++){ + if(*a >= '0' && *a <= '9') + n = *a++ - '0'; + else if(*a >= 'a' && *a <= 'f') + n = *a++ - 'a' + 10; + else if(*a >= 'A' && *a <= 'F') + n = *a++ - 'A' + 10; + else + break; + + if(i & 1){ + buf[k] |= n; + k++; + } + else + buf[k] = n<<4; + } + if(i & 1) + return -1; + return k; +} + +static int +wepparsekey(WKey* key, char* a) +{ + int i, k, len, n; + char buf[WMaxKeyLen]; + + len = strlen(a); + if(len == WMinKeyLen || len == WMaxKeyLen){ + memset(key->dat, 0, sizeof(key->dat)); + memmove(key->dat, a, len); + key->len = len; + + return 0; + } + else if(len == WMinKeyLen*2 || len == WMaxKeyLen*2){ + k = 0; + for(i = 0; i < len; i++){ + if(*a >= '0' && *a <= '9') + n = *a++ - '0'; + else if(*a >= 'a' && *a <= 'f') + n = *a++ - 'a' + 10; + else if(*a >= 'A' && *a <= 'F') + n = *a++ - 'A' + 10; + else + return -1; + + if(i & 1){ + buf[k] |= n; + k++; + } + else + buf[k] = n<<4; + } + + memset(key->dat, 0, sizeof(key->dat)); + memmove(key->dat, buf, k); + key->len = k; + + return 0; + } + + return -1; +} + +static int +wpaparsekey(WKey *key, uvlong *ivp, char *a) +{ + int len; + char *e; + + if(cistrncmp(a, "tkip:", 5) == 0 || cistrncmp(a, "ccmp:", 5) == 0) + a += 5; + else + return 1; + len = parsehex(key->dat, sizeof(key->dat), a); + if(len <= 0) + return 1; + key->len = len; + a += 2*len; + if(*a++ != '@') + return 1; + *ivp = strtoull(a, &e, 16); + if(e == a) + return -1; + return 0; +} + +static void +setauth(Ctlr *ctlr, Cmdbuf *cb, char *a) +{ + uchar wpaie[32]; + int i; + + i = parsehex((char*)wpaie, sizeof wpaie, a); + if(i < 2 || i != wpaie[1] + 2) + cmderror(cb, "bad wpa ie syntax"); + if(wpaie[0] == 0xdd) + ctlr->cryptotype = Wpa; + else if(wpaie[0] == 0x30) + ctlr->cryptotype = Wpa2; + else + cmderror(cb, "bad wpa ie"); + wlsetvar(ctlr, "wpaie", wpaie, i); + if(ctlr->cryptotype == Wpa){ + wlsetint(ctlr, "wpa_auth", 4|2); /* auth_psk | auth_unspecified */ + wlsetint(ctlr, "auth", 0); + wlsetint(ctlr, "wsec", 2); /* tkip */ + wlsetint(ctlr, "wpa_auth", 4); /* auth_psk */ + }else{ + wlsetint(ctlr, "wpa_auth", 0x80|0x40); /* auth_psk | auth_unspecified */ + wlsetint(ctlr, "auth", 0); + wlsetint(ctlr, "wsec", 4); /* aes */ + wlsetint(ctlr, "wpa_auth", 0x80); /* auth_psk */ + } +} + +static int +setcrypt(Ctlr *ctlr, Cmdbuf*, char *a) +{ + if(cistrcmp(a, "wep") == 0 || cistrcmp(a, "on") == 0) + ctlr->cryptotype = Wep; + else if(cistrcmp(a, "off") == 0 || cistrcmp(a, "none") == 0) + ctlr->cryptotype = 0; + else + return 0; + wlsetint(ctlr, "auth", ctlr->cryptotype); + return 1; +} + +static long +etherbcmctl(Ether* edev, void* buf, long n) +{ + Ctlr *ctlr; + Cmdbuf *cb; + Cmdtab *ct; + uchar ea[Eaddrlen]; + uvlong iv; + int i; + + if((ctlr = edev->ctlr) == nil) + error(Enonexist); + USED(ctlr); + + cb = parsecmd(buf, n); + if(waserror()){ + free(cb); + nexterror(); + } + ct = lookupcmd(cb, cmds, nelem(cmds)); + switch(ct->index){ + case CMauth: + setauth(ctlr, cb, cb->f[1]); + if(ctlr->essid[0]) + wljoin(ctlr, ctlr->essid, ctlr->chanid); + break; + case CMchannel: + if((i = atoi(cb->f[1])) < 0 || i > 16) + cmderror(cb, "bad channel number"); + //wlcmdint(ctlr, 30, i); /* SET_CHANNEL */ + ctlr->chanid = i; + break; + case CMcrypt: + if(setcrypt(ctlr, cb, cb->f[1])){ + if(ctlr->essid[0]) + wljoin(ctlr, ctlr->essid, ctlr->chanid); + }else + cmderror(cb, "bad crypt type"); + break; + case CMessid: + if(cistrcmp(cb->f[1], "default") == 0) + memset(ctlr->essid, 0, sizeof(ctlr->essid)); + else{ + strncpy(ctlr->essid, cb->f[1], sizeof(ctlr->essid) - 1); + ctlr->essid[sizeof(ctlr->essid) - 1] = '\0'; + } + if(!waserror()){ + wljoin(ctlr, ctlr->essid, ctlr->chanid); + poperror(); + } + break; + case CMjoin: /* join essid channel wep|on|off|wpakey */ + if(strcmp(cb->f[1], "") != 0){ /* empty string for no change */ + if(cistrcmp(cb->f[1], "default") != 0){ + strncpy(ctlr->essid, cb->f[1], sizeof(ctlr->essid)-1); + ctlr->essid[sizeof(ctlr->essid)-1] = 0; + }else + memset(ctlr->essid, 0, sizeof(ctlr->essid)); + }else if(ctlr->essid[0] == 0) + cmderror(cb, "essid not set"); + if((i = atoi(cb->f[2])) >= 0 && i <= 16) + ctlr->chanid = i; + else + cmderror(cb, "bad channel number"); + if(!setcrypt(ctlr, cb, cb->f[3])) + setauth(ctlr, cb, cb->f[3]); + if(ctlr->essid[0]) + wljoin(ctlr, ctlr->essid, ctlr->chanid); + break; + case CMkey1: + case CMkey2: + case CMkey3: + case CMkey4: + i = ct->index - CMkey1; + if(wepparsekey(&ctlr->keys[i], cb->f[1])) + cmderror(cb, "bad WEP key syntax"); + wlsetint(ctlr, "wsec", 1); /* wep enabled */ + wlwepkey(ctlr, i); + break; + case CMrxkey: + case CMrxkey0: + case CMrxkey1: + case CMrxkey2: + case CMrxkey3: + case CMtxkey: + if(parseether(ea, cb->f[1]) < 0) + cmderror(cb, "bad ether addr"); + if(wpaparsekey(&ctlr->keys[0], &iv, cb->f[2])) + cmderror(cb, "bad wpa key"); + wlwpakey(ctlr, ct->index, iv, ea); + break; + case CMdebug: + iodebug = atoi(cb->f[1]); + break; + } + poperror(); + free(cb); + return n; +} + +static void +etherbcmscan(void *a, uint secs) +{ + Ether* edev; + Ctlr* ctlr; + + edev = a; + ctlr = edev->ctlr; + ctlr->scansecs = secs; +} + +static void +etherbcmattach(Ether* edev) +{ + Ctlr *ctlr; + + ctlr = edev->ctlr; + qlock(&ctlr->alock); + if(waserror()){ + //print("ether4330: attach failed: %s\n", up->errstr); + qunlock(&ctlr->alock); + nexterror(); + } + if(ctlr->edev == nil){ + if(ctlr->chipid == 0){ + sdioinit(); + sbinit(ctlr); + } + fwload(ctlr); + sbenable(ctlr); + kproc("wifireader", rproc, edev); + kproc("wifitimer", lproc, edev); + if(ctlr->regufile) + reguload(ctlr, ctlr->regufile); + wlinit(edev, ctlr); + ctlr->edev = edev; + } + qunlock(&ctlr->alock); + poperror(); +} + +static void +etherbcmshutdown(Ether*) +{ + sdioreset(); +} + + +static int +etherbcmpnp(Ether* edev) +{ + Ctlr *ctlr; + + ctlr = malloc(sizeof(Ctlr)); + ctlr->chanid = Wifichan; + edev->ctlr = ctlr; + edev->attach = etherbcmattach; + edev->transmit = etherbcmtransmit; + edev->ifstat = etherbcmifstat; + edev->ctl = etherbcmctl; + edev->scanbs = etherbcmscan; + edev->shutdown = etherbcmshutdown; + edev->arg = edev; + + return 0; +} + +void +ether4330link(void) +{ + addethercard("4330", etherbcmpnp); +} --- /sys/src/9/bcm64/ethergem.c Thu Jan 1 00:00:00 1970 +++ /sys/src/9/bcm64/ethergem.c Mon Apr 20 19:50:37 2026 @@ -0,0 +1,541 @@ +/* + * Cadence GEM gigabit ethernet for Raspberry Pi 5 RP1 + */ + +#include "u.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#include "io.h" +#include "../port/error.h" +#include "../port/netif.h" +#include "etherif.h" +#include "../bcm/ethermii.h" + +#define ETHERADDR (VIRTIO+0x80100000ull) + +enum { + GpioGEM = 32, /* reset pin */ + + Ringsize = 256, /* power of 2 */ + Rbsz = 1600, /* multiple of 64 */ + + /* GEM registers */ + Ctl = 0x00/4, + Cfg = 0x04/4, + Sts = 0x08/4, + Dmacfg = 0x10/4, + Txsts = 0x14/4, + Rxdescq = 0x18/4, + Txdescq = 0x1c/4, + Rxsts = 0x20/4, + Intsts = 0x24/4, + Intena = 0x28/4, + Intdis = 0x2c/4, + Phymgmt = 0x34/4, + Revision= 0xfc/4, + Hashbot = 0x80/4, + Hashtop = 0x84/4, + Specaddr1bot = 0x88/4, + Specaddr1top = 0x8c/4, + Txdescqhi = 0x4c8/4, + Rxdescqhi = 0x4d4/4, + + /* Ctl bits */ + Txhalt = 1<<10, + Txstart = 1<<9, + Mden = 1<<4, + Txallow = 1<<3, + Rxallow = 1<<2, + + /* Sts bits */ + Phyidle = 1<<2, + + /* Intsts / Intena / Intdis bits */ + Txdoneint = 1<<7, + Rxrcvdint = 1<<1, + + /* Cfg bits */ + Sgmii = 1<<27, /* enable gb mii, thus gb speed */ + Rxigncrc = 1<<26, + Rxckoffl = 1<<24, /* rx checksum offload: drop bad pkts */ + Mdcclkdivshft = 18, + Mdcclkdiv = 7<<18, /* default 2; 4 for Gb/s */ + Rxomitfcs = 1<<17, /* rx omit crc in memory */ + Pcsmand = 1<<11, /* not TBI */ + Gbmode = 1<<10, /* gigabit mode enable */ + Rxunihashfilt = 1<<7, + Rxmultihashfilt = 1<<6, + Rxnobcast = 1<<5, /* discard broadcasts */ + Caf = 1<<4, /* copy all frames (promiscuous mode) */ + Fd = 1<<1, /* full duplex */ + + /* Dmacfg bits */ + Dmaaddr64 = 1 << 30, /* use 64-bit dma addresses */ + Txextbd = 1 << 29, /* extended tx buffer descriptors */ + Rxextbd = 1 << 28, /* extended rx buffer descriptors */ + Rxbsshft = 16, + Rxbufsize = MASK(8)< reset phy + */ +static void +resetphy(void) +{ + gpioselrp1(GpioGEM, 5); + gpiopulloffrp1(GpioGEM); + gpiooutrp1(GpioGEM, 0); + delay(10); + gpiooutrp1(GpioGEM, 1); + delay(10); +} + +static int +mdiow(Mii *mii, int phy, int addr, int data) +{ + Ctlr *ctlr; + uint *regs; + + ctlr = mii->ctlr; + regs = ctlr->regs; + regs[Ctl] |= Mden; + + regs[Phymgmt] = 1<<30 | 1<<28 | phy<<23 | addr<<18 | 2<<16 | data; + while((regs[Sts]&Phyidle) == 0) + ; + regs[Ctl] &= ~Mden; + return 0; +} + +static int +mdior(Mii *mii, int phy, int addr) +{ + Ctlr *ctlr; + uint *regs; + int data; + + ctlr = mii->ctlr; + regs = ctlr->regs; + regs[Ctl] |= Mden; + + regs[Phymgmt] = 1<<30 | 2<<28 | phy<<23 | addr<<18 | 2<<16; + while((regs[Sts]&Phyidle) == 0) + ; + data = regs[Phymgmt]; + regs[Ctl] &= ~Mden; + return data & 0xFFFF; +} + +Desc* +ringhead(Ring *r) +{ + if((r->head - r->tail) == Ringsize) + return nil; + return &r->ring[r->head & (Ringsize-1)]; +} + +Desc* +ringtail(Ring *r) +{ + if(r->head == r->tail) + return nil; + return &r->ring[r->tail & (Ringsize-1)]; +} + +static int +inputrxbufs(Ctlr *ctlr) +{ + Ring *r; + Desc *rd; + Block *bp; + int n, len; + + r = &ctlr->rxq; + n = 0; + while((rd = ringtail(r)) != nil && (rd->addr & Full)){ + n++; + bp = (Block*)KADDR(rd->unused); + rd->addr &= (Rdlast|Full); + coherence(); + r->tail++; + if((rd->size_flags & (Rdeofr|Rdsofr|Rdbadfcs)) != (Rdeofr|Rdsofr)){ + iprint("ethergem: bad rx packet flags %ux\n", rd->size_flags); + freeb(bp); + continue; + } + len = rd->size_flags & Rdpktsize; + if (len == ETHERMINTU) + len += 4; /* for 60-byte arps? */ + if (len > ETHERMAXTU) + len = ETHERMAXTU; + bp->wp = bp->rp + len; + cachedinvse(bp->rp, len); + etheriq(ctlr->edev, bp, 1); + } + return n; +} + +static int +freetxbufs(Ctlr *ctlr) +{ + Ring *r; + Desc *td; + Block *bp; + int n; + + r = &ctlr->txq; + n = 0; + while((td = ringtail(r)) != nil && (td->size_flags & Sent)){ + bp = (Block*)KADDR(td->unused); + freeb(bp); + td->addr = 0; + coherence(); + r->tail++; + n++; + } + return n; +} + +static int +allocrxbufs(Ctlr *ctlr) +{ + Ring *r; + Desc *rd; + Block *bp; + int n; + + r = &ctlr->rxq; + n = 0; + while((rd = ringhead(r)) != nil){ + if((rd->addr & ~Rdlast) != Full) + print("gem: receive ring mangled\n"); + bp = allocb(Rbsz); + if(bp == nil) + break; + cachedwbinvse(bp->rp, Rbsz); + rd->addrhi = 0; + rd->unused = PADDR(bp); + coherence(); + rd->addr = (rd->addr & Rdlast) | PADDR(bp->rp); + coherence(); + r->head++; + n++; + } + return n; + +} + +static int +bufwork(void *a) +{ + Ctlr *ctlr; + Desc *td, *rd; + + ctlr = a; + if((td = ringtail(&ctlr->txq)) != nil && (td->size_flags & Sent)) + return 1; + if((rd = ringhead(&ctlr->rxq)) != nil && (rd->addr & ~Rdlast) != Full) + return 1; + return 0; +} + +void +gemdebug(void) +{ + Ctlr *ctlr; + Ring *r; + Desc *rd; + u32int *regs; + int i, h, t; + + ctlr = gemctlr; + regs = ctlr->regs; + print("ctl %ux intsts %ux txsts %ux rxsts %ux\n", regs[Ctl], regs[Intsts], regs[Txsts], regs[Rxsts]); + r = &ctlr->rxq; + h = r->head & (Ringsize-1); + t = r->tail & (Ringsize-1); + print("rxq %ud %ud:\n", r->head, r->tail); + for(i = 0; i < Ringsize; i++){ + rd = &r->ring[i]; + print("%2.2d %ux %ux %ux %c%c\n", i, rd->addr, rd->unused, rd->size_flags, i == h? 'H' : ' ', i == t? 'T' : ' '); + } + r = &ctlr->txq; + h = r->head & (Ringsize-1); + t = r->tail & (Ringsize-1); + print("txq %ud %ud:\n", r->head, r->tail); + for(i = 0; i < Ringsize; i++){ + rd = &r->ring[i]; + print("%2.2d %ux %ux %ux %c%c\n", i, rd->addr, rd->unused, rd->size_flags, i == h? 'H' : ' ', i == t? 'T' : ' '); + } +} + +static long +gemifstat(Ether *edev, void *a, long n, ulong offset) +{ + Ctlr *ctlr; + char *p; + + ctlr = edev->ctlr; + USED(ctlr); + p = "these stats intentionally left blank"; + n = readstr(offset, a, n, p); + return n; +} + +static void +gemshutdown(Ether *edev) +{ + u32int *regs; + + regs = ((Ctlr*)edev->ctlr)->regs; + regs[Ctl] |= Txhalt; + regs[Ctl] &= ~(Txallow | Rxallow); + regs[Intdis] = ~0; + regs[Intsts] = ~0; +} + +static void +gemattach(Ether *edev) +{ + Ctlr *ctlr; + u32int *regs; + uchar *ea; + int i; + + ctlr = edev->ctlr; + regs = ctlr->regs; + qlock(&ctlr->alock); + if(ctlr->edev != nil){ + qunlock(&ctlr->alock); + return; + } + /* configure ether address (must write bottom first) */ + ea = edev->ea; + regs[Specaddr1bot] = ea[3]<<24 | ea[2]<<16 | ea[1]<<8 | ea[0]; + regs[Specaddr1top] = ea[5]<<8 | ea[4]; + /* set up buffer descriptor rings in uncached memory */ + ctlr->txq.ring = (Desc*)DESCRIPTORS; + ctlr->rxq.ring = (Desc*)(DESCRIPTORS + Ringsize*sizeof(Desc)); + memset(ctlr->txq.ring, 0, 2*Ringsize*sizeof(Desc)); + for(i = 0; i < Ringsize; i++){ + ctlr->txq.ring[i].size_flags = Sent; + ctlr->rxq.ring[i].size_flags = Rbsz; + ctlr->rxq.ring[i].addr = Full; + } + ctlr->txq.ring[Ringsize-1].size_flags |= Tdlast; + ctlr->rxq.ring[Ringsize-1].addr |= Rdlast; + /* configure addresses of descriptor rings */ + regs[Txdescq] = (uintptr)ctlr->txq.ring - UCKZERO; + regs[Txdescqhi] = 0; + regs[Rxdescq] = (uintptr)ctlr->rxq.ring - UCKZERO; + regs[Rxdescqhi] = 0; + /* enable interrupts and transmit/receive */ + regs[Txsts] = ~0; + regs[Rxsts] = ~0; + regs[Intsts] = ~0; + regs[Intena] = Txdoneint | Rxrcvdint; + regs[Ctl] |= Txallow | Rxallow; + regs[Ctl] |= Txstart; + kproc("gemrproc", gemrproc, edev); + ctlr->edev = edev; + qunlock(&ctlr->alock); +} + +static void +gemtransmit(Ether *edev) +{ + Ctlr *ctlr; + Block *bp; + Desc *td; + Ring *r; + uint *regs; + int n; + + ctlr = edev->ctlr; + regs = ctlr->regs; + r = &ctlr->txq; + n = 0; + ilock(&ctlr->txlock); + while((td = ringhead(r)) != nil){ + if((bp = qget(edev->oq)) == nil) + break; + cachedwbse(bp->rp, BLEN(bp)); + td->addr = PADDR(bp->rp); + td->addrhi = 0; + td->unused = PADDR(bp); + coherence(); + td->size_flags = (td->size_flags & Tdlast) | Tdeofr | BLEN(bp); + coherence(); + r->head++; + n++; + } + //if(td == nil && qcanread(edev->oq)) + // iprint("gemtransmit(after %d): queue full\n", n); + if(n) + regs[Ctl] |= Txstart; + iunlock(&ctlr->txlock); +} + +static int +gemreset(Ctlr *ctlr) +{ + u32int *regs; + uint cfg; + + ctlr->regs = regs = (u32int*)ETHERADDR; + if(((regs[Revision]>>16) & 0xFF) != 0x07){ + iprint("gem: interface not found at %#p, Revision reg = %#x\n", regs, regs[Revision]); + return -1; + } + //iprint("gem: Revision %#x\n", regs[Revision]); + ctlr->mii.ctlr = ctlr; + ctlr->mii.mir = mdior; + ctlr->mii.miw = mdiow; + resetphy(); + if(mii(&ctlr->mii, ~0) == 0){ + iprint("gem: no phy found"); + return -1; + } + //iprint("gem: phy oui %#x\n", ctlr->mii.curphy->oui); + cfg = regs[Cfg]; + cfg &= ~(Rxnobcast|Rxckoffl|Mdcclkdiv); + cfg |= Pcsmand | Rxomitfcs | Fd | Gbmode | Sgmii | + Rxunihashfilt | Rxmultihashfilt | 4<ctlr; + regs = ctlr->regs; + sts = regs[Intsts]; + for(i = 0; ; i++){ + regs[Intsts] = sts; + inputrxbufs(ctlr); + sts = regs[Intsts]; + if(sts == 0) + break; + if(i == 100){ + iprint("gem: interrupt stuck sts=%ux\n", sts); + break; + } + } + if(bufwork(ctlr)) + wakeup(&ctlr->rrendez); +} + +static void +gemrproc(void *a) +{ + Ether *edev; + Ctlr *ctlr; + + edev = a; + ctlr = edev->ctlr; + for(;;){ + allocrxbufs(ctlr); + if(freetxbufs(ctlr)) + gemtransmit(edev); + tsleep(&ctlr->rrendez, bufwork, ctlr, 250); + } +} + +static int +gempnp(Ether* edev) +{ + Ctlr *ctlr; + + ctlr = malloc(sizeof(Ctlr)); + if(ctlr == nil || gemreset(ctlr)){ + free(ctlr); + return -1; + } + edev->ctlr = ctlr; + gemshutdown(edev); + edev->port = (uintptr)ctlr->regs; + edev->irq = IRQgem; + edev->mbps = 1000; + edev->maxmtu = ETHERMAXTU; + edev->attach = gemattach; + edev->shutdown = gemshutdown; + edev->transmit = gemtransmit; + edev->interrupt = geminterrupt; + edev->ifstat = gemifstat; + + edev->arg = edev; + gemctlr = ctlr; + return 0; +} + +void +ethergemlink(void) +{ + addethercard("gem", gempnp); +} --- /sys/src/9/bcm64/etherif.h Thu Jan 1 00:00:00 1970 +++ /sys/src/9/bcm64/etherif.h Wed Oct 31 10:33:22 2012 @@ -0,0 +1 @@ +#include "../omap/etherif.h" --- /sys/src/9/bcm64/fns.h Thu Jan 1 00:00:00 1970 +++ /sys/src/9/bcm64/fns.h Tue Mar 24 19:40:29 2026 @@ -0,0 +1,183 @@ +#include "../port/portfns.h" + +Dirtab* addarchfile(char*, int, long(*)(Chan*, void*, long, vlong), + long(*)(Chan*, void*, long, vlong)); +extern void archreboot(void); +extern void archreset(void); +extern void armtimerset(int); +extern void cachedwb(void); +extern void cachedwbinv(void); +extern void cachedinvse(void*, int); +extern void cachedwbse(void*, int); +extern void cachedwbinvse(void*, int); +extern void cachedwbtlb(void*, int); +extern void cacheiinv(void); +extern void cacheiinvse(void*, int); +extern void cacheuwbinv(void); +extern uintptr cankaddr(uintptr pa); +extern int cas32(void*, u32int, u32int); +extern int cas(ulong*, ulong, ulong); +extern void clockinit(void); +extern void clockshutdown(void); +extern int cmpswap(long*, long, long); +extern void coherence(void); +extern u32int cpidget(void); +extern u32int cprdcpaccess(void); +extern u32int cprdfeat1(void); +extern u32int cprdtimerfreq(void); +extern u64int cprdtimerval(void); +extern void cpuidprint(void); +extern char *cputype2name(char *buf, int size); +extern void cpwrcpaccess(u32int); +extern void cpwrtimerphysctl(u32int); +extern void cpwrtimerphysval(u32int); +#define cycles(ip) *(ip) = lcycles() +#define dmaaddr(va) PADDR(va) +extern void dmastart(int, int, int, void*, void*, int); +extern int dmawait(int); +extern int fbblank(int); +extern void* fbinit(int, int*, int*, int*); +extern void fbsetup(char*, uchar*, int); +extern uintptr farget(void); +extern void fpoff(void); +extern void fpon(void); +extern ulong fprdexc(void); +extern ulong fprdscr(void); +extern ulong fprdsid(void); +extern void fpwrexc(ulong); +extern void fpwrscr(ulong); +extern void fprestreg(int fpreg, uvlong val); +extern void fprestregs(uvlong*, int); +extern void fpsave(FPsave *); +extern ulong fpsavereg(int fpreg, uvlong *fpp); +extern void fpsaveregs(uvlong*, int); +extern u32int fsrget(void); +extern uint getboardrev(void); +extern ulong getclkrate(int); +extern char* getconf(char*); +extern uint getcputemp(void); +extern char *getethermac(void); +extern uint getfirmware(void); +extern int getncpus(void); +extern int getpower(int); +extern void getramsize(Confmem*); +extern void gpiosel(uint, int); +extern void gpiopullup(uint); +extern void gpiopulloff(uint); +extern void gpiopulldown(uint); +extern void gpioout(uint, int); +extern int gpioin(uint); +extern void gpioselrp1(uint, int); +extern void gpiopulluprp1(uint); +extern void gpiopulloffrp1(uint); +extern void gpiopulldownrp1(uint); +extern void gpiooutrp1(uint, int); +extern int gpioinrp1(uint); +extern void i2csetup(int); +extern long i2crecv(I2Cdev*, void*, long, ulong); +extern long i2csend(I2Cdev*, void*, long, ulong); +extern u32int ifsrget(void); +extern void irqenable(int, void (*)(Ureg*, void*), void*); +#define intrenable(i, f, a, b, n) irqenable((i), (f), (a)) +extern void intrcpushutdown(void); +extern void intrshutdown(void); +extern void intrsoff(void); +extern int isaconfig(char*, int, ISAConf*); +extern int l2ap(int); +extern void l2cacheuwbinv(void); +extern void links(void); +extern void lpapageinit(void); +extern void mmuinit(ulong); +extern void mmuinit1(void); +extern void mmuinvalidate(void); +extern void mmuinvalidateaddr(uintptr); +extern void okay(int); +extern void pl011consinit(void); +extern void procrestore(Proc *); +extern void procsave(Proc*); +extern void procsetup(Proc*); +extern void screeninit(void); +#define sdfree(p) free(p) +#define sdmalloc(n) mallocalign(n, BLOCKALIGN, 0, 0) +extern void setclkrate(int, ulong); +extern void setpower(int, int); +extern void setr13(int, u32int*); +extern void sev(void); +extern void spiclock(uint); +extern void spimode(int); +extern void spirw(uint, void*, int); +extern int splfhi(void); +extern int splflo(void); +extern void swcursorinit(void); +extern void syscall(Ureg*); +extern void syscallfmt(int syscallno, ulong pc, va_list list); +extern void sysretfmt(int syscallno, va_list list, long ret, uvlong start, uvlong stop); +extern int startcpus(uint); +extern void stopcpu(uint); +extern int tas(void *); +extern void touser(uintptr); +extern void trapinit(void); +extern void uartconsinit(void); +extern int userureg(Ureg*); +extern void vectors(void); +extern void vgpinit(void); +extern void vgpset(uint, int); +extern void vtable(void); +extern void wdogoff(void); +extern void wdogfeed(void); +extern int xhcireset(int devaddr); + +/* + * floating point emulation + */ +extern int fpiarm(Ureg*); +extern int fpudevprocio(Proc*, void*, long, uintptr, int); +extern void fpuinit(void); +extern void fpunoted(void); +extern void fpunotify(Ureg*); +extern void fpuprocrestore(Proc*); +extern void fpuprocsave(Proc*); +extern void fpusysprocsetup(Proc*); +extern void fpusysrfork(Ureg*); +extern void fpusysrforkchild(Proc*, Ureg*, Proc*); +extern int fpuemu(Ureg*); +/* + * Miscellaneous machine dependent stuff. + */ +extern char* getenv(char*, char*, int); +uintptr mmukmap(uintptr, uintptr, usize); +uintptr mmukmapx(uintptr, uvlong, usize); +uintptr mmukunmap(uintptr, uintptr, usize); +extern void* mmuuncache(void*, usize); +extern void* ucalloc(usize); +extern Block* ucallocb(int); +extern void* ucallocalign(usize size, int align, int span); +extern void ucfree(void*); +extern void ucfreeb(Block*); +/* + * Things called from port. + */ +extern void delay(int); /* only scheddump() */ +extern int islo(void); +extern void microdelay(int); /* only edf.c */ +extern void idlehands(void); +extern void setkernur(Ureg*, Proc*); /* only devproc.c */ +extern void* sysexecregs(uintptr, ulong, int); +extern void sysprocsetup(Proc*); +extern void validalign(uintptr, unsigned); + +extern void kexit(Ureg*); + +#define getpgcolor(a) 0 +#define kmapinval() +#define countpagerefs(a, b) + +#define PTR2UINT(p) ((uintptr)(p)) +#define UINT2PTR(i) ((void*)(i)) + +#define waserror() (up->nerrlab++, setlabel(&up->errlab[up->nerrlab-1])) + +#define KADDR(pa) UINT2PTR(KZERO | ((uintptr)(pa) & ~KSEGM)) +#define PADDR(va) PTR2UINT(PHYSDRAM | ((uintptr)(va) & ~KSEGM)) + +#define MASK(v) ((1UL << (v)) - 1) /* mask `v' bits wide */ --- /sys/src/9/bcm64/gpio.c Thu Jan 1 00:00:00 1970 +++ /sys/src/9/bcm64/gpio.c Fri Feb 20 14:28:19 2026 @@ -0,0 +1,117 @@ +/* + * Raspberry Pi (BCM2712) SoC GPIO support + */ + +#include "u.h" +#include "../port/lib.h" +#include "../port/error.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#include "io.h" + +#define GPIOREGS (VIRTIO+0x7d508500) +#define PINMUXREGS (VIRTIO+0x7d504100) + +/* PINMUX regs */ +enum { + Fsel0 = 0x00>>2, + FuncMask= 0xF, + Off = 0x0, + Pulldown= 0x1, + Pullup = 0x2, +}; +/* GPIO regs */ +enum { + Data = 0x04>>2, + Iodir = 0x08>>2, +}; + +void +gpiosel(uint pin, int alt) +{ + u32int *gp, *fsel; + int func, off; + static uchar alt2func[8] = { + 0, 0, 5, 4, 0, 1, 2, 3 + }; + + if(alt == Input || alt == Output){ + gp = (u32int*)GPIOREGS + 8*(pin/32); + off = pin%32; + if(alt == Input) + gp[Iodir] |= 1<= 34){ + b = 2; + *pin -= 34; + }else if(*pin >= 28){ + b = 1; + *pin -= 28; + }else + b = 0; + return b; +} +void +gpioselrp1(uint pin, int func) +{ + Gpioregs *gp; + uint b; + + gp = (Gpioregs*)GPIOREGS; + b = pinbank(&pin); + u32int *p; + p = &gp->ctlbank[b].ctlregs.reg[pin].ctl; + if(0)print("\ngpiosel %#p %ux -> %ux\n", p, *p, (*p & ~0x1f) | func); + *p = (*p & ~0x1f) | func; +} + +static void +gpiopullrp1(uint pin, int func) +{ + Gpioregs *gp; + uint b; + u32int *p; + + gp = (Gpioregs*)GPIOREGS; + b = pinbank(&pin); + p = &gp->padbank[b].padregs.pad[pin]; + if(0)print("gpiopull %#p %ux -> %ux\n", p, *p, (*p & ~(Pullup|Pulldown)) | func); + *p = (*p & ~(Pullup|Pulldown) | func); +} + +void +gpiopulloffrp1(uint pin) +{ + gpiopullrp1(pin, 0); +} + +void +gpiopulluprp1(uint pin) +{ + gpiopullrp1(pin, Pullup); +} + +void +gpiopulldownrp1(uint pin) +{ + gpiopullrp1(pin, Pulldown); +} + +void +gpiooutrp1(uint pin, int set) +{ + Gpioregs *gp; + uint b; + + gp = (Gpioregs*)GPIOREGS; + b = pinbank(&pin); + gp->padbank[b].padregs.pad[pin] &= ~0x80; /* clear output_disable */ + (&gp->riobank[b].rioregs.out_enable)[Set] = 1<riobank[b].rioregs.out)[Set] = 1<riobank[b].rioregs.out)[Clr] = 1<riobank[b].rioregs.out, gp->riobank[b].rioregs.out); + +} + +int +gpioinrp1(uint pin) +{ + Gpioregs *gp; + uint b; + + gp = (Gpioregs*)GPIOREGS; + b = pinbank(&pin); + return (gp->riobank[b].rioregs.in & (1<>21)<<3), R1 + ISB $SY + MSR R1, SPR(TTBR0_EL1) + MSR R1, SPR(TTBR1_EL1) + ISB $SY + + /* + * invalidate my caches before enabling + */ + BL cachedinv(SB) + BL cacheiinv(SB) + BL l2cacheuinv(SB) + //MOV $0x42, R0 + //BL uartputc(SB) + + /* + * enable caches and mmu + */ + MRS SPR(SCTLR_EL1), R1 + ORR $(1<<12 | 1<<2 | 1<<0), R1 /* I, C, M */ + ISB $SY + MSR R1, SPR(SCTLR_EL1) + ISB $SY + //MOV $0x32, R0 + //BL uartputc(SB) + + /* + * switch SB, SP and PC into KZERO space + */ + MOV $setSB(SB), R28 + MOV $(MACHADDR+MACHSIZE-8), R1 + MOV R1, SP + BL pcrelocate(SB) + //MOV $0x33, R0 + //BL uartputc(SB) + + /* + * enable cycle counter & access from EL0 + */ + MOV $1, R0 + MSR R0, SPR(PMCR_EL0) + //MSR R0, SPR(PMUSERENR_EL0) + MOV $(1<<31), R0 + MSR R0, SPR(PMCNTENSET) + MOV $0x33, R0 + MSR R0, SPR(CNTKCTL_EL1) + + MOV R25, R0 /* fdt pointer */ + BL main(SB) + B 0(PC) + +TEXT el2to1(SB), 0, $-4 + MSR LR, ELR_EL2 + MSR R1, SPSR_EL2 + ERET + +TEXT el1to1(SB), 0, $-4 + MSR LR, ELR_EL1 + MSR R1, SPSR_EL1 + ERET + +TEXT pcrelocate(SB), 0, $-4 + ORR $KZERO, LR + RETURN + +/* + * Use PSCI interface to start core in R0 + */ +TEXT startcpu(SB), 1, $-4 + LSL $8, R0, R1 /* core number */ + MOV $0xC4000003UL, R0 /* function CPU_ON */ + MOV $cpureset-KZERO(SB), R2 /* entry address */ + MOV $0, R3 /* context (unused) */ + SMC $0 + RETURN + +/* + * startup entry for cpu(s) other than 0 + */ +TEXT cpureset(SB), 1, $-4 + MOV $setSB-KZERO(SB), R28 + + /* + * go to EL1 mode, interrupts disabled + */ + MOV $1, R1 /* use EL1 SP in EL1 */ + MSR R1, SPSel + MOVW $(0xF<<6 | 5), R1 /* interrupts off, EL1h, aarch64 */ + MRS CurrentEL, R2 + CMPW $(2<<2), R2 + BNE 2(PC) /* not in EL2, skip */ + BL el2to1(SB) /* switch from EL2 to EL1 */ + MRS CurrentEL, R2 + CMPW $(1<<2), R2 + BNE 0(PC) /* not in EL1, hang */ + BL el1to1(SB) + + /* + * disable mmu and caches + */ + MRS SPR(SCTLR_EL1), R0 + BIC $(1<<12 | 1<<2 | 1<<0), R0 /* I, C, M */ + ORR $(1<<5), R0 /* enable CP15 DMB instruction */ + MSR R0, SPR(SCTLR_EL1) + ISB $SY + + /* + * invalidate tlb + */ + DSB $NSHST + TLBI R0, 0,8,7,0 /* VMALLE1 */ + DSB $NSH + ISB $SY + + /* + * find Mach for this cpu + */ + MRS SPR(MPIDR_EL1), R1 + LSR $8, R1 + AND $0x3, R1 + MOV $machaddr-KZERO(SB), R0 + ADD R1<<3, R0, R2 + MOV (R2), R0 /* machaddr[cpuid] */ + CBZ R0, 0(PC) /* must not be zero */ + SUB $KZERO, R0, R(MACH) /* m = PADDR(machaddr[cpuid]) */ + + /* + * start stack at top of local Mach + */ + ADD $(MACHSIZE-8), R(MACH), R1 + MOV R1, SP + + /* + * configure mmu control registers + */ + MOV tcr_el1(SB), R1 + MSR R1, SPR(TCR_EL1) + ISB $SY + MOV mair_el1(SB), R1 + MSR R1, SPR(MAIR_EL1) + ISB $SY + MOV 24(R(MACH)), R1 /* m->mmul1 */ + SUB $KZERO, R1 + ADD $((L2VA>>21)<<3), R1 + ISB $SY + MSR R1, SPR(TTBR0_EL1) + MSR R1, SPR(TTBR1_EL1) + ISB $SY + + /* + * invalidate my caches before enabling + */ + BL cachedinv(SB) + BL cacheiinv(SB) + BL l2cacheuinv(SB) + //MOV $0x34, R0 + //BL uartputc(SB) + + /* + * enable caches and mmu + */ + MRS SPR(SCTLR_EL1), R1 + ORR $(1<<12 | 1<<2 | 1<<0), R1 /* I, C, M */ + ISB $SY + MSR R1, SPR(SCTLR_EL1) + ISB $SY + //MOV $0x35, R0 + //BL uartputc(SB) + + /* + * switch SB, SP, R(MACH) and PC into KZERO space + */ + MOV $setSB(SB), R28 + MOV SP, R1 + ORR $KZERO, R1 + MOV R1, SP + ORR $KZERO, R(MACH) + BL pcrelocate(SB) + //MOV $0x36, R0 + //BL uartputc(SB) + + /* + * enable cycle counter & access from EL0 + */ + MOV $1, R0 + MSR R0, SPR(PMCR_EL0) + //MSR R0, SPR(PMUSERENR_EL0) + MOV $(1<<31), R0 + MSR R0, SPR(PMCNTENSET) + MOV $0x33, R0 + MSR R0, SPR(CNTKCTL_EL1) + + /* + * call cpustart and loop forever if it returns + */ + //MOV $0x37, R0 + //BL uartputc(SB) + MRS SPR(0x1800a0), R0 /* mpidr_el1 */ + LSR $8, R0 + AND $0x3, R0 + BL cpustart(SB) + B 0(PC) + +TEXT setvbar(SB), 0, $-4 + MSR R0, SPR(VBAR_EL1) + RETURN + +TEXT cpidget(SB), 1, $-4 + MRS SPR(MIDR_EL1), R0 + RETURN + +TEXT farget(SB), 1, $-4 + MRS SPR(FAR_EL1), R0 + RETURN + +TEXT cprdtimerfreq(SB), 1, $-4 + MRS SPR(CNTFRQ_EL0), R0 + RETURN + +TEXT cprdtimerval(SB), 1, $-4 + MRS SPR(CNTPCT_EL0), R0 + RETURN + +TEXT cpwrtimerphysctl(SB), 1, $-4 + MSR R0, SPR(CNTV_CTL_EL0) + RETURN + +TEXT cpwrtimerphysval(SB), 1, $-4 + MSR R0, SPR(CNTV_TVAL_EL0) + RETURN + +TEXT lcycles(SB), 1, $-4 + MRS SPR(PMCCNTR_EL0), R0 + RETURN + +TEXT splhi(SB), 1, $-4 + MRS DAIF, R0 + MSR $Dirq, DAIFSet + RETURN + +TEXT splfhi(SB), 1, $-4 + MRS DAIF, R0 + MSR $(Dirq|Dfiq), DAIFSet + RETURN + +TEXT spllo(SB), 1, $-4 + MRS DAIF, R0 + MSR $(Dirq|Dfiq), DAIFClr + RETURN + +TEXT splflo(SB), 1, $-4 + MRS DAIF, R0 + MSR $Dfiq, DAIFClr + RETURN + +TEXT splx(SB), 1, $-4 + MSR R0, DAIF + RETURN + +TEXT spldone(SB), 1, $0 /* end marker for devkprof.c */ + RETURN + +TEXT islo(SB), 1, $-4 + MRS DAIF, R0 + AND $(Dirq<<6), R0 + EOR $(Dirq<<6), R0 + RETURN + +TEXT tas(SB), 1, $-4 +TEXT _tas(SB), 1, $-4 +#ifdef ATOMICS + MOV $0, R1 + MOV $1, R2 + CASALW(1,2,0) + MOV R1, R0 + RETURN +#else + MOV $1, R2 + DMB $ISH +_tas1: + LDXRW (R0), R1 + CBNZ R1, lockbusy + STXRW R2, (R0), R3 + CBNZ R3, _tas1 + DMB $ISH + MOV R1, R0 + RETURN +lockbusy: + CLREX + MOV R1, R0 + DMB $ISH + RETURN +#endif + +TEXT _xinc(SB), 1, $-4 /* void _xinc(long *); */ +TEXT ainc(SB), 1, $-4 /* long ainc(long *); */ +#ifdef ATOMICS + MOV $1, R1 + LDADDALW(1,2,0) + ADD R1, R2, R0 + RETURN +#else + DMB $ISH +spinainc: + LDXRW (R0), R3 + ADD $1, R3 + STXRW R3, (R0), R4 + CBNZ R4, spinainc + DMB $ISH + MOV R3, R0 + RETURN +#endif + +TEXT _xdec(SB), 1, $-4 /* long _xdec(long *); */ +TEXT adec(SB), 1, $-4 /* long adec(long *); */ +#ifdef ATOMICS + MOV $-1, R1 + LDADDALW(1,2,0) + ADD R1, R2, R0 + RETURN +#else + DMB $ISH +spinadec: + LDXRW (R0), R3 + SUB $1,R3 + STXRW R3, (R0), R4 + CBNZ R4, spinadec + DMB $ISH + MOV R3, R0 + RETURN +#endif + +TEXT touser(SB), 0, $-4 + MOV $0x1020, R1 + MOV $(1<<4), R2 /* aarch32 execution state */ + AND $0xFFFFFFFF, R0, R13 + MSR R1, ELR_EL1 + MSR R2, SPSR_EL1 + ERET + +#define FPSRBITS (0x1F<<27 | 1<<7 | 0x1F) +#define FPCRBITS (0xFFF<<15 | 0x1F<<8) + +TEXT fprdscr(SB), 1, $-4 + MOV FPSR, R1 + AND $FPSRBITS, R1 + MOV FPCR, R0 + AND $FPCRBITS, R0 + ORR R1, R0 + RETURN + +TEXT fprdexc(SB), 1, $-4 + MOV $0, R0 + MRS SPR(CPACR_EL1), R1 + AND $(3<<20), R1 + CMP $(3<<20), R1 + BNE 2(PC) + MOV $(1<<30), R0 + RETURN + +TEXT fprdcpacr(SB), 1, $-4 + MRS SPR(CPACR_EL1), R0 + RETURN; + +TEXT fpwrscr(SB), 1, $-4 + AND $FPSRBITS, R0, R1 + MOV R1, FPSR + AND $FPCRBITS, R0, R1 + MOV R1, FPCR + RETURN + +TEXT fpwrexc(SB), 1, $-4 + MOV $0, R1 + AND $(1<<30), R0 + CBZ R0, 2(PC) + MOV $(3<<20), R1 + MSR R1, SPR(CPACR_EL1) + DSB $SY + ISB $SY + RETURN + +TEXT fpsavef1(SB), 1, $-4 + FMOVD F1, (R0) + RETURN + +#define FSTQ(src,dst,off) WORD $(0x3D800000 | src<<0 | dst<<5 | (off/16)<<10) +TEXT fpsaveregs(SB), 1, $-4 + FSTQ (0,0,0) + FSTQ (1,0,16) + FSTQ (2,0,32) + FSTQ (3,0,48) + FSTQ (4,0,64) + FSTQ (5,0,80) + FSTQ (6,0,96) + FSTQ (7,0,112) + FSTQ (8,0,128) + FSTQ (9,0,144) + FSTQ (10,0,160) + FSTQ (11,0,176) + FSTQ (12,0,192) + FSTQ (13,0,208) + FSTQ (14,0,224) + FSTQ (15,0,240) + RETURN + +#define FLDQ(src,off,dst) WORD $(0x3DC00000 | dst<<0 | src<<5 | (off/16)<<10) +TEXT fprestregs(SB), 1, $-4 + FLDQ (0,0,0) + FLDQ (0,16,1) + FLDQ (0,32,2) + FLDQ (0,48,3) + FLDQ (0,64,4) + FLDQ (0,80,5) + FLDQ (0,96,6) + FLDQ (0,112,7) + FLDQ (0,128,8) + FLDQ (0,144,9) + FLDQ (0,160,10) + FLDQ (0,176,11) + FLDQ (0,192,12) + FLDQ (0,208,13) + FLDQ (0,224,14) + FLDQ (0,240,15) + RETURN + +TEXT cas(SB), 1, $-4 + MOVWU ov+8(FP), R1 + MOVWU nv+16(FP), R2 +#ifdef ATOMICS + MOV R1, R3 + CASALW(1,2,0) + CMP R1, R3 + BNE 3(PC) + MOV $1, R0 + RETURN + MOV $0, R0 + RETURN +#else +_cas1: + LDXRW (R0), R3 + CMP R3, R1 + BNE _cas0 + STXRW R2, (R0), R4 + CBNZ R4, _cas1 + MOVW $1, R0 + DMB $ISH + RETURN +_cas0: + CLREX + MOVW $0, R0 + RETURN +#endif + +TEXT setlabel(SB), 1, $-4 + MOV SP, R1 + MOVP R1, LR, 0(R0) + MOVW $0, R0 + RETURN + +TEXT gotolabel(SB), 1, $-4 + MOVP 0(R0), R1, LR + MOV R1, SP + MOVW $1, R0 + RETURN + +TEXT getcallerpc(SB), $0 + MOV 0(SP), R0 + RETURN + +TEXT idlehands(SB), 1, $-4 + MRS DAIF, R3 + MSR $(Dirq|Dfiq), DAIFSet /* splfhi */ + DSB $SY + MOVW nrdy(SB), R0 + CBNZ R0, 3(PC) + WFI + DSB $SY + MSR R3, DAIF /* splx */ + RETURN + +TEXT cachedwbtlb(SB), 1, $-4 +TEXT coherence(SB), 1, $-4 + DSB $SY + RETURN + +/* + * invalidate tlb + */ +TEXT mmuinvalidate(SB), 0, $-4 + DSB $NSHST + TLBI ZR, 0,8,7,0 /* VMALLE1 */ + DSB $NSH + ISB $SY + RETURN + +TEXT mmuinvalidateaddr(SB), 0, $-4 + DSB $NSHST + LSR $12, R0 + TLBI R0, 0,8,7,3 /* VAAE1 */ + DSB $NSH + ISB $SY + RETURN + +#include "cache.v8.s" + +TEXT reboot(SB), $0 +TEXT callwithureg(SB), $0 + RETURN --- /sys/src/9/bcm64/main.c Thu Jan 1 00:00:00 1970 +++ /sys/src/9/bcm64/main.c Sat Apr 11 21:43:46 2026 @@ -0,0 +1,724 @@ +#include "u.h" +#include "../port/tos32.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "io.h" +#include "fns.h" + +#include "init.h" +#include + +enum { + /* space for syscall args, return PC, top-of-stack struct */ + Ustkheadroom = sizeof(Sargs) + sizeof(uintptr) + sizeof(Tos32), +}; + +Mach* machaddr[MAXMACH]; +Conf conf; + +/* + * Where configuration info is left for the loaded programme. + */ +#define BOOTARGS ((char*)CONFADDR) +#define BOOTARGSLEN (MACHADDR-CONFADDR) +#define MAXCONF 64 +#define MAXCONFLINE 160 + +void (*pl011init)(void); + +/* + * Option arguments from the command line. + * oargv[0] is the boot file. + */ +static int oargc; +static char* oargv[20]; +static char oargb[128]; +static int oargblen; + +static uintptr sp; /* XXX - must go - user stack of init proc */ + +/* store plan9.ini contents here at least until we stash them in #ec */ +static char confname[MAXCONF][KNAMELEN]; +static char confval[MAXCONF][MAXCONFLINE]; +static int nconf; + +static int +findconf(char *name) +{ + int i; + + for(i = 0; i < nconf; i++) + if(cistrcmp(confname[i], name) == 0) + return i; + return -1; +} + +char* +getconf(char *name) +{ + int i; + + i = findconf(name); + if(i >= 0) + return confval[i]; + return nil; +} + +void +addconf(char *name, char *val) +{ + int i; + + i = findconf(name); + if(i < 0){ + if(val == nil || nconf >= MAXCONF) + return; + i = nconf++; + strecpy(confname[i], confname[i]+sizeof(confname[i]), name); + } + strecpy(confval[i], confval[i]+sizeof(confval[i]), val); +} + +static void +plan9iniinit(char *s, int cmdline) +{ + char *toks[MAXCONF]; + int i, c, n; + char *v; + + if((c = *s) < ' ' || c >= 0x80) + return; + if(cmdline) + n = tokenize(s, toks, MAXCONF); + else + n = getfields(s, toks, MAXCONF, 1, "\n"); + for(i = 0; i < n; i++){ + if(toks[i][0] == '#') + continue; + v = strchr(toks[i], '='); + if(v == nil) + continue; + *v++ = '\0'; + addconf(toks[i], v); + } +} + +/* + * Flattened device tree walk + */ +typedef struct Dtnode Dtnode; +struct Dtnode { + Dtnode *parent; + char *name; +}; +static struct { + char *stringtab; + void (*property)(Dtnode, char*, uchar*, int); +} dt; + +uchar * +dtwalk(uchar *p, Dtnode n) +{ + Dtnode nn; + uint len, nameoff; + + nn.parent = &n; + //print("walk %s %#p", n.name, p); + //for(int i = 0; i < 16; i++) print(" %2.2ux", p[i]); + //print("\n"); + for(;;){ + switch(nhgetl(p)){ + case 9: /* end tree */ + return p; + case 1: /* begin node */ + nn.name = (char*)p + 4; + p = (uchar*)nn.name + strlen(nn.name) + 1; + p = (uchar*)(((uintptr)p + 3) & ~3); + p = dtwalk(p, nn); + if(p == nil) + return nil; + break; + case 2: /* end node */ + return p + 4; + case 3: /* property */ + len = nhgetl(p + 4); + nameoff = nhgetl(p + 8); + (*dt.property)(n, &dt.stringtab[nameoff], p + 12, len); + p += 12 + len; + p = (uchar*)(((uintptr)p + 3) & ~3); + break; + case 4: /* nop */ + p += 4; + break; + default: /* syntax error */ + return nil; + } + } +} + +int +devicetree(void *a, void (*f)(Dtnode, char*, uchar*, int)) +{ + u32int *hdr; + Dtnode root; + + hdr = a; + if(nhgetl(hdr) != 0xd00dfeed){ + print("no devicetree at %#p: %ux\n", hdr, nhgetl(hdr)); + return -1; + } + dt.stringtab = (char*)a + nhgetl(&hdr[3]); + dt.property = f; + root.parent = &root; + root.name = "/"; + if(dtwalk((uchar*)a + nhgetl(&hdr[2]), root) == nil){ + print("bad syntax in devicetree\n"); + return -1; + } + return 0; +} + +void +dtproperty(Dtnode n, char *prop, uchar *value, int len) +{ + extern uchar ether0mac[]; + + if(!strcmp(n.name, "chosen") && !strcmp(prop, "bootargs")) + plan9iniinit((char*)value, 1); + else if(!strncmp(n.name, "ethernet@", 9) && + !strcmp(n.parent->name, "rp1") && + strstr(prop, "local-mac-address") && len == 6) + memmove(ether0mac, value, 6); + else if(!strncmp(n.name, "pcie@12", 7) && + !strcmp(prop, "ranges") && + len >= 48) + soc.pcispace = (uvlong)nhgetl(value+40) << 32; + else if(!strcmp(n.name, "memory@0") && !strcmp(prop, "reg")) + conf.mem[0].limit = nhgetl(value + 8); + else if(!strcmp(n.name, "clk_emmc2") && !strcmp(prop, "clock-frequency")) + soc.emmc2freq = nhgetl(value); + else if(!strncmp(n.name, "framebuffer@", 12) && + !strcmp(n.parent->name, "chosen")) + fbsetup(prop, value, len); + else if(!strcmp(n.name, "pinctrl@7d504100") && !strcmp(prop, "compatible")){ + if(strstr((char*)value, "bcm2712d") != nil) + soc.dstepping = 1; + } +} + +uvlong memsize = 0x30000000; + +void +confinit() +{ + int i, userpcnt; + ulong kpages; + uintptr pa; + char *p; + + if((p = getconf("service")) != nil){ + if(strcmp(p, "cpu") == 0) + cpuserver = 1; + else if(strcmp(p,"terminal") == 0) + cpuserver = 0; + } + if((p = getconf("*maxmem")) != nil){ + memsize = strtoull(p, 0, 0) - PHYSDRAM; + if (memsize < 16*MB) /* sanity */ + memsize = 16*MB; + } + + getramsize(&conf.mem[0]); + if(conf.mem[0].limit == 0){ + conf.mem[0].base = PHYSDRAM; + conf.mem[0].limit = PHYSDRAM + memsize; + } + /* + * pi4 extra memory (beyond video ram) indicated by board id + */ + switch(getboardrev()&0xF00000){ + case 0xA00000: + break; + case 0xB00000: + conf.mem[1].base = 1*GiB; + conf.mem[1].limit = 2*GiB; + break; + case 0xC00000: + conf.mem[1].base = 1*GiB; + conf.mem[1].limit = 0xFF000000; + break; + default: + case 0xD00000: + conf.mem[1].base = 1*GiB; + conf.mem[1].limit = 0xFF000000; + conf.himem.base = 4LL*GiB; + conf.himem.limit = 8LL*GiB; + break; + case 0xE00000: + conf.mem[1].base = 1*GiB; + conf.mem[1].limit = 0xFF000000; + conf.himem.base = 4LL*GiB; + conf.himem.limit = 16LL*GiB; + break; + } + if(p != nil){ + for(i = 0; i < nelem(conf.mem); i++){ + if(memsize < conf.mem[i].base) + conf.mem[i].limit = conf.mem[i].base; + else if(memsize < conf.mem[i].limit) + conf.mem[i].limit = memsize; + } + if(memsize < conf.himem.base) + conf.himem.limit = conf.himem.base; + else if(memsize < conf.himem.limit) + conf.himem.limit = memsize; + } + if(conf.himem.limit > 0) + soc.dramsize = conf.himem.limit; + else + soc.dramsize = conf.mem[1].limit; + + if(p = getconf("*kernelpercent")) + userpcnt = 100 - strtol(p, 0, 0); + else + userpcnt = 0; + + conf.npage = 0; + pa = PADDR(PGROUND(PTR2UINT(end))); + pa += BY2PG; + + /* + * we assume that the kernel is at the beginning of one of the + * contiguous chunks of memory and fits therein. + */ + for(i=0; i conf.mem[i].base && pa < conf.mem[i].limit) + conf.mem[i].base = pa; + + conf.mem[i].npage = (conf.mem[i].limit - conf.mem[i].base)/BY2PG; + conf.npage += conf.mem[i].npage; + } + conf.himem.npage = (conf.himem.limit - conf.himem.base)/BY2PG; + + if(userpcnt < 10 || userpcnt > 99) + userpcnt = 90; + conf.upages = (conf.npage*userpcnt)/100; + if(conf.npage - conf.upages > 800*MiB/BY2PG) + conf.upages = conf.npage - 800*MiB/BY2PG; + conf.ialloc = ((conf.npage-conf.upages)/2)*BY2PG; + + /* set up other configuration parameters */ + conf.nproc = 100 + (conf.npage/(MB/BY2PG))*5; + if(cpuserver) + conf.nproc *= 3; + if(conf.nproc > 2000) + conf.nproc = 2000; + conf.nswap = conf.npage*3; + conf.nswppo = 4096; + conf.nimage = 200; + + conf.copymode = 1; /* copy on reference, not copy on write */ + + /* + * Guess how much is taken by the large permanent + * datastructures. Mntcache and Mntrpc are not accounted for + * (probably ~300KB). + */ + kpages = conf.npage - conf.upages; + kpages *= BY2PG; + kpages -= conf.upages*sizeof(Page) + + conf.nproc*sizeof(Proc) + + conf.nimage*sizeof(Image) + + conf.nswap + + conf.nswppo*sizeof(Page*); + mainmem->maxsize = kpages; + if(!cpuserver) + /* + * give terminals lots of image memory, too; the dynamic + * allocation will balance the load properly, hopefully. + * be careful with 32-bit overflow. + */ + imagmem->maxsize = kpages; +} + +/* enable scheduling of this cpu */ +void +machon(uint cpu) +{ + ulong cpubit; + + cpubit = 1 << cpu; + lock(&active); + if ((active.machs & cpubit) == 0) { /* currently off? */ + conf.nmach++; + active.machs |= cpubit; + } + unlock(&active); +} + +/* disable scheduling of this cpu */ +void +machoff(uint cpu) +{ + ulong cpubit; + + cpubit = 1 << cpu; + lock(&active); + if (active.machs & cpubit) { /* currently on? */ + conf.nmach--; + active.machs &= ~cpubit; + } + unlock(&active); +} + +void +machinit(void) +{ + Mach *m0; + + m->ticks = 1; + m->perf.period = 1; + m0 = MACHP(0); + if (m->machno != 0) { + /* synchronise with cpu 0 */ + m->ticks = m0->ticks; + } +} + +void +mach0init(void) +{ + conf.nmach = 0; + + m->machno = 0; + machaddr[m->machno] = m; + + machinit(); + active.exiting = 0; + + up = nil; +} + +void +launchinit(int ncpus) +{ + int mach; + Mach *mm; + PTE *l1; + + if(ncpus > MAXMACH) + ncpus = MAXMACH; + for(mach = 1; mach < ncpus; mach++){ + machaddr[mach] = mm = mallocalign(MACHSIZE, MACHSIZE, 0, 0); + l1 = mallocalign(L1SIZE, BY2PG, 0, 0); + if(mm == nil || l1 == nil) + panic("launchinit"); + memset(mm, 0, MACHSIZE); + mm->machno = mach; + + mmuinit(PADDR(l1)); /* clone cpu0's l1 table */ + cachedwbse(l1, L1SIZE); + mm->mmul1 = l1; + cachedwbse(mm, MACHSIZE); + } + cachedwbse(machaddr, sizeof machaddr); + if((mach = startcpus(ncpus)) < ncpus) + print("only %d cpu%s started\n", mach, mach == 1? "" : "s"); +} + +static void +optionsinit(char* s) +{ + strecpy(oargb, oargb+sizeof(oargb), s); + + oargblen = strlen(oargb); + oargc = tokenize(oargb, oargv, nelem(oargv)-1); + oargv[oargc] = nil; +} + +void +main(uvlong arg) +{ + devicetree((void*)arg, dtproperty); + m = (Mach*)MACHADDR; + mach0init(); + m->mmul1 = (PTE*)L1; + machon(0); + + optionsinit("/boot/boot boot"); + quotefmtinstall(); + + confinit(); + xinit(); + xsummary(); + /* set clock rate to arm_freq from config.txt (default pi1:700Mhz pi2:900MHz) */ + setclkrate(ClkArm, 0); + screeninit(); + if(pl011init != nil) + (*pl011init)(); + print("\nPlan 9 from Bell Labs\n"); + print("vcore reports memory = 0x%llux\n", conf.mem[0].limit); + trapinit(); + clockinit(); + printinit(); + timersinit(); + if(conf.monitor) + swcursorinit(); + cpuidprint(); + if(0)print("clocks: CPU %lud core %lud UART %lud EMMC %lud\n", + getclkrate(ClkArm), getclkrate(ClkCore), getclkrate(ClkUart), getclkrate(ClkEmmc)); + archreset(); + procinit0(); + initseg(); + links(); + chandevreset(); /* most devices are discovered here */ + pageinit(); + lpapageinit(); + swapinit(); + userinit(); + launchinit(getncpus()); + mmuinit1(); + schedinit(); + assert(0); /* shouldn't have returned */ + spllo(); +} + +/* + * starting place for first process + */ +void +init0(void) +{ + int i; + Chan *c; + char buf[2*KNAMELEN]; + + up->nerrlab = 0; + coherence(); + spllo(); + + /* + * These are o.k. because rootinit is null. + * Then early kproc's will have a root and dot. + */ + up->slash = namec("#/", Atodir, 0, 0); + pathclose(up->slash->path); + up->slash->path = newpath("/"); + up->dot = cclone(up->slash); + + chandevinit(); + + if(!waserror()){ + snprint(buf, sizeof(buf), "%s %s", "ARM", conffile); + ksetenv("terminal", buf, 0); + ksetenv("cputype", "arm", 0); + if(cpuserver) + ksetenv("service", "cpu", 0); + else + ksetenv("service", "terminal", 0); + //snprint(buf, sizeof(buf), "-a %s", getethermac()); + //ksetenv("etherargs", buf, 0); + + /* convert plan9.ini variables to #e and #ec */ + for(i = 0; i < nconf; i++) { + ksetenv(confname[i], confval[i], 0); + ksetenv(confname[i], confval[i], 1); + } + if(getconf("pitft")){ + c = namec("#P/pitft", Aopen, OWRITE, 0); + if(!waserror()){ + devtab[c->type]->write(c, "init", 4, 0); + poperror(); + } + cclose(c); + } + poperror(); + } + kproc("alarm", alarmkproc, 0); + touser(sp); + assert(0); /* shouldn't have returned */ +} + +static void +bootargs(uintptr base) +{ + int i; + ulong ssize; + char **av, *p; + + /* + * Push the boot args onto the stack. + * The initial value of the user stack must be such + * that the total used is larger than the maximum size + * of the argument list checked in syscall. + */ + i = oargblen+1; + p = UINT2PTR(STACKALIGN(base + BY2PG - Ustkheadroom - i)); + memmove(p, oargb, i); + + /* + * Now push the argv pointers. + * The code jumped to by touser in lproc.s expects arguments + * main(char* argv0, ...) + * and calls + * startboot("/boot/boot", &argv0) + * not the usual (int argc, char* argv[]) + */ + av = (char**)(p - (oargc+1)*sizeof(ulong)); + av = (char**)STACKALIGN(PTR2UINT(av)); + ssize = base + BY2PG - PTR2UINT(av); + for(i = 0; i < oargc; i++){ + *(ulong*)av = PTR2UINT((oargv[i] - oargb) + (p - base) + (USTKTOP - BY2PG)); + av = (char**)(PTR2UINT(av) + sizeof(ulong)); + } + *(ulong*)av = 0; + sp = USTKTOP - ssize; +} + +/* + * create the first process + */ +void +userinit(void) +{ + Proc *p; + Segment *s; + KMap *k; + Page *pg; + + /* no processes yet */ + up = nil; + + p = newproc(); + p->compat32 = 1; + p->pgrp = newpgrp(); + p->egrp = smalloc(sizeof(Egrp)); + p->egrp->ref = 1; + p->fgrp = dupfgrp(nil); + p->rgrp = newrgrp(); + p->procmode = 0640; + + kstrdup(&eve, ""); + kstrdup(&p->text, "*init*"); + kstrdup(&p->user, eve); + + /* + * Kernel Stack + */ + p->sched.pc = PTR2UINT(init0); + p->sched.sp = PTR2UINT(p->kstack+KSTACK-sizeof(up->s.args)-sizeof(uintptr)); + p->sched.sp = STACKALIGN(p->sched.sp); + + /* + * User Stack + * + * Technically, newpage can't be called here because it + * should only be called when in a user context as it may + * try to sleep if there are no pages available, but that + * shouldn't be the case here. + */ + s = newseg(SG_STACK, USTKTOP-USTKSIZE, USTKSIZE/BY2PG); + s->flushme++; + p->seg[SSEG] = s; + pg = newpage(1, 0, USTKTOP-BY2PG); + segpage(s, pg); + k = kmap(pg); + bootargs(VA(k)); + kunmap(k); + + /* + * Text + */ + s = newseg(SG_TEXT, UTZERO_COMPAT32, 1); + p->seg[TSEG] = s; + pg = newpage(1, 0, UTZERO_COMPAT32); + memset(pg->cachectl, PG_TXTFLUSH, sizeof(pg->cachectl)); + segpage(s, pg); + k = kmap(s->map[0]->pages[0]); + memmove(UINT2PTR(VA(k)), initcode, sizeof initcode); + kunmap(k); + + ready(p); +} + +static void +shutdown(int ispanic) +{ + int ms, once; + + lock(&active); + if(ispanic) + active.ispanic = ispanic; + else if(m->machno == 0 && (active.machs & (1<machno)) == 0) + active.ispanic = 0; + once = active.machs & (1<machno); + active.machs &= ~(1<machno); + active.exiting = 1; + unlock(&active); + + if(once) { + delay(m->machno*100); /* stagger them */ + iprint("cpu%d: exiting\n", m->machno); + } + spllo(); + if (m->machno == 0) + ms = 5*1000; + else + ms = 2*1000; + for(; ms > 0; ms -= TK2MS(2)){ + delay(TK2MS(2)); + if(active.machs == 0 && consactive() == 0) + break; + } + if(active.ispanic){ + if(!cpuserver) + for(;;) + ; + if(getconf("*debug")) + delay(5*60*1000); + else + delay(10000); + } +} + +void +exit(int code) +{ + shutdown(code); + splfhi(); + if(m->machno == 0) + archreboot(); + else + for(;;){} +} + +/* + * stub for ../omap/devether.c + */ +int +isaconfig(char *class, int ctlrno, ISAConf *isa) +{ + char cc[32], *p; + int i; + + if(strcmp(class, "ether") != 0) + return 0; + snprint(cc, sizeof cc, "%s%d", class, ctlrno); + p = getconf(cc); + if(p == nil) + return (ctlrno == 0); + isa->type = ""; + isa->nopt = tokenize(p, isa->opt, NISAOPT); + for(i = 0; i < isa->nopt; i++){ + p = isa->opt[i]; + if(cistrncmp(p, "type=", 5) == 0) + isa->type = p + 5; + } + return 1; +} + +void +rerrstr(char*, uint) +{} --- /sys/src/9/bcm64/mem.h Thu Jan 1 00:00:00 1970 +++ /sys/src/9/bcm64/mem.h Wed Apr 8 09:50:15 2026 @@ -0,0 +1,98 @@ +/* + * Memory and machine-specific definitions. Used in C and assembler. + */ +#define KiB 1024u /* Kibi 0x0000000000000400 */ +#define MiB 1048576u /* Mebi 0x0000000000100000 */ +#define GiB 1073741824u /* Gibi 000000000040000000 */ + +/* + * Sizes + */ +#define BY2PG (4*KiB) /* bytes per page */ +#define PGSHIFT 12 /* log(BY2PG) */ + +#define MAXMACH 4 /* max # cpus system can run */ +#define MACHSIZE BY2PG +#define L1SIZE (5 * BY2PG) + +#define KSTKSIZE (16*KiB) /* was 8*KiB ... revisit? */ +#define STACKALIGN(sp) ((sp) & ~15) /* bug: assure with alloc */ + +/* + * Magic registers + */ + +#define USER 26 /* R26 is up-> */ +#define MACH 27 /* R27 is m-> */ + +/* + * Address spaces. + * KTZERO is used by kprof and dumpstack (if any). + * + * KZERO is mapped to physical 0 (start of ram). + * + */ + +#define L2VA 0x100000000uLL /* L2 page tables virtual addr */ +#define UCKZERO 0x140000000ull /* kernel low RAM uncached */ +#define KSEG0 0x200000000ull /* kernel segment */ +#define KSEGM 0xFFFFFFFE00000000ull /* mask to check segment */ +#define KZERO KSEG0 /* kernel address space */ +#define MACHADDR (KZERO+0x80000) /* Mach structure */ +#define L1 (KZERO+0x81000) /* tt ptes: 5 pages 4KiB aligned */ + +#define KTZERO (KZERO+0x100000) /* kernel text start */ +#define VIRTIO (KZERO+16LL*GiB) /* i/o registers */ +#define FRAMEBUFFER (VIRTIO+4LL*GiB) /* video framebuffer */ +#define ARMLOCAL VIRTIO /* armv7/8 only */ +#define DESCRIPTORS (UCKZERO+0xF0000) + +#define UZERO 0 /* user segment */ +#define UTZERO (UZERO+0x10000) +#define UTROUND(t) ROUNDUP((t), 0x10000) +#define UTZERO_COMPAT32 (UZERO+BY2PG) /* user text start */ +#define UTROUND_COMPAT32(t) ROUNDUP((t), BY2PG) +#define USTKTOP 0xFFFF0000uLL /* user segment end +1 */ +#define USTKSIZE (16*1024*1024) /* user stack size */ +#define TSTKTOP (USTKTOP-USTKSIZE) /* sysexec temporary stack */ +#define TSTKSIZ 256 + +/* address at which to copy and execute rebootcode */ +#define REBOOTADDR (KZERO+0x1800) + +/* + * Legacy... + */ +#define BLOCKALIGN 64 /* only used in allocb.c */ +#define KSTACK KSTKSIZE + +/* + * Sizes + */ +#define BI2BY 8 /* bits per byte */ +#define BY2SE 4 +#define BY2WD 4 +#define BY2V 8 /* only used in xalloc.c */ + +#define PTEMAPMEM (1024*1024) +#define PTEPERTAB (PTEMAPMEM/BY2PG) +#define SEGMAPSIZE 4096 +#define SSEGMAPSIZE 16 +#define PPN(x) ((x) & (~(BY2PG-1) | PTEHIMEM)) + +/* + * With a little work these move to port. + */ +#define PTEVALID (1<<0) +#define PTERONLY 0 +#define PTEWRITE (1<<1) +#define PTEUNCACHED (1<<2) +#define PTEKERNEL (1<<3) +#define PTEHIMEM (0xF<<8) + +/* + * Physical machine information from here on. + * PHYS addresses as seen from the arm cpu. + * BUS addresses as seen from the videocore gpu. + */ +#define PHYSDRAM 0 --- /sys/src/9/bcm64/mkfile Thu Jan 1 00:00:00 1970 +++ /sys/src/9/bcm64/mkfile Mon Apr 20 19:54:16 2026 @@ -0,0 +1,140 @@ +CONF=pi5 +CONFLIST=pi5 pi5cpu +EXTRACOPIES= + +loadaddr=0x200100000 + +objtype=arm64 +$target + +$OBJ: $HFILES + +install:V: /$objtype/$p$CONF + +/$objtype/$p$CONF:D: $p$CONF s$p$CONF + cp -x $p$CONF s$p$CONF /$objtype/ & + for(i in $EXTRACOPIES) + { 9fs $i && cp $p$CONF s$p$CONF /n/$i/$objtype && echo -n $i... & } + wait + echo + touch $target + +<../boot/bootmkfile +<../port/portmkfile +<|../port/mkbootrules $CONF + +^($BCM)\.$O:R: '../bcm/\1.c' + $CC $CFLAGS -. -I. -I../bcm ../bcm/$stem1.c + +^($OMAP)\.$O:R: '../omap/\1.c' + $CC $CFLAGS -. -I. -I../bcm ../omap/$stem1.c + +arch.$O clock.$O fpiarm.$O main.$O mmu4.$O screen.$O syscall.$O trap.$O trap4.$O: \ + /$objtype/include/ureg.h + +#archbcm.$O archbcm2.$O devether.$0 etherusb.$O: etherif.h ../port/netif.h +#fpi.$O fpiarm.$O fpimem.$O: ../port/fpi.h +#l.$O lexception.$O lproc.$O armv6.$O armv7.$O: arm.s +#armv7.$O: cache.v7.s +#main.$O: errstr.h init.h reboot.h +#mouse.$O screen.$O: screen.h +#devusb.$O usbdwc.$O: ../port/usb.h +#usbdwc.$O: dwcotg.h + +init.h:D: ../port/initcode.c ../bcm/init9.s + 5c ../port/initcode.c + 5a ../bcm/init9.s + 5l -l -R1 -s -o init.out init9.5 initcode.5 /arm/lib/libc.a + {echo 'uchar initcode[]={' + xd -1x init.h + +#reboot.h:D: rebootcode.s arm.s arm.h mem.h +# $AS rebootcode.s +# # -lc is only for memmove. -T arg is PADDR(REBOOTADDR) +# $LD -l -s -T0x1800 -R4 -o reboot.out rebootcode.$O -lc +# {echo 'uchar rebootcode[]={' +# xd -1x reboot.out | +# sed -e '1,2d' -e 's/^[0-9a-f]+ //' -e 's/ ([0-9a-f][0-9a-f])/0x\1,/g' +# echo '};'} > reboot.h --- /sys/src/9/bcm64/mkureg Thu Jan 1 00:00:00 1970 +++ /sys/src/9/bcm64/mkureg Wed Jul 30 20:58:36 2025 @@ -0,0 +1,15 @@ +#!/bin/rc + +{ echo '#include '; echo '#include ' } | 7c -a /fd/0 | sed 's/;$//g' | awk ' +/sizeofUreg/ { + active = 1 + print "Ureg_size = " $3 + next +} +active && NF == 3 { + print "Ureg_" $3 " = " $2 +} +/^}/ { + active = 0 +} +' --- /sys/src/9/bcm64/mmu64.c Thu Jan 1 00:00:00 1970 +++ /sys/src/9/bcm64/mmu64.c Wed Apr 8 11:19:33 2026 @@ -0,0 +1,500 @@ +/* + * armv8-a aarch64 memory management + * initial implementation: minimally evolved from armv7 LPAE + * + * Three levels of page tables: + * L0 table: we use <128 entries, each mapping 1GiB virtual space, as either + * a contiguous block, or + * an L1 page: 512 entries, each mapping 2MiB, as either + * a contiguous block, or + * an L2 page: 512 entries, each mapping 4KiB + */ + +#include "u.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#include "../bcm/arm.h" + +typedef uvlong LPTE; + +#define L0X(va) FEXT((va), 30, 8) +#define L1X(va) FEXT((va), 21, 12) +#define L2X(va) FEXT((va), 12, 9) + +enum { + L1lo = L1X(UZERO), + L1hi = L1X(USTKTOP+2*MiB-1), + L2size = BY2PG, + + LXN = 1LL<<54, + LPXN = 1LL<<53, + LAF = 1<<10, + LShareOuter = 2<<8, + LShareInner = 3<<8, + LKrw = 0<<6, + LUrw = 1<<6, + LUro = 3<<6, + LMem = 0<<2, + LUncached = 1<<2, + LDevice = 2<<2, + LBlock = 1<<0, + LTable = 3<<0, + LPage = 3<<0, + + MMem = 0xFF, /* MEM_WB */ + MUncached = 0x44, /* MEM_UC */ + MDevice = 0x04, /* DEV_nGnRE*/ + + EAE_LPAE = 1<<31, + SH0_INNER = 3<<12, + ORGN0_WB = 1<<10, + IRGN0_WB = 1<<8, + + LAttrDRAM = LKrw | LAF | LShareOuter | LMem, + LAttrPhys = LPXN | LAF | LShareOuter | LUncached, + LAttrIO = LKrw | LAF | LXN | LPXN | LShareOuter | LDevice, + LAttrUser = LPXN | LAF | LShareInner | LMem, +}; + +#define XPPN(pa) ((pa)&~(BY2PG-1) | (uvlong)((pa)&PTEHIMEM) << (32-8)) +#define LPPN(pte) ((pte)&0xFFFFFF000ULL) + +/* + * TTBCR and MAIR0 register settings for LPAE (used in armv7.s) + */ +ulong mair0 = MMem<<0 | MUncached<<8 | MDevice<<16; +ulong ttbcr = EAElpae | SH0inner | ORGN0wb | IRGN0wb; + +/* + * TCR_EL1 and MAIR_EL1 settings for aarch64 (used in l.s) + */ +uvlong tcr_el1 = + 2ull << 32 | /* phys address 40 bits */ + 2 << 30 | /* ttbr1 granularity 4k */ + 3 << 28 | /* ttbr1 share inner */ + 1 << 23 | /* disable ttbr1 */ + (64-37)<<16 | /* ttbr1 region size */ + 0 << 14 | /* ttbr0 granularity 4k */ + 3 << 12 | /* ttbr0 share inner */ + 1 << 10 | /* ttbr0 outer cache wb */ + 1 << 8 | /* ttbr0 inner cache wb */ + (64-37); /* ttbr0 region size */ + +uvlong mair_el1 = MMem<<0 | MUncached<<8 | MDevice<<16; + +/* + * Set up initial PTEs + * Called before main, with mmu off, to initialise cpu0's tables + * Called from launchinit to clone cpu0's tables for other cpus + */ +void +mmuinit(ulong a) +{ + LPTE *l0, *l1; + uintptr pa, va; + int i; + + l1 = (LPTE*)(uintptr)a; + if((uintptr)l1 != PADDR(L1)) + memmove(l1, m->mmul1, L1SIZE); + else + memset(l1, 0, L1SIZE); + + /* + * embed L0 table as a page of L1 table + * first page of L1 table also serves as L2 table page + * such that L2 tables are aliased at kernel address L2VA + */ + l0 = &l1[L1X(L2VA)]; + pa = PADDR(l1); + for(i = 0; i < 5; i++){ + l0[i] = pa|LAttrDRAM|LTable; + pa += BY2PG; + } + /* temporary identity map for low 1GB of RAM */ + pa = PHYSDRAM; + l0[0] = pa|LAttrDRAM|LBlock; + if((uintptr)l1 != PADDR(L1)) + return; + + /* kernel mapping for 16GB of RAM at KZERO */ + pa = PHYSDRAM; + va = KZERO; + for(i = 0; i < 16; i++){ + l0[L0X(va)] = pa|LAttrDRAM|LBlock; + va += GiB; + pa += GiB; + } + /* map first 1GB of RAM uncached at 1 4000 0000 */ + pa = PHYSDRAM; + va = UCKZERO; + l0[L0X(va)] = pa|LAttrPhys|LBlock; + /* + * map 4G for I/O registers at VIRTIO = 06 0000 0000 + * first half maps to local bus at 10 0000 0000 - 10 7FFF FFFF + * second half maps to RP1 in PCI window at 1C 0000 0000 - 1C 7FFF FFFF + */ + pa = 0x1000000000ull; + va = VIRTIO; + for(i = 0; i < 2; i++){ + l0[L0X(va)] = pa|LAttrIO|LBlock; + l0[L0X(pa)] = pa|LAttrIO|LBlock; + va += GiB; + pa += GiB; + } + pa = soc.pcispace; + for(; i < 4; i++){ + l0[L0X(va)] = pa|LAttrIO|LBlock; + va += GiB; + pa += GiB; + } +} + +void +mmuinit1() +{ + LPTE *l0, *l1; + uintptr pa; + + /* + * undo identity mapping for first 1GB of RAM + */ + l1 = m->mmull1; + l0 = &l1[L1X(L2VA)]; + pa = PADDR(l1); + l0[0] = pa|LAttrDRAM|LTable; + cachedwbtlb(l0, sizeof(*l0)); + + mmuinvalidate(); +} + +static int +nonzero(uintptr *p) +{ + int i; + for(i = 0; i < BY2PG/sizeof(uintptr); i++) + if(*p++ != 0) + return 1; + return 0; +} + +static void +mmul2empty(Proc* proc, int clear) +{ + LPTE *l1; + Page **l2, *page; + KMap *k; + + l1 = m->mmull1; + l2 = &proc->mmul2; + for(page = *l2; page != nil; page = page->next){ + if(clear){ + k = kmap(page); + memset((void*)VA(k), 0, L2size); + kunmap(k); + } + l1[page->daddr] = Fault; + l2 = &page->next; + } + coherence(); + *l2 = proc->mmul2cache; + proc->mmul2cache = proc->mmul2; + proc->mmul2 = nil; +} + +static void +mmul1empty(void) +{ + LPTE *l1; + + /* clean out any user mappings still in l1 */ + if(m->mmul1lo > 0){ + if(m->mmul1lo == 1) + m->mmull1[L1lo] = Fault; + else + memset(&m->mmull1[L1lo], 0, m->mmul1lo*sizeof(LPTE)); + m->mmul1lo = 0; + } + if(m->mmul1hi > 0){ + l1 = &m->mmull1[L1hi - m->mmul1hi]; + if(m->mmul1hi == 1) + *l1 = Fault; + else + memset(l1, 0, m->mmul1hi*sizeof(LPTE)); + m->mmul1hi = 0; + } + if(m->kmapll2 != nil) + memset(m->kmapll2, 0, NKMAPS*sizeof(LPTE)); +} + +void +mmuswitch(Proc* proc) +{ + int x; + LPTE *l1; + Page *page; + + if(proc != nil && proc->newtlb){ + mmul2empty(proc, 1); + proc->newtlb = 0; + } + + mmul1empty(); + + /* move in new map */ + l1 = m->mmull1; + if(proc != nil){ + for(page = proc->mmul2; page != nil; page = page->next){ + x = page->daddr; + l1[x] = XPPN(page->pa)|LAttrDRAM|LTable; + if(x >= L1lo + m->mmul1lo && x < L1hi - m->mmul1hi){ + if(x+1 - L1lo < L1hi - x) + m->mmul1lo = x+1 - L1lo; + else + m->mmul1hi = L1hi - x; + } + } + if(proc->nkmap) + memmove(m->kmapll2, proc->kmapltab, sizeof(proc->kmapltab)); + } + + /* make sure map is in memory */ + /* could be smarter about how much? */ + cachedwbtlb(&l1[L1X(UZERO)], (L1hi - L1lo)*sizeof(LPTE)); + if(proc != nil && proc->nkmap) + cachedwbtlb(m->kmapll2, sizeof(proc->kmapltab)); + + /* lose any possible stale tlb entries */ + mmuinvalidate(); +} + +void +flushmmu(void) +{ + int s; + + s = splhi(); + up->newtlb = 1; + mmuswitch(up); + splx(s); +} + +void +mmurelease(Proc* proc) +{ + Page *page, *next; + + mmul2empty(proc, 0); + for(page = proc->mmul2cache; page != nil; page = next){ + next = page->next; + if(--page->ref) + panic("mmurelease: page->ref %d", page->ref); + pagechainhead(page); + } + if(proc->mmul2cache && palloc.r.p) + wakeup(&palloc.r); + proc->mmul2cache = nil; + + mmul1empty(); + + /* make sure map is in memory */ + /* could be smarter about how much? */ + cachedwbtlb(&m->mmull1[L1X(UZERO)], (L1hi - L1lo)*sizeof(LPTE)); + + /* lose any possible stale tlb entries */ + mmuinvalidate(); +} + +void +putmmu(ulong va, ulong pa, Page* page) +{ + int x, s; + Page *pg; + LPTE *l1, *pte; + LPTE nx; + static int chat = 0; + int fromcache = 0; + + /* + * disable interrupts to prevent flushmmu (called from hzclock) + * from clearing page tables while we are setting them + */ + s = splhi(); + x = L1X(va); + l1 = &m->mmull1[x]; + pte = (LPTE*)(L2VA + L2size*x); + if(*l1 == Fault){ + /* l2 pages have 512 entries */ + if(up->mmul2cache == nil){ + spllo(); + pg = newpage(1, 0, 0); + splhi(); + /* if newpage slept, we might be on a different cpu */ + l1 = &m->mmull1[x]; + }else{ + fromcache = 1; + pg = up->mmul2cache; + up->mmul2cache = pg->next; + } + pg->daddr = x; + pg->next = up->mmul2; + up->mmul2 = pg; + + *l1 = XPPN(pg->pa)|LAttrDRAM|LTable; + cachedwbtlb(l1, sizeof *l1); + + /* force l2 page to memory */ + if(nonzero((uintptr*)pte)){ + if(chat++ < 32) + iprint("nonzero L2 page from %s pa %lux va %lux daddr %lux\n", + fromcache? "cache" : "newpage", pg->pa, pg->va, pg->daddr); + memset(pte, 0, L2size); + } + cachedwbtlb(pte, L2size); + + if(x >= L1lo + m->mmul1lo && x < L1hi - m->mmul1hi){ + if(x+1 - L1lo < L1hi - x) + m->mmul1lo = x+1 - L1lo; + else + m->mmul1hi = L1hi - x; + } + } + + /* protection bits are + * PTERONLY|PTEVALID; + * PTEWRITE|PTEVALID; + * PTEWRITE|PTEUNCACHED|PTEVALID; + */ + nx = LAttrUser|LPage; + if(pa & PTEUNCACHED) + nx = LAttrPhys|LPage; + if(pa & PTEWRITE) + nx |= LUrw; + else + nx |= LUro; + pte[L2X(va)] = XPPN(pa)|nx; + if(0)if(chat++ < 32) + iprint("putmmu %lux %lux => %llux\n", va, pa, pte[L2X(va)]); + cachedwbtlb(&pte[L2X(va)], sizeof(LPTE)); + + /* clear out the current entry */ + mmuinvalidateaddr(va); + + if(page->cachectl[m->machno] == PG_TXTFLUSH){ + /* pio() sets PG_TXTFLUSH whenever a text pg has been written */ + cachedwbse((void*)VA(kmap(page)), BY2PG); + cacheiinvse((void*)page->va, BY2PG); + page->cachectl[m->machno] = PG_NOFLUSH; + } + //checkmmu(va, pa); + splx(s); +} + +/* + * With 64-bit kernel, all memory is addressable with KADDR. + * The remaining use of cankaddr is to limit (in xinit) the + * number of kernel pages, to ensure that malloc'ed memory + * is addressible with 32 bits. + */ +uintptr +cankaddr(uintptr pa) +{ + if(pa == 0 || pa > 1ull<<32) + return 0; + return 1ull<<32 - pa; +} + +uintptr +mmukmap(uintptr va, uintptr pa, usize size) +{ + uint o; + LPTE *l0, *l1, *pte; + + print("mmukmapx %#p => %#p (%lux)\n", va, pa, size); + l1 = (LPTE*)L1; + l0 = &l1[L1X(L2VA)]; + o = pa & (GiB-1); + pa -= o; + pte = &l0[L0X(va)]; + *pte = pa|LAttrIO|LBlock; + cachedwbse(pte, sizeof *pte); /* just needs barrier? */ + return va + o; +} + +void +checkmmu(ulong va, ulong pa) +{ + int x; + LPTE *l1, *pte; + + x = L1X(va); + l1 = &m->mmull1[x]; + if((*l1<able) != LTable){ + iprint("checkmmu cpu%d va=%#lux l1 %p=%llux\n", m->machno, va, l1, *l1); + return; + } + pte = (LPTE*)(L2VA + L2size*x); + pte += L2X(va); + if(pa == ~0 || (pa != 0 && LPPN(*pte) != XPPN(pa))){ + iprint("checkmmu va=%#lux pa=%lux l1 %p=%llux pte %p", va, pa, l1, *l1, pte); + iprint("=%llux\n", *pte); + } +} + +KMap* +kmap(Page *p) +{ + uvlong pa; + + pa = XPPN(p->pa); + return (KMap*)KADDR(pa); +} + +void kunmap(KMap*) {} + +/* + * Append pages with physical addresses above 4GiB to the freelist + */ +void +lpapageinit(void) +{ + int j; + ulong npage; + Page *pages, *p; + uvlong pa; + + npage = conf.himem.npage; + if(npage == 0) + return; + + pages = xalloc(npage*sizeof(Page)); + if(pages == 0) + panic("lpapageinit"); + + p = pages; + pa = conf.himem.base; + for(j=0; jprev = p-1; + p->next = p+1; + p->pa = pa; + p->pa |= (pa >> (32-8)) & PTEHIMEM; + p++; + pa += BY2PG; + } + palloc.tail->next = pages; + pages[0].prev = palloc.tail; + palloc.tail = p - 1; + palloc.tail->next = 0; + + palloc.freecount += npage; + palloc.user += npage; + + /* Paging numbers */ + swapalloc.highwater = (palloc.user*5)/100; + swapalloc.headroom = swapalloc.highwater + (swapalloc.highwater/4); + + print("%ldM extended memory: ", npage*(BY2PG/1024)/1024); + print("%ldM user total\n", palloc.user*(BY2PG/1024)/1024); +} Binary files sys/src/9/bcm64/nvram and /sys/src/9/bcm64/nvram differ --- /sys/src/9/bcm64/pi5 Thu Jan 1 00:00:00 1970 +++ /sys/src/9/bcm64/pi5 Thu Apr 23 15:54:36 2026 @@ -0,0 +1,76 @@ +dev + root + cons + env + pipe + proc + mnt + srv + dup + arch + ssl + tls + bridge log + cap + fs + ip arp chandial ip ipv6 ipaux iproute netlog nullmedium pktmedium ptclbsum inferno + draw screen + mouse mouse + kbmap + kbin kbd latin1 + uart +# gpio gpio +# spi spi +# i2c i2c + + fakertc +# rtc3231 i2c + ether netif + sd + usb + aoe + +link + archbcm5 +# pci +# gisb + loopbackmedium + ethermedium + sdhc + usbxhci +# etherusb + ether4330 + ethergem ethermii gpio +# pitft + +ip + tcp + udp + ipifc + icmp + icmp6 +# ipmux + +misc + mmu64 + trap + uartpl011 gpiorp1 + sdmmc + sdaoe sdscsi + vcore + vfp3 + +port + int cpuserver = 0; + +boot boot #S/sdM0/ + local + tcp + +bootdir + boot$CONF.out boot + /arm/bin/ip/ipconfig + /arm/bin/auth/factotum + /arm/bin/fossil/fossil + /arm/bin/venti/venti + /arm/bin/usb/usbd --- /sys/src/9/bcm64/pi5cpu Thu Jan 1 00:00:00 1970 +++ /sys/src/9/bcm64/pi5cpu Thu Apr 23 15:54:57 2026 @@ -0,0 +1,76 @@ +dev + root + cons + env + pipe + proc + mnt + srv + dup + arch + ssl + tls + bridge log + cap + fs + ip arp chandial ip ipv6 ipaux iproute netlog nullmedium pktmedium ptclbsum inferno + draw screen + mouse mouse + kbmap + kbin kbd latin1 + uart +# gpio gpio +# spi spi +# i2c i2c + + fakertc +# rtc3231 i2c + ether netif + sd + usb + aoe + +link + archbcm5 +# pci +# gisb + loopbackmedium + ethermedium + sdhc + usbxhci +# etherusb + ether4330 + ethergem ethermii gpio +# pitft + +ip + tcp + udp + ipifc + icmp + icmp6 +# ipmux + +misc + mmu64 + trap + uartpl011 gpiorp1 + sdmmc + sdaoe sdscsi + vcore + vfp3 + +port + int cpuserver = 1; + +boot cpu boot #S/sdM0/ + local + tcp + +bootdir + boot$CONF.out boot + /arm/bin/ip/ipconfig + /arm/bin/auth/factotum + /arm/bin/fossil/fossil + /arm/bin/venti/venti + /arm/bin/usb/usbd --- /sys/src/9/bcm64/screen.c Thu Jan 1 00:00:00 1970 +++ /sys/src/9/bcm64/screen.c Mon Mar 23 09:23:24 2026 @@ -0,0 +1,573 @@ +/* + * bcm2385 framebuffer + */ + +#include "u.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#include + +#define Image IMAGE +#include +#include +#include +#include "screen.h" + +enum { + Tabstop = 4, + Scroll = 8, + Wid = 1024, + Ht = 768, + Depth = 16, +}; + +Cursor arrow = { + { -1, -1 }, + { 0xFF, 0xFF, 0x80, 0x01, 0x80, 0x02, 0x80, 0x0C, + 0x80, 0x10, 0x80, 0x10, 0x80, 0x08, 0x80, 0x04, + 0x80, 0x02, 0x80, 0x01, 0x80, 0x02, 0x8C, 0x04, + 0x92, 0x08, 0x91, 0x10, 0xA0, 0xA0, 0xC0, 0x40, + }, + { 0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFC, 0x7F, 0xF0, + 0x7F, 0xE0, 0x7F, 0xE0, 0x7F, 0xF0, 0x7F, 0xF8, + 0x7F, 0xFC, 0x7F, 0xFE, 0x7F, 0xFC, 0x73, 0xF8, + 0x61, 0xF0, 0x60, 0xE0, 0x40, 0x40, 0x00, 0x00, + }, +}; + +Memimage *gscreen; +Lcd *lcd; + +static Memdata xgdata; + +/*static*/ Memimage xgscreen = +{ + { 0, 0, Wid, Ht }, /* r */ + { 0, 0, Wid, Ht }, /* clipr */ + Depth, /* depth */ + 3, /* nchan */ + RGB16, /* chan */ + nil, /* cmap */ + &xgdata, /* data */ + 0, /* zero */ + 0, /* width in words of a single scan line */ + 0, /* layer */ + 0, /* flags */ +}; + +static Memimage *conscol; +static Memimage *back; +static Memsubfont *memdefont; + +static Lock screenlock; + +static Point curpos; +static int h, w; +static Rectangle window; + +static void myscreenputs(char *s, int n); +static void screenputc(char *buf); +static void screenwin(void); + +/* + * Software cursor. + */ +static int swvisible; /* is the cursor visible? */ +static int swenabled; /* is the cursor supposed to be on the screen? */ +static Memimage *swback; /* screen under cursor */ +static Memimage *swimg; /* cursor image */ +static Memimage *swmask; /* cursor mask */ +static Memimage *swimg1; +static Memimage *swmask1; + +static Point swoffset; +static Rectangle swrect; /* screen rectangle in swback */ +static Point swpt; /* desired cursor location */ +static Point swvispt; /* actual cursor location */ +static int swvers; /* incremented each time cursor image changes */ +static int swvisvers; /* the version on the screen */ + +/* + * called with drawlock locked for us, most of the time. + * kernel prints at inopportune times might mean we don't + * hold the lock, but memimagedraw is now reentrant so + * that should be okay: worst case we get cursor droppings. + */ +static void +swcursorhide(void) +{ + if(swvisible == 0) + return; + if(swback == nil) + return; + swvisible = 0; + memimagedraw(gscreen, swrect, swback, ZP, memopaque, ZP, S); + flushmemscreen(swrect); +} + +static void +swcursoravoid(Rectangle r) +{ + if(swvisible && rectXrect(r, swrect)) + swcursorhide(); +} + +static void +swcursordraw(void) +{ + int dounlock; + + if(swvisible) + return; + if(swenabled == 0) + return; + if(swback == nil || swimg1 == nil || swmask1 == nil) + return; + dounlock = canqlock(&drawlock); + swvispt = swpt; + swvisvers = swvers; + swrect = rectaddpt(Rect(0,0,16,16), swvispt); + memimagedraw(swback, swback->r, gscreen, swpt, memopaque, ZP, S); + memimagedraw(gscreen, swrect, swimg1, ZP, swmask1, ZP, SoverD); + flushmemscreen(swrect); + swvisible = 1; + if(dounlock) + qunlock(&drawlock); +} + +int +cursoron(int dolock) +{ + int retry; + + if (dolock) + lock(&cursor); + if (canqlock(&drawlock)) { + retry = 0; + swcursorhide(); + swcursordraw(); + qunlock(&drawlock); + } else + retry = 1; + if (dolock) + unlock(&cursor); + return retry; +} + +void +cursoroff(int dolock) +{ + if (dolock) + lock(&cursor); + swcursorhide(); + if (dolock) + unlock(&cursor); +} + +static void +swload(Cursor *curs) +{ + uchar *ip, *mp; + int i, j, set, clr; + + if(!swimg || !swmask || !swimg1 || !swmask1) + return; + /* + * Build cursor image and mask. + * Image is just the usual cursor image + * but mask is a transparent alpha mask. + * + * The 16x16x8 memimages do not have + * padding at the end of their scan lines. + */ + ip = byteaddr(swimg, ZP); + mp = byteaddr(swmask, ZP); + for(i=0; i<32; i++){ + set = curs->set[i]; + clr = curs->clr[i]; + for(j=0x80; j; j>>=1){ + *ip++ = set&j ? 0x00 : 0xFF; + *mp++ = (clr|set)&j ? 0xFF : 0x00; + } + } + swoffset = curs->offset; + swvers++; + memimagedraw(swimg1, swimg1->r, swimg, ZP, memopaque, ZP, S); + memimagedraw(swmask1, swmask1->r, swmask, ZP, memopaque, ZP, S); +} + +/* called from devmouse */ +void +setcursor(Cursor* curs) +{ + cursoroff(0); + swload(curs); + cursoron(0); +} + +static int +swmove(Point p) +{ + swpt = addpt(p, swoffset); + return 0; +} + +static void +swcursorclock(void) +{ + int x; + + if(!swenabled) + return; + swmove(mousexy()); + if(swvisible && eqpt(swpt, swvispt) && swvers==swvisvers) + return; + + x = splhi(); + if(swenabled) + if(!swvisible || !eqpt(swpt, swvispt) || swvers!=swvisvers) + if(canqlock(&drawlock)){ + swcursorhide(); + swcursordraw(); + qunlock(&drawlock); + } + splx(x); +} + +void +swcursorinit(void) +{ + static int init; + + if(!init){ + init = 1; + addclock0link(swcursorclock, 10); + swenabled = 1; + } + if(swback){ + freememimage(swback); + freememimage(swmask); + freememimage(swmask1); + freememimage(swimg); + freememimage(swimg1); + } + + swback = allocmemimage(Rect(0,0,32,32), gscreen->chan); + swmask = allocmemimage(Rect(0,0,16,16), GREY8); + swmask1 = allocmemimage(Rect(0,0,16,16), GREY1); + swimg = allocmemimage(Rect(0,0,16,16), GREY8); + swimg1 = allocmemimage(Rect(0,0,16,16), GREY1); + if(swback==nil || swmask==nil || swmask1==nil || swimg==nil || swimg1 == nil){ + print("software cursor: allocmemimage fails\n"); + return; + } + + memfillcolor(swmask, DOpaque); + memfillcolor(swmask1, DOpaque); + memfillcolor(swimg, DBlack); + memfillcolor(swimg1, DBlack); +} + +int +hwdraw(Memdrawparam *par) +{ + Memimage *dst, *src, *mask; + + if((dst=par->dst) == nil || dst->data == nil) + return 0; + if((src=par->src) == nil || src->data == nil) + return 0; + if((mask=par->mask) == nil || mask->data == nil) + return 0; + + if(dst->data->bdata == xgdata.bdata) + swcursoravoid(par->r); + if(src->data->bdata == xgdata.bdata) + swcursoravoid(par->sr); + if(mask->data->bdata == xgdata.bdata) + swcursoravoid(par->mr); + + if(lcd) + lcd->draw(par->r); + + return 0; +} + +static int +screensize(void) +{ + char *p, buf[32]; + char *f[3]; + int width, height, depth; + + p = getconf("vgasize"); + if(p == nil || memccpy(buf, p, '\0', sizeof buf) == nil) + return -1; + + if(getfields(buf, f, nelem(f), 0, "x") != nelem(f) || + (width = atoi(f[0])) < 16 || + (height = atoi(f[1])) <= 0 || + (depth = atoi(f[2])) <= 0) + return -1; + xgscreen.r.max = Pt(width, height); + xgscreen.depth = depth; + return 0; +} + +static int fbchan; +static uintptr fbaddr; +static ulong fbsize; + +void +fbsetup(char *prop, uchar *value, int len) +{ + if(!strcmp(prop, "width") && len == 4) + xgscreen.r.max.x = nhgetl(value); + else if(!strcmp(prop, "height") && len == 4) + xgscreen.r.max.y = nhgetl(value); + else if(!strcmp(prop, "reg") && len == 8){ + fbaddr = nhgetl(value); + fbsize = nhgetl(value+4); + }else if(!strcmp(prop, "format")){ + iprint("framebuffer format %s\n", (char*)value); + if(value[0] == 'a') + /* no alpha channel - change a8r8g8b8 to x8r8g8b8 */ + value[0] = 'x'; + fbchan = strtochan((char*)value); + if(fbchan == RGB24) + fbchan = BGR24; + } +} + +void +screeninit(void) +{ + char *fb; + + if(fbchan == 0 || fbaddr == 0 || fbsize == 0){ + print("missing framebuffer configuration from devicetree\n"); + return; + } + if(memsetchan(&xgscreen, fbchan) == -1){ + print("bad framebuffer format from devicetree\n"); + return; + } + fb = (char*)mmukmap(FRAMEBUFFER, fbaddr, fbsize); + print("screen %d x %d x %d fbinit %#p\n", xgscreen.r.max.x, xgscreen.r.max.y, xgscreen.depth, fb); + memset(fb, 0x55, fbsize); + xgscreen.clipr = xgscreen.r; + conf.monitor = 1; + xgdata.bdata = (uchar*)fb; + xgdata.ref = 1; + gscreen = &xgscreen; + gscreen->width = wordsperline(gscreen->r, gscreen->depth); + + memimageinit(); + memdefont = getmemdefont(); + screenwin(); + screenputs = myscreenputs; +} + +void +flushmemscreen(Rectangle) +{ + cachedwb(); +} + +uchar* +attachscreen(Rectangle *r, ulong *chan, int* d, int *width, int *softscreen) +{ + if(gscreen == nil) + return nil; + *r = gscreen->r; + *d = gscreen->depth; + *chan = gscreen->chan; + *width = gscreen->width; + *softscreen = 0; + + return gscreen->data->bdata; +} + +void +getcolor(ulong p, ulong *pr, ulong *pg, ulong *pb) +{ + USED(p, pr, pg, pb); +} + +int +setcolor(ulong p, ulong r, ulong g, ulong b) +{ + USED(p, r, g, b); + return 0; +} + +void +blankscreen(int blank) +{ + fbblank(blank); + if(lcd) + lcd->blank(blank); +} + +static void +myscreenputs(char *s, int n) +{ + int i; + Rune r; + char buf[4]; + + if(!islo()) { + /* don't deadlock trying to print in interrupt */ + if(!canlock(&screenlock)) + return; + } + else + lock(&screenlock); + + while(n > 0){ + i = chartorune(&r, s); + if(i == 0){ + s++; + --n; + continue; + } + memmove(buf, s, i); + buf[i] = 0; + n -= i; + s += i; + screenputc(buf); + } + unlock(&screenlock); +} + +static void +screenwin(void) +{ + char *greet; + Memimage *orange; + Point p, q; + Rectangle r; + + back = memwhite; + conscol = memblack; + + orange = allocmemimage(Rect(0, 0, 1, 1), RGB16); + orange->flags |= Frepl; + orange->clipr = gscreen->r; + orange->data->bdata[0] = 0x40; /* magic: colour? */ + orange->data->bdata[1] = 0xfd; /* magic: colour? */ + + w = memdefont->info[' '].width; + h = memdefont->height; + + r = insetrect(gscreen->r, 4); + + memimagedraw(gscreen, r, memblack, ZP, memopaque, ZP, S); + window = insetrect(r, 4); + memimagedraw(gscreen, window, memwhite, ZP, memopaque, ZP, S); + + memimagedraw(gscreen, Rect(window.min.x, window.min.y, + window.max.x, window.min.y + h + 5 + 6), orange, ZP, nil, ZP, S); + freememimage(orange); + window = insetrect(window, 5); + + greet = " Plan 9 Console "; + p = addpt(window.min, Pt(10, 0)); + q = memsubfontwidth(memdefont, greet); + memimagestring(gscreen, p, conscol, ZP, memdefont, greet); + flushmemscreen(r); + window.min.y += h + 6; + curpos = window.min; + window.max.y = window.min.y + ((window.max.y - window.min.y) / h) * h; +} + +static void +scroll(void) +{ + int o; + Point p; + Rectangle r; + + o = Scroll*h; + r = Rpt(window.min, Pt(window.max.x, window.max.y-o)); + p = Pt(window.min.x, window.min.y+o); + memimagedraw(gscreen, r, gscreen, p, nil, p, S); + flushmemscreen(r); + if(lcd) + lcd->draw(r); + r = Rpt(Pt(window.min.x, window.max.y-o), window.max); + memimagedraw(gscreen, r, back, ZP, nil, ZP, S); + flushmemscreen(r); + if(lcd) + lcd->draw(r); + + curpos.y -= o; +} + +static void +screenputc(char *buf) +{ + int w; + uint pos; + Point p; + Rectangle r; + static int *xp; + static int xbuf[256]; + + if (xp < xbuf || xp >= &xbuf[nelem(xbuf)]) + xp = xbuf; + + switch (buf[0]) { + case '\n': + if (curpos.y + h >= window.max.y) + scroll(); + curpos.y += h; + screenputc("\r"); + break; + case '\r': + xp = xbuf; + curpos.x = window.min.x; + break; + case '\t': + p = memsubfontwidth(memdefont, " "); + w = p.x; + if (curpos.x >= window.max.x - Tabstop * w) + screenputc("\n"); + + pos = (curpos.x - window.min.x) / w; + pos = Tabstop - pos % Tabstop; + *xp++ = curpos.x; + r = Rect(curpos.x, curpos.y, curpos.x + pos * w, curpos.y + h); + memimagedraw(gscreen, r, back, back->r.min, nil, back->r.min, S); + flushmemscreen(r); + curpos.x += pos * w; + break; + case '\b': + if (xp <= xbuf) + break; + xp--; + r = Rect(*xp, curpos.y, curpos.x, curpos.y + h); + memimagedraw(gscreen, r, back, back->r.min, nil, back->r.min, S); + flushmemscreen(r); + curpos.x = *xp; + break; + case '\0': + break; + default: + p = memsubfontwidth(memdefont, buf); + w = p.x; + + if (curpos.x >= window.max.x - w) + screenputc("\n"); + + *xp++ = curpos.x; + r = Rect(curpos.x, curpos.y, curpos.x + w, curpos.y + h); + memimagedraw(gscreen, r, back, back->r.min, nil, back->r.min, S); + memimagestring(gscreen, curpos, conscol, ZP, memdefont, buf); + flushmemscreen(r); + curpos.x += w; + break; + } +} --- /sys/src/9/bcm64/screen.h Thu Jan 1 00:00:00 1970 +++ /sys/src/9/bcm64/screen.h Tue Mar 24 21:17:05 2026 @@ -0,0 +1 @@ +#include "../bcm/screen.h" --- /sys/src/9/bcm64/sdhc.c Thu Jan 1 00:00:00 1970 +++ /sys/src/9/bcm64/sdhc.c Tue Mar 10 15:19:19 2026 @@ -0,0 +1,680 @@ +/* + * bcm2711-2712 sd host controller + * + * Copyright © 2012,2019 Richard Miller + * + * adapted from emmc.c + */ + +#include "u.h" +#include "../port/lib.h" +#include "../port/error.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#include "io.h" +#include "../port/sd.h" + +extern SDio *sdcardlink, *sdwifilink; +#define EMMC1REGS (VIRTIO+0x00fff000) +#define EMMC2REGS (VIRTIO+0x01100000) + +#define okay(n) + +enum { + Extfreq = 200*Mhz, /* guess external clock frequency */ + Initfreq = 400000, /* initialisation frequency for MMC */ + SDfreq = 25*Mhz, /* standard SD frequency */ + SDfreqhs = 50*Mhz, /* high speed frequency */ + DTO = 14, /* data timeout exponent (guesswork) */ + + GoIdle = 0, /* mmc/sdio go idle state */ + MMCSelect = 7, /* mmc/sd card select command */ + Setbuswidth = 6, /* mmc/sd set bus width command */ + Switchfunc = 6, /* mmc/sd switch function command */ + Voltageswitch = 11, /* md/sdio switch to 1.8V */ + IORWdirect = 52, /* sdio read/write direct command */ + IORWextended = 53, /* sdio read/write extended command */ + Appcmd = 55, /* mmc/sd application command prefix */ +}; + +enum { + /* Controller registers */ + SDMAaddr = 0x00>>2, + Blksizecnt = 0x04>>2, + Arg1 = 0x08>>2, + Cmdtm = 0x0c>>2, + Resp0 = 0x10>>2, + Resp1 = 0x14>>2, + Resp2 = 0x18>>2, + Resp3 = 0x1c>>2, + Data = 0x20>>2, + Status = 0x24>>2, + Control0 = 0x28>>2, + Control1 = 0x2c>>2, + Interrupt = 0x30>>2, + Irptmask = 0x34>>2, + Irpten = 0x38>>2, + Control2 = 0x3c>>2, + Capability = 0x40>>2, + Forceirpt = 0x50>>2, + Dmadesc = 0x58>>2, + Boottimeout = 0x70>>2, + Dbgsel = 0x74>>2, + Exrdfifocfg = 0x80>>2, + Exrdfifoen = 0x84>>2, + Tunestep = 0x88>>2, + Tunestepsstd = 0x8c>>2, + Tunestepsddr = 0x90>>2, + Spiintspt = 0xf0>>2, + Slotisrver = 0xfc>>2, + + /* Control0 */ + Busvoltage = 7<<9, + V1_8 = 5<<9, + V3_0 = 6<<9, + V3_3 = 7<<9, + Buspower = 1<<8, + Dwidth8 = 1<<5, + Dmaselect = 3<<3, + DmaSDMA = 0<<3, + DmaADMA1 = 1<<3, + DmaADMA2 = 2<<3, + Hispeed = 1<<2, + Dwidth4 = 1<<1, + Dwidth1 = 0<<1, + LED = 1<<0, + + /* Control1 */ + Srstdata = 1<<26, /* reset data circuit */ + Srstcmd = 1<<25, /* reset command circuit */ + Srsthc = 1<<24, /* reset complete host controller */ + Datatoshift = 16, /* data timeout unit exponent */ + Datatomask = 0xF0000, + Clkfreq8shift = 8, /* SD clock base divider LSBs */ + Clkfreq8mask = 0xFF00, + Clkfreqms2shift = 6, /* SD clock base divider MSBs */ + Clkfreqms2mask = 0xC0, + Clkgendiv = 0<<5, /* SD clock divided */ + Clkgenprog = 1<<5, /* SD clock programmable */ + Clken = 1<<2, /* SD clock enable */ + Clkstable = 1<<1, + Clkintlen = 1<<0, /* enable internal EMMC clocks */ + + /* Cmdtm */ + Indexshift = 24, + Suspend = 1<<22, + Resume = 2<<22, + Abort = 3<<22, + Isdata = 1<<21, + Ixchken = 1<<20, + Crcchken = 1<<19, + Respmask = 3<<16, + Respnone = 0<<16, + Resp136 = 1<<16, + Resp48 = 2<<16, + Resp48busy = 3<<16, + Multiblock = 1<<5, + Host2card = 0<<4, + Card2host = 1<<4, + Autocmd12 = 1<<2, + Autocmd23 = 2<<2, + Blkcnten = 1<<1, + Dmaen = 1<<0, + + /* Interrupt */ + Admaerr = 1<<25, + Acmderr = 1<<24, + Denderr = 1<<22, + Dcrcerr = 1<<21, + Dtoerr = 1<<20, + Cbaderr = 1<<19, + Cenderr = 1<<18, + Ccrcerr = 1<<17, + Ctoerr = 1<<16, + Err = 1<<15, + Cardintr = 1<<8, + Cardinsert = 1<<6, /* not in Broadcom datasheet */ + Readrdy = 1<<5, + Writerdy = 1<<4, + Dmaintr = 1<<3, + Datadone = 1<<1, + Cmddone = 1<<0, + + /* Status */ + Bufread = 1<<11, /* not in Broadcom datasheet */ + Bufwrite = 1<<10, /* not in Broadcom datasheet */ + Readtrans = 1<<9, + Writetrans = 1<<8, + Datactive = 1<<2, + Datinhibit = 1<<1, + Cmdinhibit = 1<<0, +}; + +static int cmdinfo[64] = { +[0] Ixchken, +[2] Resp136, +[3] Resp48 | Ixchken | Crcchken, +[5] Resp48, +[6] Resp48 | Ixchken | Crcchken, +[7] Resp48busy | Ixchken | Crcchken, +[8] Resp48 | Ixchken | Crcchken, +[9] Resp136, +[11] Resp48 | Ixchken | Crcchken, +[12] Resp48busy | Ixchken | Crcchken, +[13] Resp48 | Ixchken | Crcchken, +[16] Resp48, +[17] Resp48 | Isdata | Card2host | Ixchken | Crcchken, +[18] Resp48 | Isdata | Card2host | Multiblock | Blkcnten | Ixchken | Crcchken, +[24] Resp48 | Isdata | Host2card | Ixchken | Crcchken, +[25] Resp48 | Isdata | Host2card | Multiblock | Blkcnten | Ixchken | Crcchken, +[41] Resp48, +[52] Resp48 | Ixchken | Crcchken, +[53] Resp48 | Ixchken | Crcchken | Isdata, +[55] Resp48 | Ixchken | Crcchken, +}; + +typedef struct Adma Adma; +typedef struct Ctlr Ctlr; + +/* + * ADMA2 descriptor + * See SD Host Controller Simplified Specification Version 2.00 + */ + +struct Adma { + u32int desc; + u32int addr; +}; + +enum { + /* desc fields */ + Valid = 1<<0, + End = 1<<1, + Int = 1<<2, + Nop = 0<<4, + Tran = 2<<4, + Link = 3<<4, + OLength = 16, + /* maximum value for Length field */ + Maxdma = ((1<<16) - 4), +}; + +struct Ctlr { + u32int *regs; + int irq; + Rendez r; + Rendez cardr; + int fastclock; + ulong extclk; + int appcmd; + Adma *dma; +}; + +static Ctlr emmc1 = { + (u32int*)EMMC1REGS, + IRQmmc, +}; +static Ctlr emmc2 = { + (u32int*)EMMC2REGS, + IRQsdhci, +}; + +static void mmcinterrupt(Ureg*, void*); + +#define WR(reg, val) { if(0)print("WR %2.2ux %ux\n", reg<<2, val); r[reg] = val; } + +static uint +clkdiv(uint d) +{ + uint v; + + assert(d < 1<<10); + v = (d << Clkfreq8shift) & Clkfreq8mask; + v |= ((d >> 8) << Clkfreqms2shift) & Clkfreqms2mask; + return v; +} + +static Adma* +dmaalloc(void *addr, int len) +{ + int n; + uintptr a; + Adma *adma, *p; + + a = (uintptr)addr; + n = HOWMANY(len, Maxdma); + adma = sdmalloc(n * sizeof(Adma)); + for(p = adma; len > 0; p++){ + p->desc = Valid | Tran; + if(n == 1) + p->desc |= len<desc |= Maxdma<addr = (u32int)dmaaddr((void*)a); + a += Maxdma; + len -= Maxdma; + n--; + } + cachedwbse(adma, (char*)p - (char*)adma); + return adma; +} + +static void +emmcclk(Ctlr *ctl, uint freq) +{ + u32int *r; + uint div; + int i; + + r = ctl->regs; + div = ctl->extclk / (freq<<1); + if(ctl->extclk / (div<<1) > freq) + div++; + WR(Control1, clkdiv(div) | + DTO<extclk = clk; + r = ctl->regs; + if(0)print("sdhc init: control %8.8ux %8.8ux %8.8ux\n", + r[Control0], r[Control1], r[Control2]); + WR(Control1, Srsthc); + delay(10); + while(r[Control1] & Srsthc) + ; + WR(Control1, Srstdata); + delay(10); + WR(Control1, 0); + return 0; +} + +static int +sdhcinquiry(Ctlr *ctl, char *inquiry, int inqlen) +{ + u32int *r; + uint ver; + + r = ctl->regs; + ver = r[Slotisrver] >> 16; + return snprint(inquiry, inqlen, + "BCM SD Host Controller %2.2x Version %2.2x", + ver&0xFF, ver>>8); +} + +static void +sdhcenable(Ctlr *ctl) +{ + u32int *r; + + r = ctl->regs; + WR(Control0, 0); + delay(1); + WR(Control0, V3_3 | Buspower | Dwidth1 | DmaADMA2); + WR(Control1, 0); + delay(1); + emmcclk(ctl, Initfreq); + WR(Irpten, 0); + WR(Irptmask, ~Dmaintr); + WR(Interrupt, ~0); + intrenable(ctl->irq, mmcinterrupt, ctl, 0, "sdhci"); +} + +int +sdwificardintr(int wait) +{ + u32int *r; + int i; + Ctlr *ctl; + + ctl = &emmc2; + r = ctl->regs; + while(((i = r[Interrupt]) & Cardintr) == 0){ + if(!wait) + return 0; + WR(Irptmask, r[Irptmask] | Cardintr); + WR(Irpten, r[Irpten] | Cardintr); + sleep(&ctl->cardr, cardintready, r); + } + WR(Irptmask, r[Irptmask] & ~Cardintr); + return i; +} + +static int +sdhccmd(Ctlr *ctl, u32int cmd, u32int arg, u32int *resp) +{ + u32int *r; + u32int c; + int i; + ulong now; + + r = ctl->regs; + assert(cmd < nelem(cmdinfo) && cmdinfo[cmd] != 0); + c = (cmd << Indexshift) | cmdinfo[cmd]; + /* + * CMD6 may be Setbuswidth or Switchfunc depending on Appcmd prefix + */ + if(cmd == Switchfunc && !ctl->appcmd) + c |= Isdata|Card2host; + if(c & Isdata) + c |= Dmaen; + if(cmd == IORWextended){ + if(arg & (1<<31)) + c |= Host2card; + else + c |= Card2host; + if((r[Blksizecnt]&0xFFFF0000) != 0x10000) + c |= Multiblock | Blkcnten; + } + /* + * GoIdle indicates new card insertion: reset bus width & speed + */ + if(cmd == GoIdle){ + WR(Control0, r[Control0] & ~(Dwidth4|Hispeed)); + emmcclk(ctl, Initfreq); + } + if(r[Status] & Cmdinhibit){ + print("sdhc: need to reset Cmdinhibit intr %ux stat %ux\n", + r[Interrupt], r[Status]); + WR(Control1, r[Control1] | Srstcmd); + while(r[Control1] & Srstcmd) + ; + while(r[Status] & Cmdinhibit) + ; + } + if((r[Status] & Datinhibit) && + ((c & Isdata) || (c & Respmask) == Resp48busy)){ + print("sdhc: need to reset Datinhibit intr %ux stat %ux\n", + r[Interrupt], r[Status]); + WR(Control1, r[Control1] | Srstdata); + while(r[Control1] & Srstdata) + ; + while(r[Status] & Datinhibit) + ; + } + WR(Arg1, arg); + if((i = (r[Interrupt] & ~Cardintr)) != 0){ + if(i != Cardinsert) + print("sdhc: before command, intr was %ux\n", i); + WR(Interrupt, i); + } + WR(Cmdtm, c); + now = m->ticks; + while(((i=r[Interrupt])&(Cmddone|Err)) == 0) + if(m->ticks-now > HZ) + break; + if((i&(Cmddone|Err)) != Cmddone){ + if((i&~(Err|Cardintr)) != Ctoerr) + print("sdhc: cmd %ux arg %ux error intr %ux stat %ux\n", c, arg, i, r[Status]); + WR(Interrupt, i); + if(r[Status]&Cmdinhibit){ + WR(Control1, r[Control1]|Srstcmd); + while(r[Control1]&Srstcmd) + ; + } + error(Eio); + } + WR(Interrupt, i & ~(Datadone|Readrdy|Writerdy)); + switch(c & Respmask){ + case Resp136: + resp[0] = r[Resp0]<<8; + resp[1] = r[Resp0]>>24 | r[Resp1]<<8; + resp[2] = r[Resp1]>>24 | r[Resp2]<<8; + resp[3] = r[Resp2]>>24 | r[Resp3]<<8; + break; + case Resp48: + case Resp48busy: + resp[0] = r[Resp0]; + break; + case Respnone: + resp[0] = 0; + break; + } + if((c & Respmask) == Resp48busy){ + WR(Irpten, r[Irpten]|Datadone|Err); + tsleep(&ctl->r, datadone, r, 3000); + i = r[Interrupt]; + if((i & Datadone) == 0) + print("sdhc: no Datadone after CMD%d\n", cmd); + if(i & Err) + print("sdhc: CMD%d error interrupt %ux\n", + cmd, r[Interrupt]); + WR(Interrupt, i); + } + /* + * Once card is selected, use faster clock + */ + if(cmd == MMCSelect){ + delay(1); + emmcclk(ctl, SDfreq); + delay(1); + ctl->fastclock = 1; + } + if(cmd == Setbuswidth){ + if(ctl->appcmd){ + /* + * If card bus width changes, change host bus width + */ + switch(arg){ + case 0: + WR(Control0, r[Control0] & ~Dwidth4); + break; + case 2: + WR(Control0, r[Control0] | Dwidth4); + break; + } + }else{ + /* + * If card switched into high speed mode, increase clock speed + */ + if((arg&0x8000000F) == 0x80000001){ + delay(1); + emmcclk(ctl, SDfreqhs); + delay(1); + } + } + }else if(cmd == IORWdirect && (arg & ~0xFF) == (1<<31|0<<28|7<<9)){ + switch(arg & 0x3){ + case 0: + WR(Control0, r[Control0] & ~Dwidth4); + break; + case 2: + WR(Control0, r[Control0] | Dwidth4); + //WR(Control0, r[Control0] | Hispeed); + break; + } + } + ctl->appcmd = (cmd == Appcmd); + return 0; +} + +static void +sdhciosetup(Ctlr *ctl, int write, void *buf, int bsize, int bcount) +{ + u32int *r; + int len; + + r = ctl->regs; + len = bsize * bcount; + assert(((uintptr)buf&3) == 0); + assert((len&3) == 0); + assert(bsize <= 2048); + WR(Blksizecnt, bcount<<16 | bsize); + if(ctl->dma) + sdfree(ctl->dma); + ctl->dma = dmaalloc(buf, len); + if(write) + cachedwbse(buf, len); + else + cachedwbinvse(buf, len); + WR(Dmadesc, (u32int)dmaaddr(ctl->dma)); + okay(1); +} + +static void +sdhcio(Ctlr *ctl, int write, uchar *buf, int len) +{ + u32int *r; + int i; + + r = ctl->regs; + if(waserror()){ + okay(0); + nexterror(); + } + WR(Irpten, r[Irpten] | Datadone|Err); + tsleep(&ctl->r, datadone, r, 3000); + WR(Irpten, r[Irpten] & ~(Datadone|Err)); + i = r[Interrupt]; + if((i & (Datadone|Err)) != Datadone){ + print("sdhc: %s error intr %ux stat %ux\n", + write? "write" : "read", i, r[Status]); + if(r[Status] & Datinhibit) + WR(Control1, r[Control1] | Srstdata); + while(r[Control1] & Srstdata) + ; + while(r[Status] & Datinhibit) + ; + WR(Interrupt, i); + error(Eio); + } + WR(Interrupt, i); + if(!write) + cachedinvse(buf, len); + poperror(); + okay(0); +} + +static void +mmcinterrupt(Ureg*, void *a) +{ + Ctlr *ctl; + u32int *r; + int i; + + ctl = a; + r = ctl->regs; + i = r[Interrupt]; + if(i&(Datadone|Err)) + wakeup(&ctl->r); + if(i&Cardintr) + wakeup(&ctl->cardr); + WR(Irpten, r[Irpten] & ~i); +} + +static int +emmcinit1(void) +{ + return sdhcinit(&emmc1); +} +static void +emmcenable1(void) +{ + sdhcenable(&emmc1); +} +static int +emmcinquiry1(char *inquiry, int inqlen) +{ + return sdhcinquiry(&emmc1, inquiry, inqlen); +} +static int +emmccmd1(u32int cmd, u32int arg, u32int *resp) +{ + return sdhccmd(&emmc1, cmd, arg, resp); +} +static void +emmciosetup1(int write, void *buf, int bsize, int bcount) +{ + sdhciosetup(&emmc1, write, buf, bsize, bcount); +} +static void +emmcio1(int write, uchar *buf, int len) +{ + sdhcio(&emmc1, write, buf, len); +} + +static int +emmcinit2(void) +{ + return sdhcinit(&emmc2); +} +static void +emmcenable2(void) +{ + sdhcenable(&emmc2); +} +static int +emmcinquiry2(char *inquiry, int inqlen) +{ + return sdhcinquiry(&emmc2, inquiry, inqlen); +} +static int +emmccmd2(u32int cmd, u32int arg, u32int *resp) +{ + return sdhccmd(&emmc2, cmd, arg, resp); +} +static void +emmciosetup2(int write, void *buf, int bsize, int bcount) +{ + sdhciosetup(&emmc2, write, buf, bsize, bcount); +} +static void +emmcio2(int write, uchar *buf, int len) +{ + sdhcio(&emmc2, write, buf, len); +} + +SDio sdiohci[2] = { + { "sdio1", emmcinit1, emmcenable1, emmcinquiry1, emmccmd1, emmciosetup1, emmcio1, }, + { "sdio2", emmcinit2, emmcenable2, emmcinquiry2, emmccmd2, emmciosetup2, emmcio2, }, +}; + +void +sdhclink(void) +{ + sdcardlink = &sdiohci[0]; + sdwifilink = &sdiohci[1]; +} --- /sys/src/9/bcm64/sdmmc.c Thu Jan 1 00:00:00 1970 +++ /sys/src/9/bcm64/sdmmc.c Mon Mar 9 19:55:26 2026 @@ -0,0 +1,357 @@ +/* + * mmc / sd memory card + * + * Copyright © 2012 Richard Miller + * + * Assumes only one card on the bus + */ + +#include "u.h" +#include "../port/lib.h" +#include "../port/error.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#include "io.h" + +#include "../port/sd.h" + +#define CSD(end, start) rbits(csd, start, (end)-(start)+1) + +typedef struct Ctlr Ctlr; + +enum { + Inittimeout = 15, + Multiblock = 1, + + /* Commands */ + GO_IDLE_STATE = 0, + ALL_SEND_CID = 2, + SEND_RELATIVE_ADDR= 3, + SWITCH_FUNC = 6, + SELECT_CARD = 7, + SD_SEND_IF_COND = 8, + SEND_CSD = 9, + STOP_TRANSMISSION= 12, + SEND_STATUS = 13, + SET_BLOCKLEN = 16, + READ_SINGLE_BLOCK= 17, + READ_MULTIPLE_BLOCK= 18, + WRITE_BLOCK = 24, + WRITE_MULTIPLE_BLOCK= 25, + APP_CMD = 55, /* prefix for following app-specific commands */ + SET_BUS_WIDTH = 6, + SD_SEND_OP_COND = 41, + + /* Command arguments */ + /* SD_SEND_IF_COND */ + Voltage = 1<<8, + Checkpattern = 0x42, + + /* SELECT_CARD */ + Rcashift = 16, + + /* SD_SEND_OP_COND */ + Hcs = 1<<30, /* host supports SDHC & SDXC */ + Ccs = 1<<30, /* card is SDHC or SDXC */ + V3_3 = 3<<20, /* 3.2-3.4 volts */ + + /* SET_BUS_WIDTH */ + Width1 = 0<<0, + Width4 = 2<<0, + + /* SWITCH_FUNC */ + Dfltspeed = 0<<0, + Hispeed = 1<<0, + Checkfunc = 0x00FFFFF0, + Setfunc = 0x80FFFFF0, + Funcbytes = 64, + + /* OCR (operating conditions register) */ + Powerup = 1<<31, +}; + +struct Ctlr { + SDev *dev; + SDio *io; + /* SD card registers */ + u16int rca; + u32int ocr; + u32int cid[4]; + u32int csd[4]; +}; + +SDio *sdcardlink; + +extern SDifc sdmmcifc; + +static uint +rbits(u32int *p, uint start, uint len) +{ + uint w, off, v; + + w = start / 32; + off = start % 32; + if(off == 0) + v = p[w]; + else + v = p[w] >> off | p[w+1] << (32-off); + if(len < 32) + return v & ((1<secsize = 1 << CSD(83, 80); + switch(CSD(127, 126)){ + case 0: /* CSD version 1 */ + csize = CSD(73, 62); + mult = CSD(49, 47); + unit->sectors = (csize+1) * (1<<(mult+2)); + break; + case 1: /* CSD version 2 */ + csize = CSD(69, 48); + unit->sectors = (csize+1) * 512LL*KiB / unit->secsize; + break; + } + if(unit->secsize == 1024){ + unit->sectors <<= 1; + unit->secsize = 512; + } +} + +static SDev* +mmcpnp(void) +{ + SDev *sdev; + Ctlr *ctl; + + if(sdcardlink == nil){ + print("mmcpnp: sdcardlink is nil\n"); + return nil; + } + if(sdcardlink->init() < 0) + return nil; + sdev = malloc(sizeof(SDev)); + if(sdev == nil) + return nil; + ctl = malloc(sizeof(Ctlr)); + if(ctl == nil){ + free(sdev); + return nil; + } + sdev->idno = 'M'; + sdev->ifc = &sdmmcifc; + sdev->nunit = 1; + sdev->ctlr = ctl; + ctl->dev = sdev; + ctl->io = sdcardlink; + return sdev; +} + +static int +mmcverify(SDunit *unit) +{ + int n; + Ctlr *ctl; + + ctl = unit->dev->ctlr; + n = ctl->io->inquiry((char*)&unit->inquiry[8], sizeof(unit->inquiry)-8); + if(n < 0) + return 0; + unit->inquiry[0] = SDperdisk; + unit->inquiry[1] = SDinq1removable; + unit->inquiry[4] = sizeof(unit->inquiry)-4; + return 1; +} + +static int +mmcenable(SDev* dev) +{ + Ctlr *ctl; + + ctl = dev->ctlr; + ctl->io->enable(); + return 1; +} + +static void +mmcswitchfunc(SDio *io, int arg) +{ + uchar *buf; + int n; + u32int r[4]; + + n = Funcbytes; + buf = sdmalloc(n); + if(waserror()){ + print("mmcswitchfunc error\n"); + sdfree(buf); + nexterror(); + } + io->iosetup(0, buf, n, 1); + io->cmd(SWITCH_FUNC, arg, r); + io->io(0, buf, n); + sdfree(buf); + poperror(); +} + +static int +mmconline(SDunit *unit) +{ + int hcs, i; + u32int r[4]; + Ctlr *ctl; + SDio *io; + + ctl = unit->dev->ctlr; + io = ctl->io; + assert(unit->subno == 0); + + if(waserror()){ + unit->sectors = 0; + return 0; + } + if(unit->sectors != 0){ + io->cmd(SEND_STATUS, ctl->rca<sectors = 0; + poperror(); + return 2; + } + io->cmd(GO_IDLE_STATE, 0, r); + hcs = 0; + if(!waserror()){ + io->cmd(SD_SEND_IF_COND, Voltage|Checkpattern, r); + if(r[0] == (Voltage|Checkpattern)) /* SD 2.0 or above */ + hcs = Hcs; + poperror(); + } + for(i = 0; i < Inittimeout; i++){ + delay(100); + io->cmd(APP_CMD, 0, r); + io->cmd(SD_SEND_OP_COND, hcs|V3_3, r); + if(r[0] & Powerup) + break; + } + if(i == Inittimeout){ + print("sdmmc: card won't power up\n"); + error(Eio); + } + poperror(); + ctl->ocr = r[0]; + io->cmd(ALL_SEND_CID, 0, r); + memmove(ctl->cid, r, sizeof ctl->cid); + io->cmd(SEND_RELATIVE_ADDR, 0, r); + ctl->rca = r[0]>>16; + io->cmd(SEND_CSD, ctl->rca<csd, r, sizeof ctl->csd); + identify(unit, ctl->csd); + io->cmd(SELECT_CARD, ctl->rca<cmd(SET_BLOCKLEN, unit->secsize, r); + io->cmd(APP_CMD, ctl->rca<cmd(SET_BUS_WIDTH, Width4, r); + if(!waserror()){ + mmcswitchfunc(io, Hispeed|Setfunc); + poperror(); + } + poperror(); + return 1; +} + +static int +mmcrctl(SDunit *unit, char *p, int l) +{ + Ctlr *ctl; + int i, n; + + ctl = unit->dev->ctlr; + assert(unit->subno == 0); + if(unit->sectors == 0){ + mmconline(unit); + if(unit->sectors == 0) + return 0; + } + n = snprint(p, l, "rca %4.4ux ocr %8.8ux\ncid ", ctl->rca, ctl->ocr); + for(i = nelem(ctl->cid)-1; i >= 0; i--) + n += snprint(p+n, l-n, "%8.8ux", ctl->cid[i]); + n += snprint(p+n, l-n, " csd "); + for(i = nelem(ctl->csd)-1; i >= 0; i--) + n += snprint(p+n, l-n, "%8.8ux", ctl->csd[i]); + n += snprint(p+n, l-n, "\ngeometry %llud %ld %lld 255 63\n", + unit->sectors, unit->secsize, unit->sectors / (255*63)); + return n; +} + +static long +mmcbio(SDunit *unit, int lun, int write, void *data, long nb, uvlong bno) +{ + int len, tries; + ulong b; + u32int r[4]; + uchar *buf; + Ctlr *ctl; + SDio *io; + + USED(lun); + ctl = unit->dev->ctlr; + io = ctl->io; + assert(unit->subno == 0); + if(unit->sectors == 0) + error("media change"); + buf = data; + len = unit->secsize; + if(Multiblock){ + b = bno; + tries = 0; + while(waserror()) + if(++tries == 3) + nexterror(); + io->iosetup(write, buf, len, nb); + if(waserror()){ + io->cmd(STOP_TRANSMISSION, 0, r); + nexterror(); + } + io->cmd(write? WRITE_MULTIPLE_BLOCK: READ_MULTIPLE_BLOCK, + ctl->ocr & Ccs? b: b * len, r); + io->io(write, buf, nb * len); + poperror(); + io->cmd(STOP_TRANSMISSION, 0, r); + poperror(); + b += nb; + }else{ + for(b = bno; b < bno + nb; b++){ + io->iosetup(write, buf, len, 1); + io->cmd(write? WRITE_BLOCK : READ_SINGLE_BLOCK, + ctl->ocr & Ccs? b: b * len, r); + io->io(write, buf, len); + buf += len; + } + } + return (b - bno) * len; +} + +static int +mmcrio(SDreq*) +{ + return -1; +} + +SDifc sdmmcifc = { + .name = "mmc", + .pnp = mmcpnp, + .enable = mmcenable, + .verify = mmcverify, + .online = mmconline, + .rctl = mmcrctl, + .bio = mmcbio, + .rio = mmcrio, +}; --- /sys/src/9/bcm64/syscall.c Thu Jan 1 00:00:00 1970 +++ /sys/src/9/bcm64/syscall.c Tue Mar 24 19:46:38 2026 @@ -0,0 +1,440 @@ +#include "u.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#include "../port/error.h" +#include "../port/systab.h" + +#include "tos.h" +#include "../port/tos32.h" +#include "ureg.h" + +#include "../bcm/arm.h" + +#include "ureg32.h" + +typedef struct { + uintptr ip; + Ureg* arg0; + char* arg1; + char msg[ERRMAX]; + Ureg* old; + Ureg ureg; +} NFrame; + +typedef struct { + ulong ip; + ulong arg0; + ulong arg1; + char msg[ERRMAX]; + Ureg* old; + Ureg32 ureg; +} NFrame32; + +/* + * Return user to state before notify() + */ +static void +noted(Ureg* cur, uintptr arg0) +{ + union { + NFrame nf; + NFrame32 nf32; + } *nf; + int fsize; + Ureg *nur; + Ureg32 *nur32; + u64int oldpsr; + + qlock(&up->debug); + if(arg0 != NRSTR && !up->notified){ + qunlock(&up->debug); + pprint("call to noted() when not notified\n"); + pexit("Suicide", 0); + } + up->notified = 0; + fpunoted(); + + nf = up->ureg; + if(up->compat32) + fsize = sizeof(NFrame32); + else + fsize = sizeof(NFrame); + + /* sanity clause */ + if(!okaddr(PTR2UINT(nf), fsize, 0)){ + qunlock(&up->debug); + pprint("bad ureg in noted %#p\n", nf); + pexit("Suicide", 0); + } + + /* don't let user change system flags */ + if(!up->compat32){ + nur = &nf->nf.ureg; + nur->psr &= PsrMask|PsrDfiq|PsrDirq; + nur->psr |= (cur->psr & ~(PsrMask|PsrDfiq|PsrDirq)); + memmove(cur, nur, sizeof(Ureg)); + }else{ + nur32 = &nf->nf32.ureg; + oldpsr = cur->psr; + ureg32to64(cur, nur32); + cur->psr &= PsrMask|PsrDfiq|PsrDirq; + cur->psr |= (oldpsr & ~(PsrMask|PsrDfiq|PsrDirq)); + } + + switch((int)arg0){ + case NCONT: + case NRSTR: + if(!okaddr(cur->pc, BY2WD, 0) || + (up->compat32 ? !okaddr(cur->r13, BY2WD, 0) : !okaddr(cur->sp, sizeof(uintptr), 0))){ + qunlock(&up->debug); + pprint("suicide: trap in noted\n"); + pexit("Suicide", 0); + } + up->ureg = up->compat32? nf->nf32.old : nf->nf.old; + qunlock(&up->debug); + break; + case NSAVE: + if(!okaddr(cur->pc, BY2WD, 0) || + (up->compat32 ? !okaddr(cur->r13, BY2WD, 0) : !okaddr(cur->sp, sizeof(uintptr), 0))){ + qunlock(&up->debug); + pprint("suicide: trap in noted\n"); + pexit("Suicide", 0); + } + qunlock(&up->debug); + + splhi(); + if(up->compat32){ + nf->nf32.arg1 = PTR2UINT(nf->nf32.msg); + nf->nf32.arg0 = PTR2UINT(&nf->nf32.ureg); + nf->nf32.ip = 0; + cur->r13 = PTR2UINT(nf); + cur->r0 = PTR2UINT(nf->nf32.arg0); + }else{ + nf->nf.arg1 = nf->nf.msg; + nf->nf.arg0 = &nf->nf.ureg; + nf->nf.ip = 0; + cur->sp = PTR2UINT(nf); + cur->r0 = PTR2UINT(nf->nf.arg0); + } + break; + default: + pprint("unknown noted arg %#p\n", arg0); + up->lastnote.flag = NDebug; + /*FALLTHROUGH*/ + case NDFLT: + if(up->lastnote.flag == NDebug){ + qunlock(&up->debug); + pprint("suicide: %s\n", up->lastnote.msg); + } + else + qunlock(&up->debug); + pexit(up->lastnote.msg, up->lastnote.flag != NDebug); + } +} + +/* + * Call user, if necessary, with note. + * Pass user the Ureg struct and the note on his stack. + */ +int +notify(Ureg* ureg) +{ + int l, fsize; + Note *n; + u32int s; + uintptr sp; + union { + NFrame nf; + NFrame32 nf32; + } *nf; + + if(up->procctl) + procctl(up); + if(up->nnote == 0) + return 0; + + fpunotify(ureg); + + s = spllo(); + qlock(&up->debug); + + up->notepending = 0; + n = &up->note[0]; + if(strncmp(n->msg, "sys:", 4) == 0){ + l = strlen(n->msg); + if(l > ERRMAX-23) /* " pc=0x0123456789abcdef\0" */ + l = ERRMAX-23; + snprint(n->msg + l, sizeof n->msg - l, " pc=%#p", ureg->pc); + } + + if(n->flag != NUser && (up->notified || up->notify == 0)){ + if(n->flag == NDebug) + pprint("suicide: %s\n", n->msg); + qunlock(&up->debug); + pexit(n->msg, n->flag != NDebug); + } + + if(up->notified){ + qunlock(&up->debug); + splhi(); + return 0; + } + + if(up->notify == nil){ + qunlock(&up->debug); + pexit(n->msg, n->flag != NDebug); + } + if(!okaddr(PTR2UINT(up->notify), 1, 0)){ + pprint("suicide: notify function address %#p\n", up->notify); + qunlock(&up->debug); + pexit("Suicide", 0); + } + + if(up->compat32){ + fsize = sizeof(NFrame32); + sp = ureg->r13 - fsize; + }else{ + fsize = sizeof(NFrame); + sp = ureg->sp - fsize; + } + if(!okaddr(sp, fsize, 1)){ + qunlock(&up->debug); + pprint("suicide: notify stack address %#p\n", sp); + pexit("Suicide", 0); + } + + nf = UINT2PTR(sp); + if(up->compat32){ + ureg64to32(&nf->nf32.ureg, ureg); + nf->nf32.old = up->ureg; + up->ureg = nf; + memmove(nf->nf32.msg, up->note[0].msg, ERRMAX); + nf->nf32.arg1 = PTR2UINT(nf->nf32.msg); + nf->nf32.arg0 = PTR2UINT(&nf->nf32.ureg); + ureg->r0 = PTR2UINT(nf->nf32.arg0); + nf->nf32.ip = 0; + ureg->r13 = sp; + }else{ + memmove(&nf->nf.ureg, ureg, sizeof(Ureg)); + nf->nf.old = up->ureg; + up->ureg = nf; + memmove(nf->nf.msg, up->note[0].msg, ERRMAX); + nf->nf.arg1 = nf->nf.msg; + nf->nf.arg0 = &nf->nf.ureg; + ureg->r0 = PTR2UINT(nf->nf.arg0); + nf->nf.ip = 0; + ureg->sp = sp; + } + ureg->pc = PTR2UINT(up->notify); + up->notified = 1; + up->nnote--; + memmove(&up->lastnote, &up->note[0], sizeof(Note)); + memmove(&up->note[0], &up->note[1], up->nnote*sizeof(Note)); + + qunlock(&up->debug); + splx(s); + + return 1; +} + +void +syscall(Ureg* ureg) +{ + char *e; + u32int s; + uintptr sp; + ulong *argp; + uintptr ret; + int i, scallnr; + vlong startns, stopns; + + if(!userureg(ureg)) + panic("syscall: from kernel: pc %#p r14 %#p psr %#p", + ureg->pc, ureg->r14, ureg->psr); + + cycles(&up->kentry); + + m->syscall++; + up->insyscall = 1; + up->pc = ureg->pc; + up->dbgreg = ureg; + + scallnr = ureg->r0; + up->scallnr = scallnr; + if(scallnr == RFORK) + fpusysrfork(ureg); + spllo(); + if(up->compat32){ + sp = ureg->r13; + //print("aarch32 syscall %s: sp %#p sb %#p r1 %#p (%#lux) %#lux %#lux %#...\n", sysctab[scallnr], sp, ureg->r12, ureg->r1, + // ((ulong*)sp)[0], ((ulong*)sp)[1], ((ulong*)sp)[2], ((ulong*)sp)[3]); + if(sp < (USTKTOP-BY2PG) || sp > (USTKTOP-(MAXSYSARG+1)*BY2WD)) + validaddr(sp, (MAXSYSARG+1)*BY2WD, 0); + argp = (ulong*)(sp + BY2WD); + for(i = 0; i < MAXSYSARG; i++) + up->s.args[i] = *argp++; + switch(scallnr){ + case PREAD: + case PWRITE: + up->s.args[3] |= up->s.args[4]<<32; + break; + } + }else{ + sp = ureg->sp; + //print("aarch64 syscall %s: sp %#p sb %#p r1 %#p (%#p) %#p %#p %#p...\n", sysctab[scallnr], sp, ureg->r28, ureg->r1, + // ((uintptr*)sp)[0], ((uintptr*)sp)[1], ((uintptr*)sp)[2], ((uintptr*)sp)[3]); + if(sp < (USTKTOP-BY2PG) || sp > (USTKTOP-sizeof(Sargs)-sizeof(uintptr))) + validaddr(sp, sizeof(Sargs)+sizeof(uintptr), 0); + up->s = *((Sargs*)(sp+sizeof(uintptr))); + switch(scallnr){ + case SEEK: + up->s.args[4] = up->s.args[3]&0xFFFFFFFFull; + up->s.args[3] = (up->s.args[2]>>32)&0xFFFFFFFFull; + up->s.args[2] &= 0xFFFFFFFFull; + break; + } + } + + if(up->procctl == Proc_tracesyscall){ + syscallfmt(scallnr, ureg->pc, (va_list)(up->s.args)); + up->procctl = Proc_stopme; + procctl(up); + if (up->syscalltrace) + free(up->syscalltrace); + up->syscalltrace = nil; + } + + up->nerrlab = 0; + ret = -1; + startns = todget(nil); + if(!waserror()){ + if(scallnr >= nsyscall){ + pprint("bad sys call number %d pc %#p\n", + scallnr, ureg->pc); + postnote(up, 1, "sys: bad sys call", NDebug); + error(Ebadarg); + } + + up->psstate = sysctab[scallnr]; + + //print("%s %lud: %#p syscall %s\n", up->text, up->pid, ureg->r14, sysctab[scallnr]?sysctab[scallnr]:"huh?"); + + if(!up->compat32 && scallnr == NSEC) + ret = startns; + else + ret = systab[scallnr](up->s.args); + poperror(); + }else{ + /* failure: save the error buffer for errstr */ + e = up->syserrstr; + up->syserrstr = up->errstr; + up->errstr = e; + } + if(up->nerrlab){ + print("bad errstack [%d]: %d extra\n", scallnr, up->nerrlab); + for(i = 0; i < NERR; i++) + print("sp=%#p pc=%#p\n", + up->errlab[i].sp, up->errlab[i].pc); + panic("error stack"); + } + + /* + * Put return value in frame. On the x86 the syscall is + * just another trap and the return value from syscall is + * ignored. On other machines the return value is put into + * the results register by caller of syscall. + */ + ureg->r0 = ret; + + if(up->procctl == Proc_tracesyscall){ + stopns = todget(nil); + up->procctl = Proc_stopme; + sysretfmt(scallnr, (va_list)(up->s.args), ret, startns, stopns); + s = splhi(); + procctl(up); + splx(s); + if(up->syscalltrace) + free(up->syscalltrace); + up->syscalltrace = nil; + } + + up->insyscall = 0; + up->psstate = 0; + + if(scallnr == NOTED) + noted(ureg, *(ulong*)(sp+BY2WD)); + + splhi(); + if(scallnr != RFORK && (up->procctl || up->nnote)) + notify(ureg); + + /* if we delayed sched because we held a lock, sched now */ + if(up->delaysched){ + sched(); + splhi(); + } + kexit(ureg); +} + +long +execregs(ulong entry, ulong ssize, ulong nargs) +{ + uintptr sp; + Ureg *ureg; + + ureg = up->dbgreg; +// memset(ureg, 0, 15*sizeof(ulong)); + sp = (uintptr)(USTKTOP - ssize); + if(up->compat32){ + sp -= sizeof(ulong); + *(ulong*)sp = nargs; + ureg->r13 = sp; + ureg->psr |= 1<<4; + }else{ + sp -= sizeof(uintptr); + *(uintptr*)sp = nargs; + ureg->sp = sp; + ureg->psr &= ~(1<<4); + } + ureg->pc = entry; + + /* + * return the address of kernel/user shared data + * (e.g. clock stuff) + */ + return up->compat32? USTKTOP-sizeof(Tos32) : USTKTOP-sizeof(Tos); +} + +void +sysprocsetup(Proc* p) +{ + fpusysprocsetup(p); +} + +/* + * Craft a return frame which will cause the child to pop out of + * the scheduler in user mode with the return register zero. Set + * pc to point to a l.s return function. + */ +void +forkchild(Proc *p, Ureg *ureg) +{ + Ureg *cureg; + + p->sched.sp = STACKALIGN((uintptr)p->kstack+KSTACK-sizeof(Ureg)); + p->sched.pc = (uintptr)forkret; + + cureg = (Ureg*)(p->sched.sp); + memmove(cureg, ureg, sizeof(Ureg)); + + /* syscall returns 0 for child */ + cureg->r0 = 0; + + /* Things from bottom of syscall which were never executed */ + p->psstate = 0; + p->insyscall = 0; + + fpusysrforkchild(p, cureg, up); +} --- /sys/src/9/bcm64/trap.c Thu Jan 1 00:00:00 1970 +++ /sys/src/9/bcm64/trap.c Mon Apr 13 17:12:39 2026 @@ -0,0 +1,795 @@ +/* + * Adapted from ../teg2/trap.c + * + * arm mpcore generic interrupt controller (gic) v1 + * traps, exceptions, interrupts, system calls. + * + * there are two pieces: the interrupt distributor and the cpu interface. + * + * memset or memmove on any of the distributor registers generates an + * exception like this one: + * panic: external abort 0x28 pc 0xc048bf68 addr 0x50041800 + * + * we use l1 and l2 cache ops to force vectors to be visible everywhere. + * + * apparently irqs 0—15 (SGIs) are always enabled. + */ +#include "u.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#include "io.h" +#include "../port/error.h" + +#include "ureg.h" + +#define IRQLOCAL(irq) ((irq) - IRQlocal + 16) +#define IRQGLOBAL(irq) ((irq) + 64 + 32) + +#define ISSGI(irq) ((uint)(irq) < Nppi) + +enum { + Debug = 0, + + Intrdist = 0x7fff9000, + Intrcpu = 0x7fffa000, + + Nvec = 8, /* # of vectors at start of lexception.s */ + Bi2long = BI2BY * sizeof(long), + Nirqs = 1024, + Nsgi = 16, /* software-generated (inter-processor) intrs */ + Nppi = 32, /* sgis + other private peripheral intrs */ +}; + +typedef struct Intrcpuregs Intrcpuregs; +typedef struct Intrdistregs Intrdistregs; + +/* + * almost this entire register set is buggered. + * the distributor is supposed to be per-system, not per-cpu, + * yet some registers are banked per-cpu, as marked. + */ +struct Intrdistregs { /* distributor */ + ulong ctl; + ulong ctlrtype; + ulong distid; + uchar _pad0[0x80 - 0xc]; + + /* botch: *[0] are banked per-cpu from here */ + /* bit maps */ + ulong grp[32]; /* in group 1 (non-secure) */ + ulong setena[32]; /* forward to cpu interfaces */ + ulong clrena[32]; + ulong setpend[32]; + ulong clrpend[32]; + ulong setact[32]; /* active? */ + ulong clract[32]; + /* botch: *[0] are banked per-cpu until here */ + + uchar pri[1020]; /* botch: pri[0] — pri[7] are banked per-cpu */ + ulong _rsrvd1; + /* botch: targ[0] through targ[7] are banked per-cpu and RO */ + uchar targ[1020]; /* byte bit maps: cpu targets indexed by intr */ + ulong _rsrvd2; + /* botch: cfg[1] is banked per-cpu */ + ulong cfg[64]; /* bit pairs: edge? 1-N? */ + ulong _pad1[64]; + ulong nsac[64]; /* bit pairs (v2 only) */ + + /* software-generated intrs (a.k.a. sgi) */ + ulong swgen; /* intr targets */ + uchar _pad2[0xf10 - 0xf04]; + uchar clrsgipend[16]; /* bit map (v2 only) */ + uchar setsgipend[16]; /* bit map (v2 only) */ +}; + +enum { + /* ctl bits */ + Forw2cpuif = 1, + + /* ctlrtype bits */ + Cpunoshft = 5, + Cpunomask = MASK(3), + Intrlines = MASK(5), + + /* cfg bits */ + Level = 0<<1, + Edge = 1<<1, /* edge-, not level-sensitive */ + Toall = 0<<0, + To1 = 1<<0, /* vs. to all */ + + /* swgen bits */ + Totargets = 0, + Tonotme = 1<<24, + Tome = 2<<24, +}; + +/* each cpu sees its own registers at the same base address ((ARMLOCAL+Intrcpu)) */ +struct Intrcpuregs { + ulong ctl; + ulong primask; + + ulong binpt; /* group pri vs subpri split */ + ulong ack; + ulong end; + ulong runpri; + ulong hipripend; + + /* aliased regs (secure, for group 1) */ + ulong alibinpt; + ulong aliack; /* (v2 only) */ + ulong aliend; /* (v2 only) */ + ulong alihipripend; /* (v2 only) */ + + uchar _pad0[0xd0 - 0x2c]; + ulong actpri[4]; /* (v2 only) */ + ulong nsactpri[4]; /* (v2 only) */ + + uchar _pad0[0xfc - 0xf0]; + ulong ifid; /* ro */ + + uchar _pad0[0x1000 - 0x100]; + ulong deact; /* wo (v2 only) */ +}; + +enum { + /* ctl bits */ + Enable = 1, + Eoinodeact = 1<<9, /* (v2 only) */ + + /* (ali) ack/end/hipriend/deact bits */ + Intrmask = MASK(10), + Cpuidshift = 10, + Cpuidmask = MASK(3), + + /* ifid bits */ + Archversshift = 16, + Archversmask = MASK(4), +}; + +/* secondary interrupt controller */ + +#define RP1INTC (VIRTIO + 0x80108000ull) + +enum { + StatLo = 0x108/4, + StatHi = 0x10C/4, + Set = 0x808/4, + Clr = 0xC08/4, + Ienable = 1<<0, + Iack = 1<<1, + Iackenable = 1<<3, +}; + +typedef struct Vctl Vctl; +typedef struct Vctl { + Vctl* next; /* handlers on this vector */ + char *name; /* of driver, xallocated */ + void (*f)(Ureg*, void*); /* handler to call */ + void* a; /* argument to call it with */ +} Vctl; + +static Lock vctllock; +static Vctl* vctl[Nirqs]; + +/* + * Layout at virtual address 0. + */ +typedef struct Vpage0 { + void (*vectors[Nvec])(void); + u32int vtable[Nvec]; +} Vpage0; + +enum +{ + Ntimevec = 20 /* number of time buckets for each intr */ +}; +ulong intrtimes[Nirqs][Ntimevec]; + +int irqtooearly = 0; + +static ulong shadena[32]; /* copy of enable bits, saved by intcmaskall */ +static Lock distlock; + +extern int notify(Ureg*); + +static void dumpstackwithureg(Ureg *ureg); +static int doirq(Ureg*, int); + +void +printrs(int base, ulong word) +{ + int bit; + + for (bit = 0; word; bit++, word >>= 1) + if (word & 1) + iprint(" %d", base + bit); +} + +void +dumpintrs(char *what, ulong *bits) +{ + int i, first, some; + ulong word; + Intrdistregs *idp = (Intrdistregs *)(ARMLOCAL+Intrdist); + + first = 1; + some = 0; + USED(idp); + for (i = 0; i < nelem(idp->setpend); i++) { + word = bits[i]; + if (word) { + if (first) { + first = 0; + iprint("%s", what); + } + some = 1; + printrs(i * Bi2long, word); + } + } + if (!some) + iprint("%s none", what); + iprint("\n"); +} + +void +dumpintrpend(void) +{ + Intrdistregs *idp = (Intrdistregs *)(ARMLOCAL+Intrdist); + + iprint("\ncpu%d gic regs:\n", m->machno); + dumpintrs("group 1", idp->grp); + dumpintrs("enabled", idp->setena); + dumpintrs("pending", idp->setpend); + dumpintrs("active ", idp->setact); +} + +/* + * keep histogram of interrupt service times + */ +void +intrtime(Mach*, int vno) +{ + ulong diff; + ulong x; + + x = perfticks(); + diff = x - m->perf.intrts; + m->perf.intrts = x; + + m->perf.inintr += diff; + if(up == nil && m->perf.inidle > diff) + m->perf.inidle -= diff; + + if (m->cpumhz == 0) + return; /* don't divide by zero */ + diff /= m->cpumhz*100; /* quantum = 100µsec */ + if(diff >= Ntimevec) + diff = Ntimevec-1; + if ((uint)vno >= Nirqs) + vno = Nirqs-1; + intrtimes[vno][diff]++; +} + +static ulong +intack(Intrcpuregs *icp) +{ + return icp->ack & Intrmask; +} + +static void +intdismiss(Intrcpuregs *icp, ulong ack) +{ + icp->end = ack; + coherence(); +} + +static int +irqinuse(uint irq) +{ + Intrdistregs *idp = (Intrdistregs *)(ARMLOCAL+Intrdist); + + return idp->setena[irq / Bi2long] & (1 << (irq % Bi2long)); +} + +void +intcunmask(uint irq) +{ + Intrdistregs *idp = (Intrdistregs *)(ARMLOCAL+Intrdist); + + ilock(&distlock); + idp->setena[irq / Bi2long] = 1 << (irq % Bi2long); + iunlock(&distlock); +} + +void +intcmask(uint irq) +{ + Intrdistregs *idp = (Intrdistregs *)(ARMLOCAL+Intrdist); + + ilock(&distlock); + idp->clrena[irq / Bi2long] = 1 << (irq % Bi2long); + iunlock(&distlock); +} + +static void +intcmaskall(Intrdistregs *idp) /* mask all intrs for all cpus */ +{ + int i; + + for (i = 0; i < nelem(idp->setena); i++) + shadena[i] = idp->setena[i]; + for (i = 0; i < nelem(idp->clrena); i++) + idp->clrena[i] = ~0; + coherence(); +} + +static void +intcunmaskall(Intrdistregs *idp) /* unused */ +{ + int i; + + for (i = 0; i < nelem(idp->setena); i++) + idp->setena[i] = shadena[i]; + coherence(); +} + +static ulong +permintrs(Intrdistregs *idp, int base, int r) +{ + ulong perms; + + idp->clrena[r] = ~0; /* disable all */ + coherence(); + perms = idp->clrena[r]; + if (perms) { + iprint("perm intrs:"); + printrs(base, perms); + iprint("\n"); + } + return perms; +} + +static void +intrcfg(Intrdistregs *idp) +{ + int i, cpumask; + ulong pat; + + /* set up all interrupts as level-sensitive, to one cpu (0) */ + pat = 0; + for (i = 0; i < Bi2long; i += 2) + pat |= (Level | To1) << i; + + if (m->machno == 0) { /* system-wide & cpu0 cfg */ + for (i = 0; i < nelem(idp->grp); i++) + idp->grp[i] = 0; /* secure */ + for (i = 0; i < nelem(idp->pri); i++) + idp->pri[i] = 0; /* highest priority */ + /* set up all interrupts as level-sensitive, to one cpu (0) */ + for (i = 0; i < nelem(idp->cfg); i++) + idp->cfg[i] = pat; + /* first Nppi are read-only for SGIs and PPIs */ + cpumask = 1<<0; /* just cpu 0 */ + for (i = Nppi; i < sizeof idp->targ; i++) + idp->targ[i] = cpumask; + coherence(); + + intcmaskall(idp); + for (i = 0; i < nelem(idp->clrena); i++) { + // permintrs(idp, i * Bi2long, i); + idp->clrpend[i] = idp->clract[i] = idp->clrena[i] = ~0; + } + } else { /* per-cpu config */ + idp->grp[0] = 0; /* secure */ + for (i = 0; i < 8; i++) + idp->pri[i] = 0; /* highest priority */ + /* idp->targ[0 through Nppi-1] are supposed to be read-only */ + for (i = 0; i < Nppi; i++) + idp->targ[i] = 1<machno; + idp->cfg[1] = pat; + coherence(); + + // permintrs(idp, i * Bi2long, i); + idp->clrpend[0] = idp->clract[0] = idp->clrena[0] = ~0; + /* on cpu1, irq Extpmuirq (118) is always pending here */ + } + coherence(); +} + +void +intrto(int cpu, int irq) +{ + Intrdistregs *idp = (Intrdistregs *)(ARMLOCAL+Intrdist); + + /* first Nppi are read-only for SGIs and the like */ + ilock(&distlock); + idp->targ[irq] = 1 << cpu; + iunlock(&distlock); +} + +void +intrsto(int cpu) /* unused */ +{ + int i; + Intrdistregs *idp = (Intrdistregs *)(ARMLOCAL+Intrdist); + + /* first Nppi are read-only for SGIs and the like */ + for (i = Nppi; i < sizeof idp->targ; i++) + intrto(cpu, i); + USED(idp); +} + +void +intrcpu(int cpu) +{ + Intrdistregs *idp = (Intrdistregs *)(ARMLOCAL+Intrdist); + + ilock(&distlock); + idp->swgen = Totargets | 1 << (cpu + 16) | m->machno; + iunlock(&distlock); +} + +/* + * secondary interrupt controller on RP1 southbridge + */ +void +rp1unmask(int irq) +{ + ulong *regs = (ulong*)RP1INTC; + + regs[Clr + irq] = Iackenable; + regs[Set + irq] = Ienable; +} + +void +pciehostintr(Ureg *ureg, void*) +{ + ulong *regs = (ulong*)RP1INTC; + int irq; + uvlong ack; + static int first; + + ack = regs[StatLo] | ((vlong)regs[StatHi])<<32; + //if(ack == 0 && ++first < 16) + // iprint("pciehost ack==0\n"); + for(irq = 0; ack; irq++, ack >>= 1){ + if(ack&01){ + if(!doirq(ureg, IRQRP1 + irq)){ + //regs[Set + irq] = Iack; + coherence(); + //iprint("pciehost unexpected interrupt %d, disabling\n", irq); + regs[Clr + irq] = Ienable; /* ? */ + }else{ + /* not if edge triggered, ie usb */ + //regs[Set + irq] = Iack; + } + } + } +} + +extern void setvbar(void*); +extern void _vectors(void); + +/* + * set up for exceptions + */ +void +trapinit(void) +{ + Intrdistregs *idp = (Intrdistregs *)(ARMLOCAL+Intrdist); + Intrcpuregs *icp = (Intrcpuregs *)(ARMLOCAL+Intrcpu); + + setvbar(_vectors); + assert((idp->distid & MASK(12)) == 0x43b); /* made by arm */ + assert((icp->ifid & MASK(12)) == 0x43b); /* made by arm */ + + ilock(&distlock); + idp->ctl = 0; + icp->ctl = 0; + coherence(); + + intrcfg(idp); /* some per-cpu cfg here */ + + icp->ctl = Enable; + icp->primask = (uchar)~0; /* let all priorities through */ + coherence(); + + idp->ctl = Forw2cpuif; + iunlock(&distlock); +} + +/* + * enable an irq interrupt + * note that the same private interrupt may be enabled on multiple cpus + */ +void +irqenable(int irq, void (*f)(Ureg*, void*), void* a) +{ + Vctl *v; + int ena; + static char name[] = "anon"; + + if(f == nil) + return; + + /* permute irq numbers for pi5 */ + ena = 1; + if(irq >= IRQRP1) + ena = 2; + else if(irq >= IRQlocal) + irq = IRQLOCAL(irq); + else + irq = IRQGLOBAL(irq); + if(irq >= nelem(vctl)) + panic("irqenable irq %d", irq); + + if (irqtooearly) { + iprint("irqenable for %d %s called too early\n", irq, name); + return; + } + + /* + * if in use, could be a private interrupt on a secondary cpu, + * or a shared irq number (eg emmc and sdhc) + */ + if(!ISSGI(irq) || vctl[irq] == nil) { + v = malloc(sizeof(Vctl)); + if (v == nil) + panic("irqenable: malloc Vctl"); + v->f = f; + v->a = a; + v->name = malloc(strlen(name)+1); + if (v->name == nil) + panic("irqenable: malloc name"); + strcpy(v->name, name); + + lock(&vctllock); + v->next = vctl[irq]; + if (v->next == nil) + vctl[irq] = v; + else if (!ISSGI(irq)) { + /* shared irq number */ + vctl[irq] = v; + ena = 0; + } else { + /* allocation race: someone else did it first */ + free(v->name); + free(v); + } + unlock(&vctllock); + } + switch(ena) { + case 1: /* GIC */ + intdismiss((Intrcpuregs *)(ARMLOCAL+Intrcpu), irq); + intcunmask(irq); + break; + case 2: /* RP1 */ + if(vctl[IRQGLOBAL(IRQpciehost)] == nil) + irqenable(IRQpciehost, pciehostintr, nil); + rp1unmask(irq - IRQRP1); + break; + } +} + +/* + * called by trap to handle access faults + */ +static void +faultarm(Ureg *ureg, uintptr va, int user, int read) +{ + int n, insyscall; + + if(up == nil) { + //dumpstackwithureg(ureg); + iprint("pc %#p link %#p sp %#p r2 %#p\n", ureg->pc, ureg->link, ureg->sp, ureg->r2); + panic("faultarm: cpu%d: nil up, user %d %sing %#p at %#p", + m->machno, user, (read? "read": "writ"), va, ureg->pc); + } + if(up->nlocks.ref) + iprint("faultarm: user %d pc %#p addr %#p: nlocks %ld\n", user, ureg->pc, va, up->nlocks.ref); + insyscall = up->insyscall; + up->insyscall = 1; + + n = fault(va, read); /* goes spllo */ + splhi(); + if(n < 0){ + char buf[ERRMAX]; + + if(!user){ + //dumpstackwithureg(ureg); + uintptr *badsp = (uintptr*)ureg->sp; + panic("fault: cpu%d: kernel %sing %#p at %#p link %#p [%#p %#p %#p %#p %#p]", + m->machno, read? "read": "writ", va, ureg->pc, ureg->link, badsp[0], badsp[1], badsp[2], badsp[3], badsp[4]); + } + /* don't dump registers; programs suicide all the time */ + snprint(buf, sizeof buf, "sys: trap: fault %s va=%#p", + read? "read": "write", va); + postnote(up, 1, buf, NDebug); + } + up->insyscall = insyscall; +} + +void +trap(Ureg* ureg) +{ + int ec, user, read, rem; + uintptr va; + + if(islo()) + panic("trap entered with interrupts enabled"); + if(up != nil) + rem = ((char*)ureg)-up->kstack; + else + rem = ((char*)ureg)-((char*)m+sizeof(Mach)); + if(rem < 1024) { + panic("trap: %d stack bytes left, up %#p ureg %#p at pc %#p", + rem, up, ureg, ureg->pc); + } + + user = (ureg->psr & 0xf) == 0; + read = 1; + ec = ureg->type>>26; /* exception class in ESR_EL1 sysreg */ + switch(ec){ + case 0x00: /* illegal instruction or unknown reason? */ + if(!user) + panic("illegal instruction in kernel (%8.8lux) PC=%#p link=%#p", *(ulong*)ureg->pc, ureg->pc, ureg->link); + else{ + postnote(up, 1, "sys: illegal instruction", NDebug); + } + break; + case 0x07: /* floating point */ + case 0x2C: /* floating exception from aarch62 */ + if(!user) + panic("illegal instruction in kernel PC=%#p link=%#p", ureg->pc, ureg->link); + else if(fpuemu(ureg) == 0){ + postnote(up, 1, "sys: fp instruction error", NDebug); + } + break; + case 0x11: /* aarch32 syscall */ + case 0x15: /* aarch64 syscall */ + syscall(ureg); + return; /* sic */ + case 0x22: /* PC alignment fault */ + print("alignment fault PC=%#p SP=%#p R13=%#p PSR=%#p", ureg->pc, ureg->sp, ureg->r13, ureg->psr); + if(user) + postnote(up, 1, "sys: PC alignment fault", NDebug); + else + panic("kernel PC alignment fault PC=%#p", ureg->pc); + break; + case 0x24: /* EL0 data abort */ + read = (ureg->type & 1<<6) == 0; + case 0x20: /* EL0 instruction abort */ + user = 1; + goto fault; + case 0x25: /* EL1 data abort */ + read = (ureg->type & 1<<6) == 0; + case 0x21: /* EL1 instruction abort */ + fault: + va = farget(); + faultarm(ureg, va, user, read); + break; + case 0x38: /* BKPT from aarch32 */ + case 0x3C: /* BKPT from aarch64 */ + if(user) + postnote(up, 1, "sys: breakpoint", NDebug); + else + panic("kernel bkpt PC=%#p", ureg->pc); + break; + default: + iprint("!trap PC=%#p code=%x\n", ureg->pc, ec); + for(;;) ; + } + + splhi(); + if(user){ + if(up->procctl || up->nnote) + notify(ureg); + kexit(ureg); + } +} + +/* + * called from exception vector to handle interrupts. + * if a clock interrupt, maybe reschedule. + */ +void +irq(Ureg* ureg) +{ + int clockintr, ack, user, rem; + uint irqno; + Intrcpuregs *icp = (Intrcpuregs *)(ARMLOCAL+Intrcpu); + + m->perf.intrts = perfticks(); + if(islo()) + panic("irq entered with interrupts enabled"); + if(up != nil) + rem = ((char*)ureg)-up->kstack; + else + rem = ((char*)ureg)-((char*)m+sizeof(Mach)); + if(rem < 1024) { + panic("trap: %d stack bytes left, up %#p ureg %#p at pc %#p", + rem, up, ureg, ureg->pc); + } + user = (ureg->psr & 0xf) == 0; + clockintr = 0; + m->intr++; + for(;;){ + ack = intack(icp); + irqno = ack & Intrmask; + + if(irqno == 0 || irqno == 1023) + break; + if(irqno == IRQGLOBAL(IRQclock) || irqno == IRQLOCAL(IRQcntpns)) + clockintr = 1; + + if(!doirq(ureg, irqno)){ + if (irqno >= 1022){ + iprint("cpu%d: ignoring spurious interrupt\n", m->machno); + break; + }else { + intcmask(irqno); + iprint("cpu%d: unexpected interrupt %d, now masked\n", + m->machno, irqno); + } + } + + intdismiss(icp, ack); + intrtime(m, irqno); + } + + splhi(); + + /* delaysched set because we held a lock or because our quantum ended */ + if(up && up->delaysched && clockintr){ + sched(); /* can cause more traps */ + splhi(); + } + + if(user){ + if(up->procctl || up->nnote) + notify(ureg); + kexit(ureg); + } +} + +static int +doirq(Ureg *ureg, int irqno) +{ + int handled; + Vctl *v; + + handled = 0; + for(v = vctl[irqno]; v != nil; v = v->next) + if (v->f) { + if (islo()) + panic("trap: pl0 before trap handler for %s", + v->name); + coherence(); + v->f(ureg, v->a); + coherence(); + if (islo()){ + iprint("trap: %s lowered pl", v->name); + splhi(); /* in case v->f lowered pl */ + } + handled++; + } + return handled; +} + +void +fiq(Ureg* ureg) +{ + iprint("!fiq PC=%#p type=%#p\n", ureg->pc, ureg->type); +} + +void +serror(Ureg* ureg) +{ + iprint("!serror %s PC=%#p type=%#p\n", up? up->text : "?", ureg->pc, ureg->type); +} + +void +dumpstack(void) +{} --- /sys/src/9/bcm64/uartpl011.c Thu Jan 1 00:00:00 1970 +++ /sys/src/9/bcm64/uartpl011.c Fri Feb 20 16:51:04 2026 @@ -0,0 +1,702 @@ +/* + * PL011 UART (somewhat like 8250) + * + * Raspberry Pi 5: + * eia0 is debug uart (JST connector between HDMI sockets) + * eia1-5 are RP1 uarts accessible from 40-pin header + */ + +#include "u.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#include "io.h" + +typedef struct Pinctl Pinctl; + +struct Pinctl { + uchar txpin; + uchar rxpin; + uchar alt; +}; + +static Pinctl pinctl[] = { + { 0, 0, 0xFF}, /* debug pins */ + { 14, 15, 4, }, /* rpi pins 8, 10 */ + { 0, 1, 2, }, /* rpi pins 27, 28 */ + { 4, 5, 2, }, /* rpi pins 7, 29 */ + { 8, 9, 2, }, /* rpi pins 24, 21 */ + { 12, 13, 2, }, /* rpi pins 32, 33 */ +}; + +enum { /* registers */ + Dr = 0x00>>2, /* Data (RW) */ + Fr = 0x18>>2, /* Flag */ + Ibrd = 0x24>>2, /* Integer Baud Rate Divisor */ + Fbrd = 0x28>>2, /* Fractional Baud Rate Divisor */ + Lcrh = 0x2C>>2, /* Line Control */ + Cr = 0x30>>2, /* Control */ + Ifls = 0x34>>2, /* Interrupt FIFO Level Select */ + Imsc = 0x38>>2, /* Interrupt Mask Set Clear */ + Mis = 0x40>>2, /* Masked Interrupt Status */ + Icr = 0x44>>2, /* Interrupt Clear */ + Dmacr = 0x48>>2, /* DMA Control */ +}; + +enum { /* Dr error bits */ + Oe = 0x800, /* Overrun */ + Be = 0x400, /* Break */ + Pe = 0x200, /* Parity */ + Fe = 0x100, /* Framing */ +}; + +enum { /* Fr */ + Txfe = 0x80, /* Transmit FIFO Empty */ + Rxff = 0x40, /* Receive FIFO Full */ + Txff = 0x20, /* Transmit FIFO Full */ + Rxfe = 0x10, /* Receive FIFO Empty */ + Busy = 0x08, /* Transmitting Data */ + Cts = 0x01, /* Clear to Send */ +}; + +enum { /* Lcrh */ + Stp = 0x80, /* Stick Parity */ + WlsMASK = 0x60, /* Word Length Select */ + Wls8 = 0x60, /* 8 bits/byte */ + Wls7 = 0x40, /* 7 bits/byte */ + Wls6 = 0x20, /* 6 bits/byte */ + Wls5 = 0x00, /* 5 bits/byte */ + Fen = 0x10, /* FIFO enable */ + Stb = 0x08, /* 2 stop bits */ + Eps = 0x04, /* Even Parity Select */ + Pen = 0x02, /* Parity Enable */ + Brk = 0x01, /* Break */ +}; + +enum { /* Cr */ + Ctsen = 0x8000, /* Auto CTS */ + Rtsen = 0x4000, /* Auto RTS */ + Rts = 0x0800, /* Ready To Send */ + Dtr = 0x0400, /* Data Terminal Ready (unsupported) */ + Rxe = 0x0200, /* Receive Enable */ + Txe = 0x0100, /* Transmit Enable */ + Lbe = 0x0080, /* Loopback Enable */ + Uarten = 0x0001, /* UART Enable */ +}; + +enum { /* Ifls */ + RFIFO1 = 0<<3, /* Rx FIFO trigger level 1/8 full */ + RFIFO2 = 1<<3, /* 2/8 full */ + RFIFO4 = 2<<3, /* 4/8 full */ + RFIFO6 = 3<<3, /* 6/8 full */ + RFIFO7 = 4<<3, /* 7/8 full */ + TFIFO1 = 0<<0, /* Tx FIFO trigger level 1/8 full */ + TFIFO2 = 1<<0, /* 2/8 full */ + TFIFO4 = 2<<0, /* 4/8 full */ + TFIFO6 = 3<<0, /* 6/8 full */ + TFIFO7 = 4<<0, /* 7/8 full */ +}; + +enum { /* Imsc, Mis, Icr */ + Oeint = 0x400, + Beint = 0x200, + Peint = 0x100, + Feint = 0x080, + Rtint = 0x040, + Txint = 0x020, + Rxint = 0x010, + Ctsmint = 0x002, +}; + + +typedef struct Ctlr { + u32int* io; + int irq; + int iena; + int poll; + + ushort sticky[Imsc+1]; + + Lock; + int fena; +} Ctlr; + +extern PhysUart pl011physuart; + +/* +* pi5 - start with the debug uart as eia0 +*/ +static Ctlr pl011ctlr[] = { +{ .io = (u32int*)(0x107d001000ull), //(VIRTIO+0x7d001000), + .irq = IRQpl011, + .poll = 0, }, +{ .io = (u32int*)(VIRTIO+0x80030000ull), + .irq = IRQuart1, + .poll = 0, }, +{ .io = (u32int*)(VIRTIO+0x80034000ull), + .irq = IRQuart2, + .poll = 0, }, +{ .io = (u32int*)(VIRTIO+0x80038000ull), + .irq = IRQuart3, + .poll = 0, }, +{ .io = (u32int*)(VIRTIO+0x8003c000ull), + .irq = IRQuart4, + .poll = 0, }, +{ .io = (u32int*)(VIRTIO+0x80040000ull), + .irq = IRQuart5, + .poll = 0, }, +}; + +static Uart pl011uart[] = { +{ .regs = &pl011ctlr[0], + .name = "uart0", + .freq = 44000000, + .baud = 115200, + .phys = &pl011physuart, + .next = &pl011uart[1], }, +{ .regs = &pl011ctlr[1], + .name = "uart1", + .freq = 50000000, + .baud = 115200, + .phys = &pl011physuart, + .next = &pl011uart[2], }, +{ .regs = &pl011ctlr[2], + .name = "uart2", + .freq = 50000000, + .baud = 115200, + .phys = &pl011physuart, + .next = &pl011uart[3], }, +{ .regs = &pl011ctlr[3], + .name = "uart3", + .freq = 50000000, + .baud = 115200, + .phys = &pl011physuart, + .next = &pl011uart[4], }, +{ .regs = &pl011ctlr[4], + .name = "uart4", + .freq = 50000000, + .baud = 115200, + .phys = &pl011physuart, + .next = &pl011uart[5], }, +{ .regs = &pl011ctlr[5], + .name = "uart5", + .freq = 50000000, + .baud = 115200, + .phys = &pl011physuart, + .next = nil, }, +}; + +#define csr8r(c, r) ((c)->io[r]) +#define csr8w(c, r, v) ((c)->io[r] = (c)->sticky[r] | (v), coherence()) +#define csr8o(c, r, v) ((c)->io[r] = (v), coherence()) + +static long +pl011status(Uart* uart, void* buf, long n, long offset) +{ + char *p; + Ctlr *ctlr; + ushort ier, lcr, mcr, msr; + + ctlr = uart->regs; + p = malloc(READSTR); + mcr = ctlr->sticky[Cr]; + msr = csr8r(ctlr, Fr); + ier = ctlr->sticky[Imsc]; + lcr = ctlr->sticky[Lcrh]; + snprint(p, READSTR, + "b%d c%d d%d e%d l%d m%d p%c r%d s%d i%d\n" + "dev(%d) type(%d) framing(%d) overruns(%d) " + "berr(%d) serr(%d)%s\n", + + uart->baud, + uart->hup_dcd, + 1, + uart->hup_dsr, + ((lcr & WlsMASK) >> 5) + 5, + (ier & Ctsmint) != 0, + (lcr & Pen) ? ((lcr & Eps) ? 'e': 'o'): 'n', + (mcr & Rts) != 0, + (lcr & Stb) ? 2: 1, + ctlr->fena, + + uart->dev, + uart->type, + uart->ferr, + uart->oerr, + uart->berr, + uart->serr, + (msr & Cts) ? " cts": "" + ); + n = readstr(offset, buf, n, p); + free(p); + + return n; +} + +static void +pl011fifo(Uart* uart, int level) +{ + Ctlr *ctlr; + + ctlr = uart->regs; + + /* + * Changing the FIFOena bit in Fcr flushes data + * from both receive and transmit FIFOs; there's + * no easy way to guarantee not losing data on + * the receive side, but it's possible to wait until + * the transmitter is really empty. + */ + ilock(ctlr); + while(!(csr8r(ctlr, Fr) & Txfe)) + ; + + /* + * Set the trigger level, default is the max. + * value. + */ + ctlr->fena = level; + switch(level){ + case 0: + break; + case 4: + level = RFIFO1|TFIFO7; + break; + case 8: + level = RFIFO2|TFIFO6; + break; + case 16: + level = RFIFO4|TFIFO4; + break; + case 24: + level = RFIFO6|TFIFO2; + break; + case 28: + default: + level = RFIFO7|TFIFO1; + break; + } + csr8w(ctlr, Ifls, level); + if(ctlr->fena) + ctlr->sticky[Lcrh] |= Fen; + else + ctlr->sticky[Lcrh] &= ~Fen; + csr8w(ctlr, Lcrh, 0); + iunlock(ctlr); +} + +static void +pl011dtr(Uart* uart, int on) +{ + USED(uart); + USED(on); +} + +static void +pl011rts(Uart* uart, int on) +{ + Ctlr *ctlr; + + /* + * Toggle RTS. + */ + ctlr = uart->regs; + if(on) + ctlr->sticky[Cr] |= Rts; + else + ctlr->sticky[Cr] &= ~Rts; + csr8w(ctlr, Cr, 0); +} + +static void +pl011modemctl(Uart* uart, int on) +{ + Ctlr *ctlr; + + ctlr = uart->regs; + ilock(&uart->tlock); + if(on){ + ctlr->sticky[Imsc] |= Ctsmint; + csr8w(ctlr, Imsc, 0); + uart->modem = 1; + uart->cts = csr8r(ctlr, Fr) & Cts; + } + else{ + ctlr->sticky[Imsc] &= ~Ctsmint; + csr8w(ctlr, Imsc, 0); + uart->modem = 0; + uart->cts = 1; + } + iunlock(&uart->tlock); +} + +static int +pl011parity(Uart* uart, int parity) +{ + int lcr; + Ctlr *ctlr; + + ctlr = uart->regs; + lcr = ctlr->sticky[Lcrh] & ~(Eps|Pen); + + switch(parity){ + case 'e': + lcr |= Eps|Pen; + break; + case 'o': + lcr |= Pen; + break; + case 'n': + break; + default: + return -1; + } + ctlr->sticky[Lcrh] = lcr; + csr8w(ctlr, Lcrh, 0); + + uart->parity = parity; + + return 0; +} + +static int +pl011stop(Uart* uart, int stop) +{ + int lcr; + Ctlr *ctlr; + + ctlr = uart->regs; + lcr = ctlr->sticky[Lcrh] & ~Stb; + + switch(stop){ + case 1: + break; + case 2: + lcr |= Stb; + break; + default: + return -1; + } + ctlr->sticky[Lcrh] = lcr; + csr8w(ctlr, Lcrh, 0); + + uart->stop = stop; + + return 0; +} + +static int +pl011bits(Uart* uart, int bits) +{ + int lcr; + Ctlr *ctlr; + + ctlr = uart->regs; + lcr = ctlr->sticky[Lcrh] & ~WlsMASK; + + switch(bits){ + case 5: + lcr |= Wls5; + break; + case 6: + lcr |= Wls6; + break; + case 7: + lcr |= Wls7; + break; + case 8: + lcr |= Wls8; + break; + default: + return -1; + } + ctlr->sticky[Lcrh] = lcr; + csr8w(ctlr, Lcrh, 0); + + uart->bits = bits; + + return 0; +} + +static int +pl011baud(Uart* uart, int baud) +{ + ulong bgc; + Ctlr *ctlr; + + /* + * Set the Baud rate by calculating and setting the Baud rate + * Generator Constant. This will work with fairly non-standard + * Baud rates. + */ + if(uart->freq == 0 || baud <= 0) + return -1; + bgc = 16*baud; + + ctlr = uart->regs; + csr8o(ctlr, Ibrd, uart->freq / bgc); + csr8o(ctlr, Fbrd, uart->freq % bgc); + /* + * Internally Ibrd Fbrd and Lcrh share a single register, + * updated only when Lcrh is written + */ + csr8w(ctlr, Lcrh, 0); + uart->baud = baud; + return 0; +} + +static void +pl011break(Uart* uart, int ms) +{ + Ctlr *ctlr; + + if (up == nil) + panic("pl011break: nil up"); + /* + * Send a break. + */ + if(ms <= 0) + ms = 200; + + ctlr = uart->regs; + csr8w(ctlr, Lcrh, Brk); + tsleep(&up->sleep, return0, 0, ms); + csr8w(ctlr, Lcrh, 0); +} + +static void +pl011kick(Uart* uart) +{ + int i; + Ctlr *ctlr; + + if(/* uart->cts == 0 || */ uart->blocked) + return; + + ctlr = uart->regs; + + /* + * 128 here is an arbitrary limit to make sure + * we don't stay in this loop too long. If the + * chip's output queue is longer than 128, too + * bad -- presotto + */ + for(i = 0; i < 128; i++){ + if(csr8r(ctlr, Fr) & Txff) + break; + if(uart->op >= uart->oe && uartstageoutput(uart) == 0) + break; + csr8o(ctlr, Dr, *uart->op++); /* start tx */ + } + if(csr8r(ctlr, Fr) & Txfe) + ctlr->sticky[Imsc] &= ~Txint; + else + ctlr->sticky[Imsc] |= Txint; + csr8w(ctlr, Imsc, 0); /* intr when done */ +} + +static void +pl011interrupt(Ureg*, void* arg) +{ + Ctlr *ctlr; + Uart *uart; + int iir, old, r; + + uart = arg; + ctlr = uart->regs; + for(iir = csr8r(ctlr, Mis); iir; iir = csr8r(ctlr, Mis)){ + if(iir & Ctsmint){ + r = csr8r(ctlr, Fr); + if(1){ + ilock(&uart->tlock); + old = uart->cts; + uart->cts = r & Cts; + if(old == 0 && uart->cts) + uart->ctsbackoff = 2; + iunlock(&uart->tlock); + } + } + if(iir & Txint){ + uartkick(uart); + } + if(iir & (Oeint|Beint|Peint|Feint|Rtint|Rxint)){ + /* + * Consume any received data. + * If the received byte came in with a break, + * parity or framing error, throw it away; + * overrun is an indication that something has + * already been tossed. + */ + while(!(csr8r(ctlr, Fr) & Rxfe)){ + r = csr8r(ctlr, Dr); + if(r & Oe) + uart->oerr++; + if(r & Pe) + uart->perr++; + if(r & Fe) + uart->ferr++; + if(!(r & (Be|Fe|Pe))) + uartrecv(uart, r & 0xFF); + } + } + csr8o(ctlr, Icr, iir); + } +} + +static void +pl011disable(Uart* uart) +{ + Ctlr *ctlr; + + ctlr = uart->regs; + ctlr->sticky[Imsc] = 0; + csr8w(ctlr, Imsc, 0); + ctlr->sticky[Cr] = 0; + csr8w(ctlr, Cr, 0); +} + +static void +pl011enable(Uart* uart, int ie) +{ + Ctlr *ctlr; + Pinctl *p; + + ctlr = uart->regs; + p = &pinctl[uart->dev]; + if(p->alt != 0xFF){ + gpioselrp1(p->txpin, p->alt); + gpioselrp1(p->rxpin, p->alt); + gpiopulldownrp1(p->txpin); + gpiopulldownrp1(p->rxpin); + } + + csr8o(ctlr, Cr, 0); + ctlr->sticky[Lcrh] = Wls8; /* no parity */ + csr8w(ctlr, Lcrh, 0); + pl011baud(uart, uart->baud); + + /* + * Enable interrupts and turn on DTR and RTS. + * Be careful if this is called to set up a polled serial line + * early on not to try to enable interrupts as interrupt- + * -enabling mechanisms might not be set up yet. + */ + if(ie){ + if(ctlr->iena == 0 && !ctlr->poll){ + intrenable(ctlr->irq, pl011interrupt, uart, 0, uart->name); + ctlr->iena = 1; + } + ctlr->sticky[Imsc] = Rxint|Rtint; + } + else{ + ctlr->sticky[Imsc] = 0; + } + csr8w(ctlr, Imsc, 0); + + ctlr->sticky[Cr] = Uarten|Rxe|Txe|Rts; + csr8w(ctlr, Cr, 0); +} + +static Uart* +pl011pnp(void) +{ + Uart *uart; + + uart = pl011uart; + if(uart->console == 0) + kbdq = qopen(8*1024, 0, nil, nil); + return uart; +} + +static int +pl011getc(Uart* uart) +{ + Ctlr *ctlr; + + ctlr = uart->regs; + while((csr8r(ctlr, Fr) & Rxfe)) + delay(1); + return csr8r(ctlr, Dr) & 0xFF; +} + +static void +pl011putc(Uart* uart, int c) +{ + int i; + Ctlr *ctlr; + + ctlr = uart->regs; + for(i = 0; !(csr8r(ctlr, Fr) & Txfe) && i < 128; i++) + delay(1); + csr8o(ctlr, Dr, (uchar)c); + for(i = 0; !(csr8r(ctlr, Fr) & Txfe) && i < 128; i++) + delay(1); +} + +void +pl011consinit(void) +{ + Uart *uart; + int n; + char *p, *cmd; + + if((p = getconf("console")) == nil) + return; + n = strtoul(p, &cmd, 0); + if(p == cmd) + return; + if(n < 0 || n > 5) + return; + uart = &pl011uart[n]; + + if(!uart->enabled) + (*uart->phys->enable)(uart, 0); + uartctl(uart, "l8 pn s1"); + if(*cmd != '\0') + uartctl(uart, cmd); + + consuart = uart; + uart->console = 1; +} + +void (*pl011init)(void) = pl011consinit; + +static void +lprinter(char *s, int n) +{ + u32int *r; + + r = pl011ctlr[0].io; + while(n-- > 0){ + while(r[Fr] & Txff) + ; + if(*s == '\n') + r[Dr] = '\r'; + r[Dr] = *s++; + while((r[Fr] & Txfe) == 0) + ; + } +} + +void (*lprint)(char*, int); // = lprinter; + +PhysUart pl011physuart = { + .name = "pl011", + .pnp = pl011pnp, + .enable = pl011enable, + .disable = pl011disable, + .kick = pl011kick, + .dobreak = pl011break, + .baud = pl011baud, + .bits = pl011bits, + .stop = pl011stop, + .parity = pl011parity, + .modemctl = pl011modemctl, + .rts = pl011rts, + .dtr = pl011dtr, + .status = pl011status, + .fifo = pl011fifo, + .getc = pl011getc, + .putc = pl011putc, +}; --- /sys/src/9/bcm64/ureg.s Thu Jan 1 00:00:00 1970 +++ /sys/src/9/bcm64/ureg.s Fri Aug 29 11:41:20 2025 @@ -0,0 +1,37 @@ +Ureg_size = 288 +Ureg_r0 = 0 +Ureg_r1 = 8 +Ureg_r2 = 16 +Ureg_r3 = 24 +Ureg_r4 = 32 +Ureg_r5 = 40 +Ureg_r6 = 48 +Ureg_r7 = 56 +Ureg_r8 = 64 +Ureg_r9 = 72 +Ureg_r10 = 80 +Ureg_r11 = 88 +Ureg_r12 = 96 +Ureg_r13 = 104 +Ureg_r14 = 112 +Ureg_r15 = 120 +Ureg_r16 = 128 +Ureg_r17 = 136 +Ureg_r18 = 144 +Ureg_r19 = 152 +Ureg_r20 = 160 +Ureg_r21 = 168 +Ureg_r22 = 176 +Ureg_r23 = 184 +Ureg_r24 = 192 +Ureg_r25 = 200 +Ureg_r26 = 208 +Ureg_r27 = 216 +Ureg_r28 = 224 +Ureg_r29 = 232 +Ureg_r30 = 240 +Ureg_link = 240 +Ureg_sp = 248 +Ureg_pc = 256 +Ureg_psr = 264 +Ureg_type = 272 --- /sys/src/9/bcm64/ureg32.h Thu Jan 1 00:00:00 1970 +++ /sys/src/9/bcm64/ureg32.h Mon Oct 20 16:45:57 2025 @@ -0,0 +1,20 @@ +typedef struct Ureg32 { + ulong r0; + ulong r1; + ulong r2; + ulong r3; + ulong r4; + ulong r5; + ulong r6; + ulong r7; + ulong r8; + ulong r9; + ulong r10; + ulong r11; + ulong r12; /* sb */ + ulong r13; /* sp */ + ulong r14; /* link */ + ulong type; /* of exception */ + ulong psr; + ulong pc; /* interrupted addr */ +} Ureg32; --- /sys/src/9/bcm64/usbxhci.c Thu Jan 1 00:00:00 1970 +++ /sys/src/9/bcm64/usbxhci.c Sun Apr 12 11:54:22 2026 @@ -0,0 +1,1900 @@ +/* + * from 9front ../port/usbxhci.c + */ + +#include "u.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#include "io.h" +#ifdef USEPCI +#include "pci.h" +#else +#define PCIWADDR(x) PADDR(x) +#endif +#include "../port/error.h" +#include "../bcm/usb.h" + +extern void dmaflush(int, void*, ulong); + +enum { + /* Capability Registers */ + CAPLENGTH = 0x00/4, // 1 + HCIVERSION = 0x02/4, // 2 + HCSPARAMS1 = 0x04/4, + HCSPARAMS2 = 0x08/4, + HCSPARAMS3 = 0x0C/4, + + HCCPARAMS = 0x10/4, + AC64 = 1<<0, + BNC = 1<<1, + CSZ = 1<<2, + PPC = 1<<3, + PIND = 1<<4, + LHRC = 1<<5, + LTC = 1<<6, + NSS = 1<<7, + + DBOFF = 0x14/4, + RTSOFF = 0x18/4, + + HCCPARAMS2 = 0x1C/4, + + /* Operational Registers */ + USBCMD = 0x00/4, /* USB Command Register */ + RUNSTOP = 1<<0, /* Run/Stop - RW */ + HCRST = 1<<1, /* Host Controller Reset - RW */ + INTE = 1<<2, /* Interrupter Enable - RW */ + HSEE = 1<<3, /* Host System Error Enable - RW */ + LHCRST = 1<<7, /* Light Host Controller Reset - RO/RW */ + CSS = 1<<8, /* Controller Save State - RW */ + CRS = 1<<9, /* Controller Restore State - RW */ + EWE = 1<<10, /* Enable Wrap Event - RW */ + EU3S = 1<<11, /* Enable U3 MFINDEX Stop - RW */ + + USBSTS = 0x04/4, /* USB Status Register */ + HCH = 1<<0, /* HCHalted - RO */ + HSE = 1<<2, /* Host System Error - RW1C */ + EINT = 1<<3, /* Event Interrupt - RW1C */ + PCD = 1<<4, /* Port Change Detect - RW1C */ + SSS = 1<<8, /* Save State Status - RO */ + RSS = 1<<9, /* Restore State Status - RO */ + SRE = 1<<10, /* Save/Restore Error - RW1C */ + CNR = 1<<11, /* Controller Not Ready - RO */ + HCE = 1<<12, /* Host Controller Error - RO */ + + PAGESIZE = 0x08/4, /* Page Size - RO */ + + DNCTRL = 0x14/4, /* Device Notification Control Register - RW */ + + CRCR = 0x18/4, /* Command Ring Control Register - RW */ + RCS = 1<<0, /* Ring Cycle State - RW */ + CS = 1<<1, /* Command Stop - RW1S */ + CA = 1<<2, /* Command Abort - RW1S */ + CRR = 1<<3, /* Command Ring Running - RO */ + + DCBAAP = 0x30/4, // 8 + + CONFIG = 0x38/4, /* Configure Register (MaxSlotEn[7:0]) */ + + /* Port Register Set */ + PORTSC = 0x00/4, /* Port status and Control Register */ + CCS = 1<<0, /* Current Connect Status - ROS */ + PED = 1<<1, /* Port Enable/Disabled - RW1CS */ + OCA = 1<<3, /* Over-current Active - RO */ + PR = 1<<4, /* Port Reset - RW1S */ + PLS = 15<<5, /* Port Link State - RWS */ + PP = 1<<9, /* Port Power - RWS */ + PS = 15<<10, /* Port Speed - ROS */ + PIC = 3<<14, /* Port Indicator Control - RWS */ + LWS = 1<<16, /* Port Link Write Strobe - RW */ + CSC = 1<<17, /* Connect Status Change - RW1CS */ + PEC = 1<<18, /* Port Enabled/Disabled Change - RW1CS */ + WRC = 1<<19, /* Warm Port Reset Change - RW1CS */ + OCC = 1<<20, /* Over-current Change - RW1CS */ + PRC = 1<<21, /* Port Reset Change - RW1CS */ + PLC = 1<<22, /* Port Link State Change - RW1CS */ + CEC = 1<<23, /* Port Config Error Change - RW1CS */ + CAS = 1<<24, /* Cold Attach Status - RO */ + WCE = 1<<25, /* Wake on Connect Enable - RWS */ + WDE = 1<<26, /* Wake on Disconnect Enable - RWS */ + WOE = 1<<27, /* Wake on Over-current Enable - RWS */ + DR = 1<<30, /* Device Removable - RO */ + WPR = 1<<31, /* Warm Port Reset - RW1S */ + + PORTPMSC = 0x04/4, + PORTLI = 0x08/4, + + /* Host Controller Runtime Register */ + MFINDEX = 0x0000/4, /* Microframe Index */ + IR0 = 0x0020/4, /* Interrupt Register Set 0 */ + + /* Interrupter Registers */ + IMAN = 0x00/4, /* Interrupter Management */ + IMOD = 0x04/4, /* Interrupter Moderation */ + ERSTSZ = 0x08/4, /* Event Ring Segment Table Size */ + ERSTBA = 0x10/4, /* Event Ring Segment Table Base Address */ + ERDP = 0x18/4, /* Event Ring Dequeue Pointer */ + + /* TRB flags */ + TR_ENT = 1<<1, + TR_ISP = 1<<2, + TR_NS = 1<<3, + TR_CH = 1<<4, + TR_IOC = 1<<5, + TR_IDT = 1<<6, + TR_BEI = 1<<9, + + /* TRB types */ + TR_RESERVED = 0<<10, + TR_NORMAL = 1<<10, + TR_SETUPSTAGE = 2<<10, + TR_DATASTAGE = 3<<10, + TR_STATUSSTAGE = 4<<10, + TR_ISOCH = 5<<10, + TR_LINK = 6<<10, + TR_EVENTDATA = 7<<10, + TR_NOOP = 8<<10, + + CR_ENABLESLOT = 9<<10, + CR_DISABLESLOT = 10<<10, + CR_ADDRESSDEV = 11<<10, + CR_CONFIGEP = 12<<10, + CR_EVALCTX = 13<<10, + CR_RESETEP = 14<<10, + CR_STOPEP = 15<<10, + CR_SETTRDQP = 16<<10, + CR_RESETDEV = 17<<10, + CR_FORCECMD = 18<<10, + CR_NEGBW = 19<<10, + CR_SETLAT = 20<<10, + CR_GETPORTBW = 21<<10, + CR_FORCEHDR = 22<<10, + CR_NOOP = 23<<10, + + ER_TRANSFER = 32<<10, + ER_CMDCOMPL = 33<<10, + ER_PORTSC = 34<<10, + ER_BWREQ = 35<<10, + ER_DOORBELL = 36<<10, + ER_HCE = 37<<10, + ER_DEVNOTE = 38<<10, + ER_MFINDEXWRAP = 39<<10, +}; + +typedef struct Ctlr Ctlr; +typedef struct Wait Wait; +typedef struct Ring Ring; +typedef struct Slot Slot; +typedef struct Epio Epio; +typedef struct Port Port; + +struct Wait +{ + Wait *next; + Ring *ring; + u32int *td; + u32int er[4]; + Rendez *z; +}; + +struct Ring +{ + int id; + + Slot *slot; + + u32int *base; + + u32int mask; + u32int shift; + + u32int rp; + u32int wp; + + u32int *ctx; + u32int *doorbell; + + int stopped; + + Wait *pending; + Lock; +}; + +struct Slot +{ + int id; + + int confval; // bConfigurationValue of SET_CONFIGURATION + int iface; // bInterfaceNumber of SET_INTERFACE + int altc; // bAlternateSetting of SET_INTERFACE + + Ctlr *ctlr; + Udev *dev; + + u32int *ibase; + u32int *obase; + + /* endpoint rings */ + int nep; + Ring epr[32]; +}; + +struct Port +{ + char spec[4]; + int proto; + + u32int *reg; +}; + +struct Ctlr +{ +#ifdef USEPCI + Pcidev *pcidev; +#else + int irq; +#endif + + u32int *mmio; + + u32int *opr; /* operational registers */ + u32int *rts; /* runtime registers */ + u32int *dba; /* doorbell array */ + + u64int *dcba; /* device context base array */ + + u64int *sba; /* scratchpad buffer array */ + void *sbp; /* scratchpad buffer pages */ + + u32int *erst[1]; /* event ring segment table */ + Ring er[1]; /* event ring segment */ + Ring cr[1]; /* command ring segment */ + QLock cmdlock; + + u32int µframe; + + QLock slotlock; + Slot **slot; /* slots by slot id */ + Port *port; + + u32int hccparams; + + int csz; + int pagesize; + int nscratch; + int nintrs; + int nslots; + + Rendez recover; + void *active; + uintptr base; +}; + +struct Epio +{ + QLock; + + Ring *ring; + Block *b; + + /* iso */ + u32int frame; + u32int period; + u32int incr; + u32int tdsz; + + int nleft; +}; + +static char Ebadlen[] = "bad usb request length"; +static char Enotconfig[] = "usb endpoint not configured"; +static char Erecover[] = "xhci controller needs reset"; + +static void interrupt(Ureg*, void*); +static char* +ctlrcmd(Ctlr *ctlr, u32int c, u32int s, u64int p, u32int *er); + +static void +setrptr(u32int *reg, u64int pa) +{ + coherence(); + reg[0] = pa; + reg[1] = pa>>32; +} + +static u32int +µframe(Ctlr *ctlr) +{ + u32int µ; + do { + µ = (ctlr->rts[MFINDEX] & (1<<14)-1) | + (ctlr->µframe & ~((1<<14)-1)); + } while((int)(µ - ctlr->µframe) < 0); + return µ; +} + +static void +freering(Ring *r) +{ + if(r == nil) + return; + if(r->base != nil){ + dmaflush(0, r->base, 4*4<shift); + free(r->base); + } + memset(r, 0, sizeof(*r)); +} + +static Ring* +initring(Ring *r, int shift) +{ + r->id = 0; + r->ctx = nil; + r->slot = nil; + r->doorbell = nil; + r->pending = nil; + r->stopped = 0; + r->shift = shift; + r->mask = (1<rp = r->wp = 0; + r->base = mallocalign(4*4<base == nil){ + freering(r); + error(Enomem); + } + dmaflush(1, r->base, 4*4<pending) != nil){ + r->pending = w->next; + w->next = nil; + if((z = w->z) != nil){ + w->z = nil; + wakeup(z); + } + } +} + +static u64int +resetring(Ring *r) +{ + u64int pa; + + ilock(r); + flushring(r); + r->rp = r->wp; + pa = PCIWADDR(&r->base[4*(r->wp & r->mask)]) | ((~r->wp>>r->shift) & 1); + iunlock(r); + + return pa; +} + +static u32int* +xecp(Ctlr *ctlr, uchar id, u32int *p) +{ + u32int x, *e; + +#ifdef USEPCI + e = &ctlr->mmio[ctlr->pcidev->mem[0].size/4]; +#else + e = &ctlr->mmio[1024]; +#endif + if(p == nil){ + p = ctlr->mmio; + x = ctlr->hccparams>>16; + } else { + assert(p < e); + x = (*p>>8) & 255; + } + while(x != 0){ + p += x; + if(p >= e) + break; + x = *p; + if((x & 255) == id) + return p; + x >>= 8; + x &= 255; + } + return nil; +} + +static void +handoff(Ctlr *ctlr) +{ + u32int *r; + int i; + + if((r = xecp(ctlr, 1, nil)) == nil) + return; + if(getconf("*noxhcihandoff") == nil){ + r[0] |= 1<<24; /* request ownership */ + for(i = 0; (r[0] & (1<<16)) != 0 && i<100; i++) + tsleep(&up->sleep, return0, nil, 10); + } + /* disable SMI interrupts */ + r[1] &= 7<<1 | 255<<5 | 7<<17 | 7<<29; + + /* clear BIOS ownership in case of timeout */ + r[0] &= ~(1<<16); +} + +static void +shutdown(Hci *hp) +{ + Ctlr *ctlr = hp->aux; + int i; + + ctlr->opr[USBCMD] = 0; + for(i=0; (ctlr->opr[USBSTS] & HCH) == 0 && i < 10; i++) + delay(10); +#ifdef USEPCI + pciintrdisable(ctlr->pcidev->tbdf, hp->interrupt, hp); + pcidisable(ctlr->pcidev); +#endif +} + +static void +release(Ctlr *ctlr) +{ + int i; + + freering(ctlr->cr); + for(i=0; ier); i++){ + freering(&ctlr->er[i]); + free(ctlr->erst[i]); + ctlr->erst[i] = nil; + } + free(ctlr->port), ctlr->port = nil; + free(ctlr->slot), ctlr->slot = nil; + free(ctlr->dcba), ctlr->dcba = nil; + free(ctlr->sba), ctlr->sba = nil; + if(ctlr->sbp != nil){ + dmaflush(0, ctlr->sbp, ctlr->nscratch*ctlr->pagesize); + free(ctlr->sbp); + ctlr->sbp = nil; + } +} + +static void recover(void *arg); + +static void +init(Hci *hp) +{ + Ctlr *ctlr; + Port *pp; + u32int *x; + uchar *p; + int i, j; + + ctlr = hp->aux; +#ifdef USEPCI + pcienable(ctlr->pcidev); +#endif + if(ctlr->mmio[CAPLENGTH] == -1){ +#ifdef USEPCI + pcidisable(ctlr->pcidev); +#endif + error("controller vanished"); + } + + ctlr->opr = &ctlr->mmio[(ctlr->mmio[CAPLENGTH]&0xFF)/4]; + ctlr->dba = &ctlr->mmio[ctlr->mmio[DBOFF]/4]; + ctlr->rts = &ctlr->mmio[ctlr->mmio[RTSOFF]/4]; + + ctlr->hccparams = ctlr->mmio[HCCPARAMS]; + handoff(ctlr); +#ifdef USEPCI + xhcireset(BUSBNO(hp->tbdf)<<20 | BUSDNO(hp->tbdf)<<15 | BUSFNO(hp->tbdf)<<12); +#endif + + for(i=0; (ctlr->opr[USBSTS] & CNR) != 0 && i<100; i++) + tsleep(&up->sleep, return0, nil, 10); + if(i == 100) + print("%s: controller not ready, status %ux\n", hp->type, ctlr->opr[USBSTS]); + + ctlr->opr[USBCMD] = HCRST; + delay(1); + for(i=0; (ctlr->opr[USBSTS] & (CNR|HCH)) != HCH && i<100; i++) + tsleep(&up->sleep, return0, nil, 10); + if(i == 100) + print("%s: controller not halted, status %ux\n", hp->type, ctlr->opr[USBSTS]); + +#ifdef USEPCI + pcisetbme(ctlr->pcidev); + pciintrenable(ctlr->pcidev->tbdf, hp->interrupt, hp); +#endif + + if(waserror()){ + shutdown(hp); + release(ctlr); + nexterror(); + } + + ctlr->csz = (ctlr->hccparams & CSZ) != 0; + ctlr->pagesize = (ctlr->opr[PAGESIZE] & 0xFFFF) << 12; + + ctlr->nscratch = (ctlr->mmio[HCSPARAMS2] >> 27) & 0x1F | (ctlr->mmio[HCSPARAMS2] >> 16) & 0x3E0; + ctlr->nintrs = (ctlr->mmio[HCSPARAMS1] >> 8) & 0x7FF; + ctlr->nslots = (ctlr->mmio[HCSPARAMS1] >> 0) & 0xFF; + + hp->highspeed = 1; + hp->superspeed = 0; + hp->nports = (ctlr->mmio[HCSPARAMS1] >> 24) & 0xFF; + ctlr->port = malloc(hp->nports * sizeof(Port)); + if(ctlr->port == nil) + error(Enomem); + for(i=0; inports; i++) + ctlr->port[i].reg = &ctlr->opr[0x400/4 + i*4]; + + x = nil; + while((x = xecp(ctlr, 2, x)) != nil){ + i = x[2]&255; + j = (x[2]>>8)&255; + while(j--){ + if(i < 1 || i > hp->nports) + break; + pp = &ctlr->port[i-1]; + pp->proto = x[0]>>16; + memmove(pp->spec, &x[1], 4); + if(memcmp(pp->spec, "USB ", 4) == 0 && pp->proto >= 0x0300) + hp->superspeed |= 1<<(i-1); + i++; + } + } + + ctlr->slot = malloc((1+ctlr->nslots)*sizeof(ctlr->slot[0])); + ctlr->dcba = mallocalign((1+ctlr->nslots)*sizeof(ctlr->dcba[0]), 64, 0, ctlr->pagesize); + if(ctlr->slot == nil || ctlr->dcba == nil) + error(Enomem); + if(ctlr->nscratch != 0){ + ctlr->sba = mallocalign(ctlr->nscratch*8, 64, 0, ctlr->pagesize); + ctlr->sbp = mallocalign(ctlr->nscratch*ctlr->pagesize, ctlr->pagesize, 0, 0); + if(ctlr->sba == nil || ctlr->sbp == nil) + error(Enomem); + for(i=0, p = ctlr->sbp; inscratch; i++, p += ctlr->pagesize){ + memset(p, 0, ctlr->pagesize); + ctlr->sba[i] = PCIWADDR(p); + } + dmaflush(1, ctlr->sbp, ctlr->nscratch*ctlr->pagesize); + dmaflush(1, ctlr->sba, ctlr->nscratch*8); + ctlr->dcba[0] = PCIWADDR(ctlr->sba); + } else { + ctlr->dcba[0] = 0; + } + for(i=1; i<=ctlr->nslots; i++) + ctlr->dcba[i] = 0; + + ctlr->opr[CONFIG] = (ctlr->opr[CONFIG] & 0xFFFFFC00) | ctlr->nslots; /* MaxSlotsEn */ + + dmaflush(1, ctlr->dcba, (1+ctlr->nslots)*sizeof(ctlr->dcba[0])); + setrptr(&ctlr->opr[DCBAAP], PCIWADDR(ctlr->dcba)); + + initring(ctlr->cr, 8); /* 256 entries */ + ctlr->cr->id = 0; + ctlr->cr->doorbell = &ctlr->dba[0]; + setrptr(&ctlr->opr[CRCR], resetring(ctlr->cr)); + + for(i=0; inintrs; i++){ + u32int *irs = &ctlr->rts[IR0 + i*8]; + + if(i >= nelem(ctlr->er)){ + irs[ERSTSZ] = 0; /* disable ring */ + irs[IMAN] = 1; + irs[IMOD] = 0; + setrptr(&irs[ERSTBA], 0); + setrptr(&irs[ERDP], 0); + continue; + } + + /* allocate and link into event ring segment table */ + initring(&ctlr->er[i], 8); /* 256 entries */ + ctlr->erst[i] = mallocalign(4*4, 64, 0, 0); + if(ctlr->erst[i] == nil) + error(Enomem); + *((u64int*)ctlr->erst[i]) = PCIWADDR(ctlr->er[i].base); + ctlr->erst[i][2] = ctlr->er[i].mask+1; + ctlr->erst[i][3] = 0; + dmaflush(1, ctlr->erst[i], 4*4); + + irs[ERSTSZ] = 1; /* just one segment */ + irs[IMAN] = 3; + irs[IMOD] = 0; + setrptr(&irs[ERSTBA], PCIWADDR(ctlr->erst[i])); + setrptr(&irs[ERDP], PCIWADDR(ctlr->er[i].base) | (1<<3)); + } + poperror(); + + ctlr->µframe = 0; + ctlr->opr[USBSTS] = ctlr->opr[USBSTS] & (HSE|EINT|PCD|SRE); + coherence(); + + ctlr->opr[USBCMD] = RUNSTOP|INTE|HSEE|EWE; + for(i=0; (ctlr->opr[USBSTS] & (CNR|HCH)) != 0 && i<100; i++) + tsleep(&up->sleep, return0, nil, 10); + + kproc("xhcirecover", recover, hp); +#ifndef USEPCI + intrenable(hp->irq, interrupt, hp, UNKNOWN, "usbxhci"); +#endif +} + +static int +needrecover(void *arg) +{ + Ctlr *ctlr = arg; + return ctlr->er->stopped || + (ctlr->opr[USBSTS] & (HCH|HCE|HSE)) != 0; +} + +static void +recover(void *arg) +{ + Hci *hp = arg; + Ctlr *ctlr = hp->aux; + + while(waserror()) + ; + while(!needrecover(ctlr)) + tsleep(&ctlr->recover, needrecover, ctlr, 10); + shutdown(hp); + + /* + * flush all transactions and wait until all devices have + * been detached by usbd. + */ + for(;;){ + int i, j, active; + + ilock(ctlr->cr); + ctlr->cr->stopped = 1; + flushring(ctlr->cr); + iunlock(ctlr->cr); + + active = 0; + qlock(&ctlr->slotlock); + for(i=1; i<=ctlr->nslots; i++){ + Slot *slot = ctlr->slot[i]; + if(slot == nil) + continue; + active++; + for(j=0; j < slot->nep; j++){ + Ring *ring = &slot->epr[j]; + if(ring->base == nil) + continue; + ilock(ring); + ring->stopped = 1; + flushring(ring); + iunlock(ring); + } + } + qunlock(&ctlr->slotlock); + if(active == 0) + break; + + tsleep(&up->sleep, return0, nil, 100); + } + + qlock(&ctlr->slotlock); + qlock(&ctlr->cmdlock); + + release(ctlr); + if(waserror()) { + print("xhci recovery failed: %s\n", up->errstr); + } else { + init(hp); + poperror(); + } + + qunlock(&ctlr->cmdlock); + qunlock(&ctlr->slotlock); + + pexit("", 1); +} + +static void +dump(Hci *) +{ +} + +static void +queuetd(Ring *r, u32int c, u32int s, u64int p, Wait *w) +{ + u32int *td, x; + + x = r->wp++; + if((x & r->mask) == r->mask){ + td = r->base + 4*(x & r->mask); + *(u64int*)td = PCIWADDR(r->base); + td[2] = 0; + td[3] = ((~x>>r->shift)&1) | (1<<1) | TR_LINK; + dmaflush(1, td, 4*4); + x = r->wp++; + } + td = r->base + 4*(x & r->mask); + if(w != nil){ + w->er[0] = w->er[1] = w->er[2] = w->er[3] = 0; + w->ring = r; + w->td = td; + w->z = &up->sleep; + + ilock(r); + w->next = r->pending; + r->pending = w; + iunlock(r); + } + coherence(); + *(u64int*)td = p; + td[2] = s; + td[3] = ((~x>>r->shift)&1) | c; + dmaflush(1, td, 4*4); +} + +static char *ccerrtab[] = { +[2] "Data Buffer Error", +[3] "Babble Detected Error", +[4] "USB Transaction Error", +[5] "TRB Error", +[6] "Stall Error", +[7] "Resume Error", +[8] "Bandwidth Error", +[9] "No Slots Available", +[10] "Invalid Stream Type", +[11] "Slot Not Enabled", +[12] "Endpoint Not Enabled", +[13] "Short Packet", +[14] "Ring Underrun", +[15] "Ring Overrun", +[16] "VF Event Ring Full", +[17] "Parameter Error", +[18] "Bandwidth Overrun Error", +[19] "Context State Error", +[20] "No Ping Response", +[21] "Event Ring Full", +[22] "Incompatible Device", +[23] "Missed Service Error", +[24] "Command Ring Stopped", +[25] "Command Aborted", +[26] "Stopped", +[27] "Stopped - Length Invalid", +[29] "Max Exit Latency Too Large", +[31] "Isoch Buffer Overrun", +[32] "Event Lost Error", +[33] "Undefined Error", +[34] "Invalid Stream ID", +[35] "Secondary Bandwidth Error", +[36] "Split Transaction Error", +}; + +static char* +ccerrstr(u32int cc) +{ + char *s; + + if(cc == 0 || cc == 1 || cc == 13) + return nil; + if(cc < nelem(ccerrtab) && ccerrtab[cc] != nil) + s = ccerrtab[cc]; + else + s = "???"; + return s; +} + +static int +waitdone(void *a) +{ + return ((Wait*)a)->z == nil; +} + +static char* +waittd(Ctlr *ctlr, Wait *w, int tmout) +{ + Ring *r = w->ring; + + coherence(); + *r->doorbell = r->id; + + while(waserror()){ + if(r->stopped) { + ctlr->er->stopped = 1; + wakeup(&ctlr->recover); + + /* wait for rescue */ + tmout = 0; + continue; + } + + if(r == ctlr->cr) + ctlr->opr[CRCR] |= CA; + else + ctlrcmd(ctlr, CR_STOPEP | (r->id<<16) | (r->slot->id<<24), 0, 0, nil); + r->stopped = 1; + + /* time to abort the transaction */ + tmout = 5000; + } + if(tmout > 0){ + tsleep(&up->sleep, waitdone, w, tmout); + if(!waitdone(w)) + error("timed out"); + } else { + while(!waitdone(w)) + sleep(&up->sleep, waitdone, w); + } + poperror(); + { char *err; + err = ccerrstr(w->er[2]>>24); + if(err != nil && strcmp(err, "???") == 0) + iprint("waittd (from %#p) unknown error %d\n", getcallerpc(&ctlr), w->er[2]>>24); + return err; + } +} + +static char* +ctlrcmd(Ctlr *ctlr, u32int c, u32int s, u64int p, u32int *er) +{ + Wait w[1]; + char *err; + + qlock(&ctlr->cmdlock); + if(needrecover(ctlr)){ + qunlock(&ctlr->cmdlock); + return Erecover; + } + ctlr->cr->stopped = 0; + queuetd(ctlr->cr, c, s, p, w); + err = waittd(ctlr, w, 5000); + qunlock(&ctlr->cmdlock); + + if(er != nil) + memmove(er, w->er, 4*4); + + return err; +} + +static void +completering(Ring *r, u32int *er) +{ + Wait *w, **wp; + u32int *td, x; + u64int pa; + + pa = (*(u64int*)er) & ~15ULL; + ilock(r); + + for(x = r->rp; (int)(r->wp - x) > 0;){ + td = &r->base[4*(x++ & r->mask)]; + if((u64int)PCIWADDR(td) == pa){ + r->rp = x; + break; + } + } + + wp = &r->pending; + while(w = *wp){ + if((u64int)PCIWADDR(w->td) == pa){ + Rendez *z = w->z; + + memmove(w->er, er, 4*4); + *wp = w->next; + w->next = nil; + + if(z != nil){ + w->z = nil; + wakeup(z); + } + break; + } else { + wp = &w->next; + } + } + + iunlock(r); +} + +static void +interrupt(Ureg*, void *arg) +{ + Hci *hp = arg; + Ctlr *ctlr = hp->aux; + Ring *ring = ctlr->er; + Slot *slot; + u32int *irs, *td, x; + + if(ring->base == nil) + return; + + irs = &ctlr->rts[IR0]; + x = irs[IMAN]; + if(x & 1) irs[IMAN] = x & 3; + + for(x = ring->rp;; x=++ring->rp){ + td = ring->base + 4*(x & ring->mask); + dmaflush(0, td, 4*4); + + if((((x>>ring->shift)^td[3])&1) == 0) + break; + + switch(td[3] & 0xFC00){ + case ER_CMDCOMPL: + completering(ctlr->cr, td); + break; + case ER_TRANSFER: + x = td[3]>>24; + if(x == 0 || x > ctlr->nslots) + break; + slot = ctlr->slot[x]; + if(slot == nil) + break; + completering(&slot->epr[(td[3]>>16)-1&31], td); + break; + case ER_MFINDEXWRAP: + ctlr->µframe = (ctlr->rts[MFINDEX] & (1<<14)-1) | + (ctlr->µframe+(1<<14) & ~((1<<14)-1)); + break; + case ER_HCE: + iprint("xhci: host controller error: %ux %ux %ux %ux\n", + td[0], td[1], td[2], td[3]); + ctlr->er->stopped = 1; + wakeup(&ctlr->recover); + return; + case ER_PORTSC: + break; + case ER_BWREQ: + case ER_DOORBELL: + case ER_DEVNOTE: + default: + iprint("xhci: event %ud: %ux %ux %ux %ux\n", + x, td[0], td[1], td[2], td[3]); + } + } + + setrptr(&irs[ERDP], PCIWADDR(td) | (1<<3)); +} + +static void +freeslot(void *arg) +{ + Slot *slot; + + if(arg == nil) + return; + slot = arg; + if(slot->id != 0){ + Ctlr *ctlr = slot->ctlr; + qlock(&ctlr->slotlock); + if(ctlr->slot != nil && ctlr->slot[slot->id] == slot){ + ctlrcmd(ctlr, CR_DISABLESLOT | (slot->id<<24), 0, 0, nil); + dmaflush(0, slot->obase, 32*32 << ctlr->csz); + ctlr->dcba[slot->id] = 0; + dmaflush(1, &ctlr->dcba[slot->id], sizeof(ctlr->dcba[0])); + ctlr->slot[slot->id] = nil; + } + qunlock(&ctlr->slotlock); + } + freering(&slot->epr[0]); + free(slot->ibase); + free(slot->obase); + free(slot); +} + +static Slot* +allocslot(Ctlr *ctlr, Udev *dev) +{ + u32int r[4]; + Slot *slot; + char *err; + + slot = malloc(sizeof(Slot)); + if(slot == nil) + error(Enomem); + + slot->ctlr = ctlr; + slot->dev = dev; + slot->nep = 0; + slot->id = 0; + + slot->confval = 0; + slot->iface = 0; + slot->altc = 0; + + qlock(&ctlr->slotlock); + if(waserror()){ + qunlock(&ctlr->slotlock); + freeslot(slot); + nexterror(); + } + if(ctlr->slot == nil) + error(Erecover); + slot->ibase = mallocalign(32*33 << ctlr->csz, 64, 0, ctlr->pagesize); + slot->obase = mallocalign(32*32 << ctlr->csz, 64, 0, ctlr->pagesize); + if(slot->ibase == nil || slot->obase == nil) + error(Enomem); + + if((err = ctlrcmd(ctlr, CR_ENABLESLOT, 0, 0, r)) != nil) + error(err); + slot->id = r[3]>>24; + if(slot->id <= 0 || slot->id > ctlr->nslots || ctlr->slot[slot->id] != nil){ + slot->id = 0; + error("bad slot id from controller"); + } + poperror(); + + dmaflush(1, slot->obase, 32*32 << ctlr->csz); + ctlr->dcba[slot->id] = PCIWADDR(slot->obase); + dmaflush(1, &ctlr->dcba[slot->id], sizeof(ctlr->dcba[0])); + + ctlr->slot[slot->id] = slot; + + qunlock(&ctlr->slotlock); + + return slot; +} + +static void +setdebug(Hci *, int) +{ +} + +static void +epclose(Ep *ep) +{ + Ctlr *ctlr; + Slot *slot; + Ring *ring; + Epio *io; + + if(ep->dev->isroot) + return; + + io = ep->aux; + if(io == nil) + return; + ep->aux = nil; + + ctlr = ep->hp->aux; + slot = ep->dev->aux; + + if(ep->nb > 0 && (io[OREAD].ring != nil || io[OWRITE].ring != nil)){ + u32int *w; + + /* input control context */ + w = slot->ibase; + memset(w, 0, 32<csz); + w[1] = 1; + if((ring = io[OREAD].ring) != nil){ + w[0] |= 1 << ring->id; + if(ring->id == slot->nep) + slot->nep--; + ctlrcmd(ctlr, CR_STOPEP | (ring->id<<16) | (slot->id<<24), 0, 0, nil); + } + if((ring = io[OWRITE].ring) != nil){ + w[0] |= 1 << ring->id; + if(ring->id == slot->nep) + slot->nep--; + ctlrcmd(ctlr, CR_STOPEP | (ring->id<<16) | (slot->id<<24), 0, 0, nil); + } + + /* (input) slot context */ + w += 8<csz; + w[0] = (w[0] & ~(0x1F<<27)) | slot->nep<<27; + + /* (input) ep context */ + w += ep->nb*2*8<csz; + memset(w, 0, 2*32<csz); + + dmaflush(1, slot->ibase, 32*33 << ctlr->csz); + ctlrcmd(ctlr, CR_CONFIGEP | (slot->id<<24), 0, PCIWADDR(slot->ibase), nil); + dmaflush(0, slot->obase, 32*32 << ctlr->csz); + + freering(io[OREAD].ring); + freering(io[OWRITE].ring); + } + freeb(io[OREAD].b); + freeb(io[OWRITE].b); + free(io); +} + +static void +initepctx(u32int *w, Ring *r, Ep *ep) +{ + int ival; + + if(ep->dev->speed == Lowspeed || ep->dev->speed == Fullspeed){ + for(ival=3; ival < 11 && (1<pollival; ival++) + ; + } else { + for(ival=0; ival < 15 && (1<pollival; ival++) + ; + } + w[0] = ival<<16; + w[1] = ((ep->ttype-Tctl) | (r->id&1)<<2)<<3 | (ep->ntds-1)<<8 | ep->maxpkt<<16; + if(ep->ttype != Tiso) + w[1] |= 3<<1; + *((u64int*)&w[2]) = PCIWADDR(r->base) | 1; + w[4] = 2*ep->maxpkt; + if(ep->ttype == Tintr || ep->ttype == Tiso) + w[4] |= (ep->maxpkt*ep->ntds)<<16; +} + +static void +initisoio(Epio *io, Ep *ep) +{ + if(io->ring == nil) + return; + io->frame = 0; + io->period = ep->pollival<<3*(ep->dev->speed == Fullspeed); + io->incr = (ep->hz*io->period<<8)/8000; + io->tdsz = (io->incr+255>>8)*ep->samplesz; + io->b = allocb((io->ring->mask+1)*io->tdsz); +} + +static void +initep(Ep *ep) +{ + Epio *io; + Ctlr *ctlr; + Slot *slot; + Ring *ring; + u32int *w; + char *err; + + io = ep->aux; + ctlr = ep->hp->aux; + slot = ep->dev->aux; + + io[OREAD].ring = io[OWRITE].ring = nil; + if(ep->nb == 0){ + io[OWRITE].ring = &slot->epr[0]; + return; + } + + /* (input) control context */ + w = slot->ibase; + memset(w, 0, 32<csz); + w[1] = 1; + w[31] = slot->altc<<16 | slot->iface<<8 | slot->confval; + + if(waserror()){ + freering(io[OWRITE].ring), io[OWRITE].ring = nil; + freering(io[OREAD].ring), io[OREAD].ring = nil; + nexterror(); + } + if(ep->mode != OREAD){ + ring = initring(io[OWRITE].ring = &slot->epr[ep->nb*2-1], 8); + ring->id = ep->nb*2; + if(ring->id > slot->nep) + slot->nep = ring->id; + ring->slot = slot; + ring->doorbell = &ctlr->dba[slot->id]; + ring->ctx = &slot->obase[ring->id*8<csz]; + w[1] |= 1 << ring->id; + } + if(ep->mode != OWRITE){ + ring = initring(io[OREAD].ring = &slot->epr[ep->nb*2], 8); + ring->id = ep->nb*2+1; + if(ring->id > slot->nep) + slot->nep = ring->id; + ring->slot = slot; + ring->doorbell = &ctlr->dba[slot->id]; + ring->ctx = &slot->obase[ring->id*8<csz]; + w[1] |= 1 << ring->id; + } + + /* (input) slot context */ + w += 8<csz; + w[0] = (w[0] & ~(0x1F<<27)) | slot->nep<<27; + if(!ep->dev->ishub) + w[0] &= ~(1<<25); // MTT + + /* (input) ep context */ + w += ep->nb*2*8<csz; + if(io[OWRITE].ring != nil){ + memset(w, 0, 5*4); + initepctx(w, io[OWRITE].ring, ep); + } + + w += 8<csz; + if(io[OREAD].ring != nil){ + memset(w, 0, 5*4); + initepctx(w, io[OREAD].ring, ep); + } + + dmaflush(1, slot->ibase, 32*33 << ctlr->csz); + err = ctlrcmd(ctlr, CR_CONFIGEP | (slot->id<<24), 0, PCIWADDR(slot->ibase), nil); + dmaflush(0, slot->obase, 32*32 << ctlr->csz); + if(err != nil) + error(err); + + if(ep->ttype == Tiso){ + initisoio(io+OWRITE, ep); + initisoio(io+OREAD, ep); + } + poperror(); +} + +static int +speedid(int speed) +{ + switch(speed){ + case Fullspeed: return 1; + case Lowspeed: return 2; + case Highspeed: return 3; + case Superspeed: return 4; + } + return 0; +} + +static void +epopen(Ep *ep) +{ + Ctlr *ctlr = ep->hp->aux; + Slot *slot, *hub; + Ring *ring; + Epio *io; + Udev *dev; + char *err; + u32int *w; + int i; + + if(ep->dev->isroot) + return; + if(needrecover(ctlr)) + error(Erecover); + io = malloc(sizeof(Epio)*2); + if(io == nil) + error(Enomem); + ep->aux = io; + if(waserror()){ + epclose(ep); + nexterror(); + } + dev = ep->dev; + slot = dev->aux; + if(slot != nil && slot->dev == dev){ + initep(ep); + poperror(); + return; + } + + /* first open has to be control endpoint */ + if(ep->nb != 0) + error(Egreg); + + slot = allocslot(ctlr, dev); + if(waserror()){ + freeslot(slot); + nexterror(); + } + + /* allocate control ep 0 ring */ + ring = initring(io[OWRITE].ring = &slot->epr[0], 4); + ring->id = 1; + slot->nep = 1; + ring->slot = slot; + ring->doorbell = &ctlr->dba[slot->id]; + ring->ctx = &slot->obase[8]; + + /* (input) control context */ + w = slot->ibase; + memset(w, 0, 3*32<csz); + w[1] = 3; /* A0, A1 */ + + /* (input) slot context */ + w += 8<csz; + w[2] = w[3] = 0; + w[0] = dev->routestr | speedid(dev->speed)<<20 | + (dev->speed == Highspeed && dev->ishub != 0)<<25 | // MTT + (dev->ishub != 0)<<26 | slot->nep<<27; + w[1] = dev->rootport<<16; + + /* find the parent hub that this device is conected to */ + qlock(&ctlr->slotlock); + for(i=1; i<=ctlr->nslots; i++){ + hub = ctlr->slot[i]; + if(hub == nil || hub->dev == nil || hub->dev->aux != hub) + continue; + if(hub == slot || hub->dev == dev) + continue; + if(!hub->dev->ishub) + continue; + if(hub->dev->addr != dev->hub) + continue; + if(hub->dev->rootport != dev->rootport) + continue; + + if(dev->speed < Highspeed && hub->dev->speed == Highspeed){ + w[0] |= 1<<25; // MTT + w[2] = hub->id | dev->port<<8; + } + break; + } + qunlock(&ctlr->slotlock); + + /* (input) ep context 0 */ + w += 8<csz; + initepctx(w, io[OWRITE].ring, ep); + + dmaflush(1, slot->ibase, 32*33 << ctlr->csz); + err = ctlrcmd(ctlr, CR_ADDRESSDEV | (slot->id<<24), 0, PCIWADDR(slot->ibase), nil); + dmaflush(0, slot->obase, 32*32 << ctlr->csz); + if(err != nil) + error(err); + + /* (output) slot context */ + w = slot->obase; + + dev->addr = w[3] & 0xFF; + + dev->aux = slot; + dev->free = freeslot; + + poperror(); + poperror(); +} + +static long +isoread(Ep *, uchar *, long) +{ + error(Egreg); + return 0; +} + +static long +isowrite(Ep *ep, uchar *p, long n) +{ + uchar *s, *d; + Ctlr *ctlr; + Epio *io; + u32int i, µ; + long m; + + s = p; + io = (Epio*)ep->aux + OWRITE; + qlock(io); + if(waserror()){ + qunlock(io); + nexterror(); + } + µ = io->period; + ctlr = ep->hp->aux; + if(needrecover(ctlr)) + error(Erecover); + for(i = io->frame;; i++){ + for(;;){ + m = (int)(io->ring->wp - io->ring->rp); + if(m <= 0) + i = (80 + µframe(ctlr))/µ; + if(m < io->ring->mask) + break; + *io->ring->doorbell = io->ring->id; + tsleep(&up->sleep, return0, nil, 5); + } + m = ((io->incr + (i*io->incr&255))>>8)*ep->samplesz; + d = io->b->rp + (i&io->ring->mask)*io->tdsz; + m -= io->nleft, d += io->nleft; + if(n < m){ + memmove(d, p, n); + p += n; + io->nleft += n; + break; + } + memmove(d, p, m); + p += m, n -= m; + m += io->nleft, d -= io->nleft; + io->nleft = 0; + dmaflush(1, d, m); + queuetd(io->ring, TR_ISOCH | (i*µ/8 & 0x7ff)<<20 | TR_IOC, m, PCIWADDR(d), nil); + } + io->frame = i; + while(io->ring->rp != io->ring->wp){ + int d = (int)(i*µ - µframe(ctlr))/8; + //d -= ep->sampledelay*1000 / ep->hz; + if(d < 5) + break; + *io->ring->doorbell = io->ring->id; + tsleep(&up->sleep, return0, nil, d); + } + qunlock(io); + poperror(); + + return p - s; +} + +static char* +unstall(Ep *ep, Ring *r) +{ + char *err; + + switch(r->ctx[0]&7){ + case 2: /* halted */ + case 4: /* error */ + ep->clrhalt = 1; + } + if(ep->clrhalt){ + ep->clrhalt = 0; + err = ctlrcmd(r->slot->ctlr, CR_RESETEP | (r->id<<16) | (r->slot->id<<24), 0, 0, nil); + dmaflush(0, r->ctx, 8*4 << r->slot->ctlr->csz); + if(err != nil) + return err; + r->stopped = 1; + } + if(r->stopped){ + err = ctlrcmd(r->slot->ctlr, CR_SETTRDQP | (r->id<<16) | (r->slot->id<<24), 0, resetring(r), nil); + dmaflush(0, r->ctx, 8*4 << r->slot->ctlr->csz); + if(err != nil) + return err; + r->stopped = 0; + } + if(r->wp - r->rp >= r->mask) + return "Ring Full"; + return nil; +} + +static long +epread(Ep *ep, void *va, long n) +{ + Epio *io; + Ctlr *ctlr; + uchar *p; + char *err; + Wait w[1]; + + if(ep->dev->isroot) + error(Egreg); + + p = va; + if(ep->ttype == Tctl){ + io = (Epio*)ep->aux + OREAD; + qlock(io); + if(io->b == nil || BLEN(io->b) == 0){ + qunlock(io); + return 0; + } + if(n > BLEN(io->b)) + n = BLEN(io->b); + memmove(p, io->b->rp, n); + io->b->rp += n; + qunlock(io); + return n; + } else if(ep->ttype == Tiso) + return isoread(ep, p, n); + + if((uintptr)p <= KZERO){ + Block *b; + + b = allocb(n); + if(waserror()){ + freeb(b); + nexterror(); + } + n = epread(ep, b->rp, n); + memmove(p, b->rp, n); + freeb(b); + poperror(); + return n; + } + + ctlr = (Ctlr*)ep->hp->aux; + io = (Epio*)ep->aux + OREAD; + qlock(io); + if(waserror()){ + dmaflush(0, io->ring->ctx, 8*4 << ctlr->csz); + qunlock(io); + nexterror(); + } + + if((err = unstall(ep, io->ring)) != nil) + error(err); + + dmaflush(1, p, n); + queuetd(io->ring, TR_NORMAL | TR_IOC, n, PCIWADDR(p), w); + err = waittd(ctlr, w, ep->tmout); + dmaflush(0, p, n); + if(err != nil) + error(err); + + qunlock(io); + poperror(); + + n -= (w->er[2] & 0xFFFFFF); + if(n < 0) + n = 0; + + return n; +} + +static long +epwrite(Ep *ep, void *va, long n) +{ + Wait w[3]; + Ctlr *ctlr; + Epio *io; + uchar *p; + char *err; + + if(ep->dev->isroot) + error(Egreg); + + p = va; + if(ep->ttype == Tctl){ + int dir, len; + Ring *ring; + Slot *slot; + + if(n < 8) + error(Eshort); + + if(p[0] == 0x00 && p[1] == 0x05) + return n; + + ctlr = (Ctlr*)ep->hp->aux; + io = (Epio*)ep->aux + OREAD; + ring = io[OWRITE-OREAD].ring; + slot = ring->slot; + qlock(io); + if(waserror()){ + ilock(ring); + ring->pending = nil; + iunlock(ring); + dmaflush(0, ring->ctx, 8*4 << ctlr->csz); + qunlock(io); + nexterror(); + } + if(io->b != nil){ + freeb(io->b); + io->b = nil; + } + len = GET2(&p[6]); + dir = (p[0] & Rd2h) != 0; + if(len > 0){ + io->b = allocb(len); + if(dir == 0){ /* out */ + assert(len >= n-8); + memmove(io->b->wp, p+8, n-8); + } else { + memset(io->b->wp, 0, len); + io->b->wp += len; + } + } + if((err = unstall(ep, ring)) != nil) + error(err); + + if((ring->ctx[1]>>16) != ep->maxpkt){ + u32int *w = slot->ibase; + w[0] = 0; + w[1] = 1<id; + w += (ring->id+1)*8<csz; + initepctx(w, ring, ep); + dmaflush(1, slot->ibase, 32*33 << ctlr->csz); + err = ctlrcmd(ctlr, CR_EVALCTX | (slot->id<<24), 0, PCIWADDR(slot->ibase), nil); + dmaflush(0, slot->obase, 32*32 << ctlr->csz); + if(err != nil) + error(err); + } + + queuetd(ring, TR_SETUPSTAGE | (len > 0 ? 2+dir : 0)<<16 | TR_IDT | TR_IOC, 8, + p[0] | p[1]<<8 | GET2(&p[2])<<16 | + (u64int)(GET2(&p[4]) | len<<16)<<32, &w[0]); + if(len > 0){ + dmaflush(1, io->b->rp, len); + queuetd(ring, TR_DATASTAGE | dir<<16 | TR_IOC, len, + PCIWADDR(io->b->rp), &w[1]); + } + queuetd(ring, TR_STATUSSTAGE | (len == 0 || !dir)<<16 | TR_IOC, 0, 0, &w[2]); + + if((err = waittd(ctlr, &w[0], ep->tmout)) != nil) + error(err); + if(len > 0){ + if((err = waittd(ctlr, &w[1], ep->tmout)) != nil) + error(err); + if(dir != 0){ + dmaflush(0, io->b->rp, len); + io->b->wp -= (w[1].er[2] & 0xFFFFFF); + if(io->b->wp < io->b->rp) + io->b->wp = io->b->rp; + } + } + if((err = waittd(ctlr, &w[2], ep->tmout)) != nil) + error(err); + + if(p[0] == 0x00 && p[1] == 0x09){ + slot->confval = GET2(&p[2]); + } else if(p[0] == 0x01 && p[1] == 0x0d){ + slot->altc = GET2(&p[2]); + slot->iface = GET2(&p[4]); + } + + qunlock(io); + poperror(); + + return n; + } else if(ep->ttype == Tiso) + return isowrite(ep, p, n); + + if((uintptr)p <= KZERO){ + Block *b; + + b = allocb(n); + if(waserror()){ + freeb(b); + nexterror(); + } + memmove(b->wp, p, n); + n = epwrite(ep, b->wp, n); + freeb(b); + poperror(); + return n; + } + + ctlr = (Ctlr*)ep->hp->aux; + io = (Epio*)ep->aux + OWRITE; + qlock(io); + if(waserror()){ + dmaflush(0, io->ring->ctx, 8*4 << ctlr->csz); + qunlock(io); + nexterror(); + } + + if((err = unstall(ep, io->ring)) != nil) + error(err); + + dmaflush(1, p, n); + queuetd(io->ring, TR_NORMAL | TR_IOC, n, PCIWADDR(p), w); + if((err = waittd(ctlr, w, ep->tmout)) != nil) + error(err); + + qunlock(io); + poperror(); + + return n; +} + +static char* +seprintep(char *s, char*, Ep*) +{ + return s; +} + +static int +portstatus(Hci *hp, int port) +{ + Ctlr *ctlr = hp->aux; + u32int psc, ps; + + if(ctlr->port == nil || needrecover(ctlr)) + return 0; + + ps = 0; + psc = ctlr->port[port-1].reg[PORTSC]; + if(psc & CCS) ps |= HPpresent; + if(psc & PED) ps |= HPenable; + if(psc & OCA) ps |= HPovercurrent; + if(psc & PR) ps |= HPreset; + + if((hp->superspeed & (1<<(port-1))) != 0){ + ps |= psc & (PLS|PP); + if(psc & CSC) ps |= 1<<0+16; + if(psc & OCC) ps |= 1<<3+16; + if(psc & PRC) ps |= 1<<4+16; + if(psc & WRC) ps |= 1<<5+16; + if(psc & PLC) ps |= 1<<6+16; + if(psc & CEC) ps |= 1<<7+16; + } else { + if((ps & HPreset) == 0){ + switch((psc>>10)&15){ + case 1: + /* full speed */ + break; + case 2: + ps |= HPslow; + break; + case 3: + ps |= HPhigh; + break; + } + } + if(psc & PP) ps |= HPpower; + if(psc & CSC) ps |= HPstatuschg; + if(psc & PRC) ps |= HPchange; + } + + return ps; +} + +static int +portenable(Hci*, int, int) +{ + return 0; +} + +static int +portreset(Hci *hp, int port, int on) +{ + Ctlr *ctlr = hp->aux; + + if(ctlr->port == nil || needrecover(ctlr)) + return 0; + + if(on){ + ctlr->port[port-1].reg[PORTSC] |= PR; + tsleep(&up->sleep, return0, nil, 200); + } + return 0; +} + + +static Ctlr *ctlrs[Nhcis]; + +#ifdef USEPCI +static void +scanpci(void) +{ + static int already = 0; + int i; + uintpci io; + Ctlr *ctlr; + Pcidev *p; + u32int *mmio; + + if(already) + return; + already = 1; + p = nil; + while ((p = pcimatch(p, 0, 0)) != nil) { + /* + * Find XHCI controllers (Programming Interface = 0x30). + */ + if(p->ccrb != Pcibcserial || p->ccru != Pciscusb || p->ccrp != 0x30) + continue; + io = p->mem[0].bar & ~0x0f; + if(io == 0) + continue; + print("usbxhci: %#x %#x: port %llux size %#x irq %d\n", + p->vid, p->did, io, p->mem[0].size, p->intl); + mmio = (u32int*)mmukmapx(VIRTPCI, io, p->mem[0].size); + if(mmio == nil){ + print("usbxhci: cannot map registers\n"); + continue; + } + ctlr = malloc(sizeof(Ctlr)); + if(ctlr == nil){ + print("usbxhci: no memory\n"); + continue; + } + ctlr->base = io; + ctlr->active = nil; + ctlr->pcidev = p; + ctlr->mmio = mmio; + for(i = 0; i < nelem(ctlrs); i++) + if(ctlrs[i] == nil){ + ctlrs[i] = ctlr; + break; + } + if(i >= nelem(ctlrs)) + print("xhci: bug: more than %d controllers\n", nelem(ctlrs)); + } +} +#else +static void +scanctlr(int i, uintptr io, int irq) +{ + Ctlr *ctlr; + if(ctlrs[i]) + return; + ctlr = malloc(sizeof(Ctlr)); + if(ctlr == nil){ + print("usbxhci: no memory\n"); + return; + } + ctlr->irq = irq; + ctlr->base = PADDR(io); + ctlr->mmio = (uint*)io; + ctlr->active = nil; + ctlrs[i] = ctlr; +} +#endif + +static int +reset(Hci *hp) +{ + Ctlr *ctlr; + int i; + + if(getconf("*nousbxhci")) + return -1; + +#ifdef USEPCI + scanpci(); +#else + scanctlr(0, VIRTIO+0x80200000ull, IRQusbhxci1); + scanctlr(1, VIRTIO+0x80300000ull, IRQusbxhci2); +#endif + + /* + * Any adapter matches if no hp->port is supplied, + * otherwise the ports must match. + */ + for(i = 0; i < nelem(ctlrs) && ctlrs[i] != nil; i++){ + ctlr = ctlrs[i]; + if(ctlr->active == nil) + if(hp->port == 0 || hp->port == ctlr->base){ + ctlr->active = hp; + goto Found; + } + } + return -1; + +Found: + hp->aux = ctlr; + hp->port = ctlr->base; +#ifdef USEPCI + hp->irq = ctlr->pcidev->intl; + hp->tbdf = ctlr->pcidev->tbdf; +#else + hp->irq = ctlr->irq; + hp->tbdf = 0; +#endif + + hp->init = init; + hp->dump = dump; +#ifdef USEPCI + hp->interrupt = interrupt; +#endif + hp->epopen = epopen; + hp->epclose = epclose; + hp->epread = epread; + hp->epwrite = epwrite; + hp->seprintep = seprintep; + hp->portenable = portenable; + hp->portreset = portreset; + hp->portstatus = portstatus; + hp->shutdown = shutdown; + hp->debug = setdebug; + hp->type = "xhci"; + + return 0; +} + +void +usbxhcilink(void) +{ + addhcitype("xhci", reset); +} + +void +dmaflush(int clean, void *p, ulong len) +{ + uintptr s = (uintptr)p; + uintptr e = (uintptr)p + len; + + if(clean){ + s &= ~(BLOCKALIGN-1); + e += BLOCKALIGN-1; + e &= ~(BLOCKALIGN-1); + cachedwbse((void*)s, e - s); + return; + } + if(s & BLOCKALIGN-1){ + s &= ~(BLOCKALIGN-1); + cachedwbinvse((void*)s, BLOCKALIGN); + s += BLOCKALIGN; + } + if(e & BLOCKALIGN-1){ + e &= ~(BLOCKALIGN-1); + if(e < s) + return; + cachedwbinvse((void*)e, BLOCKALIGN); + } + if(s < e) + cachedinvse((void*)s, e - s); +} --- /sys/src/9/bcm64/vcore.c Thu Jan 1 00:00:00 1970 +++ /sys/src/9/bcm64/vcore.c Mon Apr 6 17:07:00 2026 @@ -0,0 +1,376 @@ +#include "u.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" + +/* + * Mailbox interface with videocore gpu + */ + +#define MAILBOX (VIRTIO + 0x07c013880) + +typedef struct Prophdr Prophdr; +typedef struct Fbinfo Fbinfo; +typedef struct Vgpio Vgpio; + +enum { + Read = 0x00>>2, + Write = 0x00>>2, + Peek = 0x10>>2, + Sender = 0x14>>2, + Status = 0x18>>2, + Full = 1<<31, + Empty = 1<<30, + Config = 0x1C>>2, + NRegs = 0x20>>2, + + ChanMask = 0xF, + ChanProps = 8, + ChanFb = 1, + + Req = 0x0, + RspOk = 0x80000000, + TagResp = 1<<31, + + TagGetfwrev = 0x00000001, + TagGetrev = 0x00010002, + TagGetmac = 0x00010003, + TagGetser = 0x00010004, + TagGetram = 0x00010005, + TagGetpower = 0x00020001, + TagSetpower = 0x00028001, + Powerwait = 1<<1, + TagGetclkspd= 0x00030002, + TagGetclkmax= 0x00030004, + TagSetclkspd= 0x00038002, + TagGettemp = 0x00030006, + TagXhciReset= 0x00030058, + TagFballoc = 0x00040001, + TagFbfree = 0x00048001, + TagFbblank = 0x00040002, + TagGetres = 0x00040003, + TagSetres = 0x00048003, + TagGetvres = 0x00040004, + TagSetvres = 0x00048004, + TagGetdepth = 0x00040005, + TagSetdepth = 0x00048005, + TagGetrgb = 0x00040006, + TagSetrgb = 0x00048006, + TagGetGpio = 0x00040010, + + Nvgpio = 2, +}; + +struct Fbinfo { + u32int xres; + u32int yres; + u32int xresvirtual; + u32int yresvirtual; + u32int pitch; /* returned by gpu */ + u32int bpp; + u32int xoffset; + u32int yoffset; + u32int base; /* returned by gpu */ + u32int screensize; /* returned by gpu */ +}; + + +struct Prophdr { + u32int len; + u32int req; + u32int tag; + u32int tagbuflen; + u32int taglen; + u32int data[1]; +}; + +struct Vgpio { + u32int *counts; + u16int incs; + u16int decs; + int ison; +}; + +static Vgpio vgpio; + +static char vcbuffer[256]; +static uintptr VCBUFFER; + +static void +vcwrite(uint chan, int val) +{ + u32int *r; + + r = (u32int*)MAILBOX + NRegs; + val &= ~ChanMask; + while(r[Status]&Full) + ; + coherence(); + r[Write] = val | chan; +} + +static int +vcread(uint chan) +{ + u32int *r; + int x; + + r = (u32int*)MAILBOX; + do{ + while(r[Status]&Empty) + ; + coherence(); + x = r[Read]; + }while((x&ChanMask) != chan); + return x & ~ChanMask; +} + +/* + * Property interface + */ + +static int +vcreq(int tag, void *buf, int vallen, int rsplen) +{ + uintptr r; + int n; + Prophdr *prop; + uintptr aprop; + static int busaddr = 1; + + if(rsplen < vallen) + rsplen = vallen; + rsplen = (rsplen+3) & ~3; + if(VCBUFFER == 0) + VCBUFFER = ((uintptr)vcbuffer) & ~0xF; + prop = (Prophdr*)(VCBUFFER); + n = sizeof(Prophdr) + rsplen + 8; + memset(prop, 0, n); + prop->len = n; + prop->req = Req; + prop->tag = tag; + prop->tagbuflen = rsplen; + prop->taglen = vallen; + if(vallen > 0) + memmove(prop->data, buf, vallen); + cachedwbinvse(prop, prop->len); + for(;;){ + aprop = busaddr? dmaaddr(prop) : PTR2UINT(prop); + vcwrite(ChanProps, aprop); + r = vcread(ChanProps); + if(r == aprop) + break; + if(!busaddr) + return -1; + busaddr = 0; + } + if(prop->req == RspOk && + prop->tag == tag && + (prop->taglen&TagResp)) { + if((n = prop->taglen & ~TagResp) < rsplen) + rsplen = n; + memmove(buf, prop->data, rsplen); + }else + rsplen = -1; + + return rsplen; +} + +/* + * Framebuffer + */ + +static int +fbdefault(int *width, int *height, int *depth) +{ + u32int buf[3]; + char *p; + + if(vcreq(TagGetres, &buf[0], 0, 2*4) != 2*4 || + vcreq(TagGetdepth, &buf[2], 0, 4) != 4) + return -1; + *width = buf[0]; + *height = buf[1]; + if((p = getconf("bcm2708_fb.fbdepth")) != nil) + *depth = atoi(p); + else + *depth = buf[2]; + return 0; +} + +void* +fbinit(int set, int *width, int *height, int *depth) +{ + Fbinfo *fi; + uintptr va; + + if(!set) + fbdefault(width, height, depth); + /* Screen width must be a multiple of 16 */ + *width &= ~0xF; + if(VCBUFFER == 0) + VCBUFFER = ((uintptr)vcbuffer) & ~0xF; + fi = (Fbinfo*)(VCBUFFER); + memset(fi, 0, sizeof(*fi)); + fi->xres = fi->xresvirtual = *width; + fi->yres = fi->yresvirtual = *height; + fi->bpp = *depth; + cachedwbinvse(fi, sizeof(*fi)); + vcwrite(ChanFb, dmaaddr(fi)); + if(vcread(ChanFb) != 0) + return 0; + va = mmukmap(FRAMEBUFFER, fi->base & ~0xC0000000, fi->screensize); + if(va) + memset((char*)va, 0x7F, fi->screensize); + return (void*)va; +} + +int +fbblank(int blank) +{ + u32int buf[1]; + + buf[0] = blank; + if(vcreq(TagFbblank, buf, sizeof buf, sizeof buf) != sizeof buf) + return -1; + return buf[0] & 1; +} + +/* + * Power management + */ +void +setpower(int dev, int on) +{ + u32int buf[2]; + + buf[0] = dev; + buf[1] = Powerwait | (on? 1 : 0); + vcreq(TagSetpower, buf, sizeof buf, sizeof buf); +} + +int +getpower(int dev) +{ + u32int buf[2]; + + buf[0] = dev; + buf[1] = 0; + if(vcreq(TagGetpower, buf, sizeof buf[0], sizeof buf) != sizeof buf) + return -1; + return buf[0] & 1; +} + +/* + * Get board revision + */ +uint +getboardrev(void) +{ + u32int buf[1]; + + if(vcreq(TagGetrev, buf, 0, sizeof buf) != sizeof buf) + return 0; + return buf[0]; +} + +/* + * Get firmware revision + */ +uint +getfirmware(void) +{ + u32int buf[1]; + + if(vcreq(TagGetfwrev, buf, 0, sizeof buf) != sizeof buf) + return 0; + return buf[0]; +} + +/* + * Get serial number + */ +uvlong +getserial(void) +{ + uvlong buf; + + if(vcreq(TagGetser, &buf, 0, sizeof buf) != sizeof buf) + return 0; + return buf; +} + +/* + * Get ARM ram + */ +void +getramsize(Confmem *mem) +{ + u32int buf[2]; + + if(vcreq(TagGetram, buf, 0, sizeof buf) != sizeof buf) + return; + mem->base = buf[0]; + mem->limit = buf[1]; +} + +/* + * Get clock rate + */ +ulong +getclkrate(int clkid) +{ + u32int buf[2]; + + buf[0] = clkid; + if(vcreq(TagGetclkspd, buf, sizeof(buf[0]), sizeof(buf)) != sizeof buf) + return 0; + return buf[1]; +} + +/* + * Set clock rate to hz (or max speed if hz == 0) + */ +void +setclkrate(int clkid, ulong hz) +{ + u32int buf[2]; + + buf[0] = clkid; + if(hz != 0) + buf[1] = hz; + else if(vcreq(TagGetclkmax, buf, sizeof(buf[0]), sizeof(buf)) != sizeof buf) + return; + vcreq(TagSetclkspd, buf, sizeof(buf), sizeof(buf)); +} + +/* + * Get cpu temperature + */ +uint +getcputemp(void) +{ + u32int buf[2]; + + buf[0] = 0; + if(vcreq(TagGettemp, buf, sizeof(buf[0]), sizeof buf) != sizeof buf) + return 0; + return buf[1]; +} + +/* + * Notify gpu that xhci firmware might need loading. This is for some + * pi4 board versions which are missing the eeprom chip for the vl805, + * requiring its firmware to come from the boot eeprom instead. + */ +int +xhcireset(int devaddr) +{ + u32int buf[1]; + + buf[0] = devaddr; + if(vcreq(TagXhciReset, buf, sizeof(buf), sizeof(buf[0])) == sizeof(buf[0])) + return buf[0]; + return -1; +} --- /sys/src/9/bcm64/vectors.s Thu Jan 1 00:00:00 1970 +++ /sys/src/9/bcm64/vectors.s Wed Apr 8 10:54:03 2026 @@ -0,0 +1,126 @@ +#define _ERET 0xd69f03e0 +#define NOP WORD $0 +#define NOP10 NOP; NOP; NOP; NOP; NOP; NOP; NOP; NOP; NOP; NOP + +#define VECTOR0(dest) \ + SUB $(16+Ureg_size), RSP; \ + MOV R30, (16+Ureg_r30)(RSP); \ + MRS SP_EL0, R30; \ + MOV R30, (16+Ureg_sp)(RSP); \ + BL uregsave(SB); \ + MOV $setSB(SB), R28; \ + MRS SPR(0x1800a0), R30; /* mpidr_el1 */ \ + LSR $8, R30; \ + AND $0x3, R30; \ + MOV $machaddr(SB), R(MACH); \ + ADD R30<<3, R(MACH); \ + MOV (R(MACH)), R(MACH); \ + MOV 16(R(MACH)), R(USER); \ + ADD $(16+0), RSP, R0; \ + BL dest(SB); \ + MSR $0x3, DAIFSet; \ + BL uregrestore(SB); \ + MOV (16+Ureg_sp)(RSP), R30; \ + MSR R30, SP_EL0; \ + MOV (16+Ureg_r30)(RSP), R30; \ + ADD $(16+Ureg_size), RSP; \ + WORD $(_ERET); \ + NOP10 + +#define VECTOR1(dest) \ + SUB $(16+Ureg_size), RSP; \ + MOV R30, (16+Ureg_r30)(RSP); \ + ADD $(16+Ureg_size), RSP, R30; \ + MOV R30, (16+Ureg_sp)(RSP); \ + BL uregsave(SB); \ + ADD $(16+0), RSP, R0; \ + BL dest(SB); \ + MSR $0x3, DAIFSet; \ + MOV R(MACH), (16+(8*MACH))(RSP); \ + MOV R(USER), (16+(8*USER))(RSP); \ + BL uregrestore(SB); \ + MOV (16+Ureg_r30)(RSP), R30; \ + ADD $(16+Ureg_size), RSP; \ + WORD $(_ERET); \ + NOP10; NOP; NOP; NOP; NOP; NOP; NOP; NOP; NOP + + +TEXT _vectors(SB), 0, $-4 +vectors: +/* exception from EL1 using SP_EL0 (not used) */ + BL _start(SB) + NOP10; NOP10; NOP10; NOP + VECTOR1(irq) + VECTOR1(fiq) + VECTOR1(serror) +/* exception from EL1 using SP_EL1 */ + VECTOR1(trap) + VECTOR1(irq) + VECTOR1(fiq) + VECTOR1(serror) +/* exception from EL0 aarch64 */ + VECTOR0(trap) + VECTOR0(irq) + VECTOR0(fiq) + VECTOR0(serror) +/* exception from EL0 aarch32 */ + VECTOR0(trap) + VECTOR0(irq) + VECTOR0(fiq) + VECTOR0(serror) + +TEXT uregsave(SB), 0, $-4 + MOVP R0, R1, (16+Ureg_r0)(RSP) + MOVP R2, R3, (16+Ureg_r2)(RSP) + MOVP R4, R5, (16+Ureg_r4)(RSP) + MOVP R6, R7, (16+Ureg_r6)(RSP) + MOVP R8, R9, (16+Ureg_r8)(RSP) + MOVP R10, R11, (16+Ureg_r10)(RSP) + MOVP R12, R13, (16+Ureg_r12)(RSP) + MOVP R14, R15, (16+Ureg_r14)(RSP) + MOVP R16, R17, (16+Ureg_r16)(RSP) + MOVP R18, R19, (16+Ureg_r18)(RSP) + MOVP R20, R21, (16+Ureg_r20)(RSP) + MOVP R22, R23, (16+Ureg_r22)(RSP) + MOVP R24, R25, (16+Ureg_r24)(RSP) + MOVP R26, R27, (16+Ureg_r26)(RSP) + MOVP R28, R29, (16+Ureg_r28)(RSP) + MRS ELR_EL1, R0 + MOV R0, (16+Ureg_pc)(RSP) + MRS SPR(ESR_EL1), R0 + MOV R0, (16+Ureg_type)(RSP) + MRS SPSR_EL1, R0 + MOV R0, (16+Ureg_psr)(RSP) + RETURN + +TEXT uregrestore(SB), 0, $-4 + MOV (16+Ureg_pc)(RSP), R0 + MSR R0, ELR_EL1 + MOV (16+Ureg_psr)(RSP), R0 + MSR R0, SPSR_EL1 + MOVP (16+Ureg_r0)(RSP), R0, R1 + MOVP (16+Ureg_r2)(RSP), R2, R3 + MOVP (16+Ureg_r4)(RSP), R4, R5 + MOVP (16+Ureg_r6)(RSP), R6, R7 + MOVP (16+Ureg_r8)(RSP), R8, R9 + MOVP (16+Ureg_r10)(RSP), R10, R11 + MOVP (16+Ureg_r12)(RSP), R12, R13 + MOVP (16+Ureg_r14)(RSP), R14, R15 + MOVP (16+Ureg_r16)(RSP), R16, R17 + MOVP (16+Ureg_r18)(RSP), R18, R19 + MOVP (16+Ureg_r20)(RSP), R20, R21 + MOVP (16+Ureg_r22)(RSP), R22, R23 + MOVP (16+Ureg_r24)(RSP), R24, R25 + MOVP (16+Ureg_r26)(RSP), R26, R27 + MOVP (16+Ureg_r28)(RSP), R28, R29 + RETURN + +TEXT forkret(SB), 0, $-4 + SUB $16, RSP + MOV R(USER), (16+(8*USER))(RSP) + BL uregrestore(SB) + MOV (16+Ureg_sp)(RSP), R30 + MSR R30, SP_EL0 + MOV (16+Ureg_r30)(RSP), R30 + ADD $(16+Ureg_size), RSP + ERET --- /sys/src/9/bcm64/vfp3.c Thu Jan 1 00:00:00 1970 +++ /sys/src/9/bcm64/vfp3.c Tue Mar 24 19:45:55 2026 @@ -0,0 +1,484 @@ +/* + * VFPv2 or VFPv3 floating point unit + */ +#include "u.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#include "ureg.h" +#include "../bcm/arm.h" + +/* subarchitecture code in m->havefp */ +enum { + VFPv2 = 2, + VFPv3 = 3, +}; + +/* fp control regs. most are read-only */ +enum { + Fpsid = 0, + Fpscr = 1, /* rw */ + Mvfr1 = 6, + Mvfr0 = 7, + Fpexc = 8, /* rw */ + Fpinst= 9, /* optional, for exceptions */ + Fpinst2=10, +}; +enum { + /* Fpexc bits */ + Fpex = 1u << 31, + Fpenabled = 1 << 30, + Fpdex = 1 << 29, /* defined synch exception */ +// Fp2v = 1 << 28, /* Fpinst2 reg is valid */ +// Fpvv = 1 << 27, /* if Fpdex, vecitr is valid */ +// Fptfv = 1 << 26, /* trapped fault is valid */ +// Fpvecitr = MASK(3) << 8, + /* FSR bits appear here */ + Fpmbc = Fpdex, /* bits exception handler must clear */ + + /* Fpscr bits; see u.h for more */ + Stride = MASK(2) << 20, + Len = MASK(3) << 16, + Dn= 1 << 25, + Fz= 1 << 24, + /* trap exception enables (not allowed in vfp3) */ + FPIDNRM = 1 << 15, /* input denormal */ + Alltraps = FPIDNRM | FPINEX | FPUNFL | FPOVFL | FPZDIV | FPINVAL, + /* pending exceptions */ + FPAIDNRM = 1 << 7, /* input denormal */ + Allexc = FPAIDNRM | FPAINEX | FPAUNFL | FPAOVFL | FPAZDIV | FPAINVAL, + /* condition codes */ + Allcc = MASK(4) << 28, +}; + +static int +havefp(void) +{ + if (m->havefpvalid) + return m->havefp; + + m->havefp = VFPv3; + m->fpnregs = 32; + if (m->machno == 0) + print("fp: %d registers, simd\n", m->fpnregs); + m->havefpvalid = 1; + return 1; +} + +/* + * these can be called to turn the fpu on or off for user procs, + * not just at system start up or shutdown. + */ + +void +fpoff(void) +{ + if (m->fpon) { + fpwrexc(0); + m->fpon = 0; + } +} + +void +fpononly(void) +{ + if (!m->fpon && havefp()) { + /* enable fp. must be first operation on the FPUs. */ + fpwrexc(Fpenabled); + m->fpon = 1; + } +} + +static void +fpcfg(void) +{ + static int printed; + + /* clear pending exceptions; no traps in vfp3; all v7 ops are scalar */ + m->fpscr = Dn | FPRNR | (FPINVAL | FPZDIV | FPOVFL) & ~Alltraps; + /* VFPv2 needs software support for underflows, so force them to zero */ + if(m->havefp == VFPv2) + m->fpscr |= Fz; + fpwrscr(m->fpscr); + m->fpconfiged = 1; + + if (printed) + return; + print("fp: arm neon\n"); + printed = 1; +} + +void +fpinit(void) +{ + if (havefp()) { + fpononly(); + fpcfg(); + } +} + +void +fpon(void) +{ + if (havefp()) { + fpononly(); + if (m->fpconfiged) + fpwrscr((fprdscr() & Allcc) | m->fpscr); + else + fpcfg(); /* 1st time on this fpu; configure it */ + } +} + +void +fpclear(void) +{ +// ulong scr; + + fpon(); +// scr = fprdscr(); +// m->fpscr = scr & ~Allexc; +// fpwrscr(m->fpscr); + + fpwrexc(fprdexc() & ~Fpmbc); +} + + +/* + * Called when a note is about to be delivered to a + * user process, usually at the end of a system call. + * Note handlers are not allowed to use the FPU so + * the state is marked (after saving if necessary) and + * checked in the Device Not Available handler. + */ +void +fpunotify(Ureg*) +{ + if(up->fpstate == FPactive){ + fpsave(&up->fpsave); + up->fpstate = FPinactive; + } + up->fpstate |= FPnotestart<fpstate>>FPnoteshift) == FPactive) + fpoff(); + up->fpstate &= ~FPnotemask; +} + +/* + * Called early in the non-interruptible path of + * sysrfork() via the machine-dependent syscall() routine. + * Save the state so that it can be easily copied + * to the child process later. + */ +void +fpusysrfork(Ureg*) +{ + if(up->fpstate == FPactive){ + fpsave(&up->fpsave); + up->fpstate = FPinactive; + }else if((up->fpstate>>FPnoteshift) == FPactive){ + fpsave(&up->notefpsave); + up->fpstate = (up->fpstate&~FPnotemask) | (FPinactive<fpstate == FPactive */ +void +fpsave(FPsave *fps) +{ + fpon(); + fps->control = fps->status = fprdscr(); + assert(m->fpnregs); + fpsaveregs((uvlong*)fps->regs, m->fpnregs); + fpoff(); +} + +static void +fprestore(FPsave *fps) +{ + fpon(); + fpwrscr(fps->control); + m->fpscr = fprdscr() & ~Allcc; + assert(m->fpnregs); + fprestregs((uvlong*)fps->regs, m->fpnregs); +} + +/* + * Called from sched() and sleep() via the machine-dependent + * procsave() routine. + * About to go in to the scheduler. + * If the process wasn't using the FPU + * there's nothing to do. + */ +void +fpuprocsave(Proc *p) +{ + if(p->fpstate&FPnotemask){ + if((p->fpstate>>FPnoteshift) == FPactive){ + if(p->state == Moribund) + fpoff(); + else + fpsave(&p->notefpsave); + p->fpstate = (p->fpstate&~FPnotemask) | (FPinactive<fpstate == FPactive){ + if(p->state == Moribund) + fpoff(); + else{ + /* + * Fpsave() stores without handling pending + * unmasked exeptions. Postnote() can't be called + * here as sleep() already has up->rlock, so + * the handling of pending exceptions is delayed + * until the process runs again and generates an + * emulation fault to activate the FPU. + */ + fpsave(&p->fpsave); + } + p->fpstate = FPinactive; + } +} + +/* + * The process has been rescheduled and is about to run. + * Nothing to do here right now. If the process tries to use + * the FPU again it will cause a Device Not Available + * exception and the state will then be restored. + */ +void +fpuprocrestore(Proc *) +{ +} + +/* + * Disable the FPU. + * Called from sysexec() via sysprocsetup() to + * set the FPU for the new process. + */ +void +fpusysprocsetup(Proc *p) +{ + p->fpstate = FPinit; + fpoff(); +} + +static void +mathnote(void) +{ + ulong status; + char *msg, note[ERRMAX]; + + status = up->fpsave.status; + + /* + * Some attention should probably be paid here to the + * exception masks and error summary. + */ + if (status & FPAINEX) + msg = "inexact"; + else if (status & FPAOVFL) + msg = "overflow"; + else if (status & FPAUNFL) + msg = "underflow"; + else if (status & FPAZDIV) + msg = "divide by zero"; + else if (status & FPAINVAL) + msg = "bad operation"; + else + msg = "spurious"; + snprint(note, sizeof note, "sys: fp: %s fppc=%#p status=%#lux", + msg, up->fpsave.pc, status); + postnote(up, 1, note, NDebug); +} + +static void +mathemu(Ureg *) +{ + if(up->fpstate & FPnotemask){ + /* in note handler */ + switch(up->fpstate>>FPnoteshift){ + case FPnotestart: + /* no FP used yet; copy state at time of note */ + if((up->fpstate&~FPnotemask) == FPinit) { + /* no FP in the process yet */ + up->fpstate = (up->fpstate&~FPnotemask) | (FPactive<notefpsave = up->fpsave; + /* fall through */ + case FPinactive: + /* restore state */ + if(up->fpsave.status & (FPAINEX|FPAUNFL|FPAOVFL|FPAZDIV|FPAINVAL)){ + postnote(up, 1, "sys: floating point exception in note handler", NDebug); + break; + } + fprestore(&up->notefpsave); + up->fpstate = (up->fpstate&~FPnotemask) | (FPactive<fpstate){ + case FPemu: + error("illegal instruction: VFP opcode in emulated mode"); + case FPinit: + fpinit(); + up->fpstate = FPactive; + break; + case FPinactive: + /* + * Before restoring the state, check for any pending + * exceptions. There's no way to restore the state without + * generating an unmasked exception. + * More attention should probably be paid here to the + * exception masks and error summary. + */ + if(up->fpsave.status & (FPAINEX|FPAUNFL|FPAOVFL|FPAZDIV|FPAINVAL)){ + mathnote(); + break; + } + fprestore(&up->fpsave); + up->fpstate = FPactive; + break; + case FPactive: + error("sys: illegal instruction: bad vfp fpu opcode"); + break; + } + fpclear(); +} + +void +fpstuck(uintptr pc) +{ + if (m->fppc == pc && m->fppid == up->pid) { + m->fpcnt++; + if (m->fpcnt > 4) + panic("fpuemu: cpu%d stuck at pid %ld %s pc %#p " + "instr %#8.8lux", m->machno, up->pid, up->text, + pc, *(ulong *)pc); + } else { + m->fppid = up->pid; + m->fppc = pc; + m->fpcnt = 0; + } +} + +enum { + N = 1<<31, + Z = 1<<30, + C = 1<<29, + V = 1<<28, + REGPC = 15, +}; + +static int +condok(int cc, int c) +{ + switch(c){ + case 0: /* Z set */ + return cc&Z; + case 1: /* Z clear */ + return (cc&Z) == 0; + case 2: /* C set */ + return cc&C; + case 3: /* C clear */ + return (cc&C) == 0; + case 4: /* N set */ + return cc&N; + case 5: /* N clear */ + return (cc&N) == 0; + case 6: /* V set */ + return cc&V; + case 7: /* V clear */ + return (cc&V) == 0; + case 8: /* C set and Z clear */ + return cc&C && (cc&Z) == 0; + case 9: /* C clear or Z set */ + return (cc&C) == 0 || cc&Z; + case 10: /* N set and V set, or N clear and V clear */ + return (~cc&(N|V))==0 || (cc&(N|V)) == 0; + case 11: /* N set and V clear, or N clear and V set */ + return (cc&(N|V))==N || (cc&(N|V))==V; + case 12: /* Z clear, and either N set and V set or N clear and V clear */ + return (cc&Z) == 0 && ((~cc&(N|V))==0 || (cc&(N|V))==0); + case 13: /* Z set, or N set and V clear or N clear and V set */ + return (cc&Z) || (cc&(N|V))==N || (cc&(N|V))==V; + case 14: /* always */ + return 1; + case 15: /* never (reserved) */ + return 0; + } + return 0; /* not reached */ +} + +/* only called to deal with user-mode instruction faults */ +int +fpuemu(Ureg* ureg) +{ + int s, cop, op; + int nfp; + uintptr pc; + static int already; + + if(waserror()){ + postnote(up, 1, up->errstr, NDebug); + return 1; + } + + pc = ureg->pc; + validaddr(pc, 4, 0); + if(m->fpon) + fpstuck(pc); /* debugging; could move down 1 line */ + op = (*(ulong *)pc >> 24) & MASK(4); + cop = (*(ulong *)pc >> 8) & MASK(4); + if (up->compat32 && ISFPAOP(cop, op)) { /* old arm 7500 fpa opcode? */ + s = spllo(); + if(!already++) + pprint("warning: emulated arm7500 fpa instr %#8.8lux at %#p\n", *(ulong *)pc, pc); + if(waserror()){ + splx(s); + nexterror(); + } + nfp = fpiarm(ureg); /* advances pc past emulated instr(s) */ + if (nfp > 1) /* could adjust this threshold */ + m->fppc = m->fpcnt = 0; + poperror(); + } else { //if (ISVFPOP(cop, op)) { /* if vfp, fpu off or unsupported instruction */ + mathemu(ureg); /* enable fpu & retry */ + nfp = 1; + } + + poperror(); + return nfp; +} + +int +fpiarm(Ureg*) +{ + error("emulated arm7500 fpa unsupported"); + return 0; +}