REM Copyright (c) 2021, RISC OS Open Ltd
REM All rights reserved.
REM
REM Redistribution and use in source and binary forms, with or without
REM modification, are permitted provided that the following conditions are met:
REM     * Redistributions of source code must retain the above copyright
REM       notice, this list of conditions and the following disclaimer.
REM     * Redistributions in binary form must reproduce the above copyright
REM       notice, this list of conditions and the following disclaimer in the
REM       documentation and/or other materials provided with the distribution.
REM     * Neither the name of RISC OS Open Ltd nor the names of its contributors
REM       may be used to endorse or promote products derived from this software
REM       without specific prior written permission.
REM
REM THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
REM AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
REM IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
REM ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
REM LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
REM CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
REM SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
REM INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
REM CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
REM ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
REM POSSIBILITY OF SUCH DAMAGE.

REM Test AbortTrap handling of different access permissions
REM
REM It iterates through all AP values, testing that reading & writing
REM triggers the abort handler correctly (abort handler should only
REM be called for accesses where the code doesn't have permission to
REM perform the access directly)

ON ERROR ERROR EXT 0,REPORT$+" at "+STR$(ERL)
da_size%=3*4096
SYS "OS_Module",6,,,4096+da_size% TO ,,rma%

swi$="OS_AbortTrap"

FOR pass=0 TO 2 STEP 2
P%=rma%
[ OPT pass
.handler%
; R0 = flags
; R1 = buffer
; R2 = required address
; R3 = length
; R12 = param
STMFD R13!,{R0-R5,R14}
ADR R4,last_call%
MRS R5,CPSR
STMIA R4,{R0-R3,R5,R12}
AND R0,R0,#15
CMP R0,#2
LDMHSFD R13!,{R0-R5,R14}
MSRHS CPSR_f,#(1<<28)+(1<<29)
ADRHS R0,bad_reason
MOVHS PC,LR

ADR R4,buffer%
LDR R5,da_base_ptr%
SUB R2,R2,R5
ADD R2,R2,R4
TST R0,#1
EORNE R1,R1,R2 ; R1^R2, R2
EORNE R2,R2,R1 ; R1^R2, R1
EORNE R1,R1,R2 ; R2, R1
.loop%
SUBS R3,R3,#1
LDRB R5,[R1],#1
STRB R5,[R2],#1
BNE loop%
LDMFD R13!,{R0-R5,PC}

.bad_reason
EQUD 0
EQUS "Bad reason" : EQUB 0
ALIGN

.writew_svc%
SWI "OS_EnterOS"
STR R1,[R0]
SWI "OS_LeaveOS"
MOV PC,R14

.readb_svc%
SWI "OS_EnterOS"
LDRB R0,[R0]
SWI "OS_LeaveOS"
MOV PC,R14

.readw_svc%
SWI "OS_EnterOS"
LDR R0,[R0]
SWI "OS_LeaveOS"
MOV PC,R14

.read_sctlr%
SWI "OS_EnterOS"
MRC CP15,0,R0,C1,C0,0
SWI "OS_LeaveOS"
MOV PC,R14

.ldm_usr%
ADR R1, ldm_buf%
LDMIA R0,{R2-R3}
STMIA R1,{R2-R3}
MOV PC,R14

.stm_usr%
ADR R1, ldm_buf%
LDMIA R1,{R2-R3}
STMIA R0,{R2-R3}
MOV PC,R14

.ldm_svc%
SWI "OS_EnterOS"
ADR R1, ldm_buf%
LDMIA R0,{R2-R3}
STMIA R1,{R2-R3}
SWI "OS_LeaveOS"
MOV PC,R14

.stm_svc%
SWI "OS_EnterOS"
ADR R1, ldm_buf%
LDMIA R1,{R2-R3}
STMIA R0,{R2-R3}
SWI "OS_LeaveOS"
MOV PC,R14

.last_call%
.last_R0% EQUD 0
.last_R1% EQUD 0
.last_R2% EQUD 0
.last_R3% EQUD 0
.last_PSR% EQUD 0
.last_R12% EQUD 0

.da_base_ptr% EQUD 0

.ldm_buf% EQUD 0 : EQUD 0

.buffer%
]
NEXT pass

seed%=-TIME
PRINT "seed: ";seed%
A%=RND(seed%)

wp%=RND
da_num%=0
at_registered%=FALSE

ON ERROR PRINT REPORT$;" at ";ERL : PROCend(1)

PRINT "handler: ";~handler%
PRINT "wp: ";~wp%

DIM expected% 8

REM Offsets to test:
REM
REM Start of mapped page, middle of mapped page, end of mapped page
DATA 4096-4,4096+2048,8192-4,-1

ap%=0
REPEAT
 SYS "OS_Memory",17,ap% TO ,ap%,permissions%
 IF ap%=-1 THEN PROCend(0)
 PROCtestap
 PROCendtest
 ap%+=1
UNTIL FALSE

