/* * Copyright (c) 2023 Anders Magnusson. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include "sim_defs.h" #include "nd100_defs.h" /* * Nord-100 Memory Management I * * The paging system on Nord-100 have two modes; normal and extended. * In normal mode it is Nord-10-compatible (with a maximum of 512KW memory) * and in extended it can handle up to 16MW. * * It is quite extensive with both individual page protection (RWX) and * ring protection, where it has four rings. Ring 0 has lowest prio, ring 3 * has highest. * * Page size is 1KW, so 64 pages per address space (page table, PT). * A process can use an extra page table as well, called alternate page table. * It is intended to allow large processes to use a separate data space. * There are four page tables, the PCR tells which table(s) to use. * * Which protection ring and which page tables a user belongs to is * configured in the internal Paging Control Register (PCR). There is * one PCR per interrupt level. The PCR looks like this: * * +-------------------+-------+-----+----------+--+-----+ * | Unused | PT | APT | Level | 0| Ring| * +-------------------+-------+-----+----------+--+-----+ * 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 * * PT/APT tells which page table should be used. There are four of them. * Level is not stored in the PCR, it is only used to tell which level * a PCR belongs to. * Ring is the user ring. Must always be >= the page ring. * * The page tables are located in "shadow memory". Shadow memory is the * top pages (>= 177400 in normal mode and >= 177000 in extended) only * accessible when running as ring 3. * Page table for normal mode looks as below: * * +---+---+---+---+---+-------+-------------------------+ * |WPM|RPM|FPM|WIP|PGU| Ring | Physical Page Number | * +---+---+---+---+---+-------+-------------------------+ * 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 * * WPM/RPM/FPM allows for Write/Read/Fetch. * WIP is set by HW and means "Written in page". * PGU is set by HW and means "Page used". * Ring is the ring the page belongs to. Must be <= User ring. * * Note that the executing program get its ring level from the page where * the instruction were fetched from, so if the User ring is 3 but the * page ring is 2 then the progra cannot access the page tables. */ #define MAXMEMSIZE (512*1024) t_stat mm_reset(DEVICE *dptr); uint16 PM[MAXMEMSIZE]; uint16 PCR[16]; /* Paging control register */ uint16 ptmap[4][64]; /* protection */ uint16 pmmap[4][64]; /* memory mapping */ uint16 pea, pes, pgs; int pea_locked, pgs_locked; /* flag to lock register after error */ int userring; /* current user ring */ extern jmp_buf env; #define ISDIS() (mm_dev.flags & DEV_DIS) UNIT mm_unit = { UDATA(NULL, UNIT_FIX+UNIT_DISABLE+UNIT_BINK, 0) }; REG mm_reg[] = { { BRDATA(PCR, PCR, 8, 16, 16) }, { ORDATA(PEA, pea, 16) }, { ORDATA(PES, pes, 16) }, { ORDATA(PGS, pgs, 16) }, { NULL } }; DEVICE mm_dev = { "MM", &mm_unit, mm_reg, 0, 1, 8, 16, 1, 8, 16, 0, 0, &mm_reset, NULL, NULL, NULL, NULL, DEV_DISABLE }; /* * Internal registers located on the MM module. */ int mm_tra(int reg) { int rv = 0; switch (reg) { case IRR_PES: regA = pes; break; case IRR_PGS: regA = pgs; pgs_locked = 0; break; case IRR_PGC: regA = PCR[(regA >> 3) & 017]; break; case IRR_PEA: regA = pea; pea_locked = 0; break; default: rv = STOP_UNHINS; } return rv; } /* * Read a byte. 0 in lr is left byte, 1 is right byte. */ uint8 rdbyte(int vaddr, int lr, int how) { uint16 val = rdmem(vaddr, how); return lr ? val & 0377 : val >> 8; } /* * Write a byte. 0 in lr is left byte, 1 is right byte. */ void wrbyte(int vaddr, int val, int lr, int how) { uint16 ov = rdmem(vaddr, how); val &= 0377; /* sanity */ ov = lr ? (ov & 0177400) | val : (ov & 0377) | (val << 8); wrmem(vaddr, ov, how); } /* * Access shadow memory. if: * sexi == 0 && v >= 0177400 && (myring == 3 || pon == 0) * or * sexi == 1 && v >= 0177000 && (myring == 3 || pon == 0) */ static int is_shadow(int vaddr) { if ((PCR[curlvl] & 03) < 3 && ISPON()) return 0; /* not valid */ if (vaddr > 0177777) return 0; if (ISSEX()) return 1; return (vaddr >= 0177400); } /* * Fetch a word from the shadow mem. */ static int shadowrd(int v) { int pt; int x = 0; if (ISSEX()) x = v & 1, v >>= 1; pt = (v >> 6) & 03; v &= 077; if (ISSEX()) return x ? pmmap[pt][v] : ptmap[pt][v]; return ptmap[pt][v]|pmmap[pt][v]; } /* * Write a word to the shadow mem. */ static void shadowwr(int v, int dat) { int pt; int x = 0; if (ISSEX()) x = v & 1, v >>= 1; pt = (v >> 6) & 03; v &= 077; if (ISSEX()) { if (x) pmmap[pt][v] = dat; else ptmap[pt][v] = dat; } else { pmmap[pt][v] = dat & 0777; ptmap[pt][v] = dat & 0177000; } } /* * MOR - memory out of range. Addressing non-existent memory. */ void morerr(int addr, int why, int pesval) { if (pea_locked == 0) { pea = (uint16)addr; pes = (uint16)(addr >> 16) | pesval; pea_locked = 1; } intrpt14(IIE_MOR, why); } /* * Physical memory read when doing DMA. * The only error that can occur is non-existent memory (MOR). * returns -1 if MOR, value otherwise. */ int dma_rdmem(int addr) { addr &= 0xffffff; if (addr < MAXMEMSIZE) return PM[addr]; morerr(addr, PM_DMA, PES_DMA); return -1; } int dma_wrmem(int addr, int val) { addr &= 0xffffff; if (addr < MAXMEMSIZE) { PM[addr] = val; return 0; } morerr(addr, PM_DMA, PES_DMA); return -1; } /* * Read direct from physical (24-bit-addr) memory. */ uint16 prdmem(int vaddr, int how) { if ((vaddr & 0xffffff) < MAXMEMSIZE) return PM[vaddr]; morerr(vaddr, PM_CPU, how == M_FETCH ? PES_FETCH : 0); return 0; } static void pgsupd(int pgnr, int pnr, int flg) { if (pgs_locked) return; pgs = (pgnr << 6) | pnr | flg; pgs_locked = 1; } uint16 rdmem(int vaddr, int how) { uint16 *ptmapp; int sh, pagetablenr, pagering, pagenr, permit, p; vaddr &= 0177777; /* Sanity */ /* Shadow memory? */ if ((vaddr >= 0177000) && is_shadow(vaddr)) return shadowrd(vaddr); /* Physical memory? */ if (ISPON() == 0) return prdmem(vaddr, 0); /* Paging on. */ permit = (how == M_FETCH ? PT_FPM : PT_RPM); userring = PCR[curlvl] & 03; sh = how == M_APT ? 7 : 9; pagetablenr = (PCR[curlvl] >> sh) & 03; pagenr = vaddr >> 10; ptmapp = &ptmap[pagetablenr][pagenr]; pagering = (*ptmapp >> 9) & 03; p = (permit == PT_FPM ? PGS_FF : 0); if ((*ptmapp & (PT_WPM|PT_RPM|PT_FPM)) == 0) { /* page fault */ pgsupd(pagetablenr, pagenr, p | PGS_PM); intrpt14(IIE_PF, PM_CPU); } else if ((*ptmapp & permit) == 0) { pgsupd(pagetablenr, pagenr, p | PGS_PM); intrpt14(IIE_PV, PM_CPU); } else if (pagering > userring) { pgsupd(pagetablenr, pagenr, p); intrpt14(IIE_PV, PM_CPU); } else { /* Mark page as read */ *ptmapp |= PT_PGU; vaddr = (pmmap[pagetablenr][pagenr] << 10) | (vaddr & 01777); return prdmem(vaddr, 0); } return 0; } /* * Write direct to physical (24-bit-addr) memory. */ void pwrmem(int vaddr, int val, int how) { if ((vaddr & 0xffffff) < MAXMEMSIZE) PM[vaddr] = val; else morerr(vaddr, PM_CPU, how == M_FETCH ? PES_FETCH : 0); } void wrmem(int vaddr, int val, int how) { uint16 *ptmapp; int sh, pagetablenr, pagering, permit, pagenr; vaddr &= 0177777; if ((vaddr >= 0177000) && is_shadow(vaddr)) { shadowwr(vaddr, val); return; } if (ISPON() == 0) { pwrmem(vaddr, val, PM_CPU); return; } /* Paging on. */ permit = PT_WPM; userring = PCR[curlvl] & 03; sh = how == M_APT ? 7 : 9; pagetablenr = (PCR[curlvl] >> sh) & 03; pagenr = vaddr >> 10; ptmapp = &ptmap[pagetablenr][pagenr]; pagering = (*ptmapp >> 9) & 03; if ((*ptmapp & (PT_WPM|PT_RPM|PT_FPM)) == 0) { /* page fault */ pgsupd(pagetablenr, pagenr, PGS_PM); intrpt14(IIE_PF, PM_CPU); } else if ((*ptmapp & permit) == 0) { pgsupd(pagetablenr, pagenr, PGS_PM); intrpt14(IIE_PV, PM_CPU); } else if (pagering > userring) { pgsupd(pagetablenr, pagenr, 0); intrpt14(IIE_PV, PM_CPU); } else { /* Mark page as written */ *ptmapp |= (PT_PGU|PT_WIP); vaddr = (pmmap[pagetablenr][pagenr] << 10) | (vaddr & 01777); pwrmem(vaddr, val, PM_CPU); } } void mm_wrpcr() { if (ISDIS()) return; PCR[(regA >> 3) & 017] = regA & 03603; } t_stat mm_reset(DEVICE *dptr) { return 0; } /* * Check if instruction is privileged enough to execute, * otherwise give priv instruction fault. */ void mm_privcheck(void) { if (ISPON() == 0) return; if (userring > 1) return; intrpt14(IIE_PI, PM_CPU); }