DEF PROCtestap
 PRINT "ap ";ap%;" permissions ";~permissions%

 REM Must be readable in SVC mode
 IF (permissions% AND &20)<>&20 THEN PRINT "Never readable?" : ENDPROC

 REM Create a sparse DA
 SYS "OS_DynamicArea",0,-1,0,-1,ap%+(1<<7)+(1<<10),da_size%,0,0,"ATTest" TO ,da_num%,,da_base%

 !da_base_ptr%=da_base%

 PRINT "da_base: ";~da_base%

 !last_R0%=0
 !last_R1%=0
 !last_R2%=0
 !last_R3%=0
 !last_PSR%=0
 !last_R12%=0

 SYS swi$,0,da_base%,da_base%+da_size%,handler%,wp%
 at_registered%=TRUE

 REM Map in the middle page. We'll perform accesses which cross from the mapped
 REM page into the unmapped area around it, to check that page access is
 REM is checked on a per-page basis instead of sending everything through our
 REM AbortTrap handler
 SYS "OS_DynamicArea",9,da_num%,da_base%+4096,4096

 REM Report memory permissions
 PROCcheckvalid

 REM Fill sparse page with test data (if writable in SVC, else just use whatever's currently there)
 IF permissions% AND &10 THEN PROCfill(da_base%+4096,4096)
 REM Fill buffer with test data
 PROCfill(buffer%,da_size%)

 REM perform the access tests
 RESTORE
 READ offset%
 REPEAT
  PROCexpected(offset%,8,4)
  A%=da_base%+offset%
  CALL ldm_usr%
  IF expected%!0<>ldm_buf%!0 OR expected%!4<>ldm_buf%!4 THEN PRINT "USR LDM ERROR @ ";~offset%;" expected ";~(expected%!0);" ";~(expected%!4);" actual ";~(ldm_buf%!0);" ";~(ldm_buf%!4) : PROCend(1)

  PROCexpected(offset%,8,32)
  A%=da_base%+offset%
  CALL ldm_svc%
  IF expected%!0<>ldm_buf%!0 OR expected%!4<>ldm_buf%!4 THEN PRINT "SVC LDM ERROR @ ";~offset%;" expected ";~(expected%!0);" ";~(expected%!4);" actual ";~(ldm_buf%!0);" ";~(ldm_buf%!4) : PROCend(1)

  ldm_buf%!0=RND
  ldm_buf%!4=RND
  A%=da_base%+offset%
  CALL stm_usr%
  PROCexpected(offset%,8,2)
  REM "expected" and "actual" are reversed here, because we're using FNexpected to read back what's been written to the memory
  IF expected%!0<>ldm_buf%!0 OR expected%!4<>ldm_buf%!4 THEN PRINT "USR STM ERROR @ ";~offset%;" expected ";~(ldm_buf%!0);" ";~(ldm_buf%!4);" actual ";~(expected%!0);" ";~(expected%!4) : PROCend(1)

  ldm_buf%!0=RND
  ldm_buf%!4=RND
  A%=da_base%+offset%
  CALL stm_svc%
  PROCexpected(offset%,8,16)
  REM "expected" and "actual" are reversed here, because we're using FNexpected to read back what's been written to the memory
  IF expected%!0<>ldm_buf%!0 OR expected%!4<>ldm_buf%!4 THEN PRINT "SVC STM ERROR @ ";~offset%;" expected ";~(ldm_buf%!0);" ";~(ldm_buf%!4);" actual ";~(expected%!0);" ";~(expected%!4) : PROCend(1)
  READ offset%
 UNTIL offset%=-1
ENDPROC

DEF PROCendtest
 IF at_registered% THEN
  PROClast
  at_registered%=FALSE
  SYS swi$,1,da_base%,da_base%+da_size%,handler%,wp%
 ENDIF
 IF da_num%<>0 THEN SYS "OS_DynamicArea",1,da_num% : da_num%=0
ENDPROC

DEF PROCend(E%)
 PROCendtest
 SYS "OS_Module",7,,rma%
 IF E% THEN ERROR EXT 0,"Failed"
 PRINT "Success"
 END
ENDPROC

DEF PROClast
 PRINT "last R0 ";~!last_R0%;" R1 ";~!last_R1%;" R2 ";~!last_R2%;" R3 ";~!last_R3%;" PSR ";~!last_PSR%;" R12 ";~!last_R12%
ENDPROC

DEF PROCfill(base%,len%)
 WHILE len%>0
  A%=base%
  B%=RND
  CALL writew_svc%
  base%+=4
  len%-=4
 ENDWHILE
ENDPROC

DEF FNexpected(addr%,access%)
 IF addr%<da_base% OR addr%>=da_base%+da_size% THEN PRINT "Bad addr ";~addr% : PROCend(1)
 addr%-=da_base%
 IF (permissions% AND access%)=access% AND addr%>=4096 AND addr%<8192 THEN A%=da_base%+addr% : =USR readb_svc%
=buffer%?addr%

DEF PROCcheckvalid
 PROCcheckpage("Low",0)
 PROCcheckpage("Mid",4096)
 PROCcheckpage("High",8192)
ENDPROC

DEF PROCcheckpage(name$,offset%)
 LOCAL flags%,access%
 SYS "OS_ValidateAddress",da_base%+offset%,da_base%+offset%+4096 TO ;flags%
 SYS "OS_Memory",24,da_base%+offset%,da_base%+offset%+4096 TO ,access%
 PRINT name$;" valid: ";((NOT flags%) AND 2);" ";~access%
ENDPROC

DEF PROCexpected(offset%,len%,access%)
 WHILE len%>0
  len%-=1
  expected%?len%=FNexpected(da_base%+offset%+len%,access%)
 ENDWHILE
ENDPROC