Rick's b.log - 2016/08/03 |
|
It is the 21st of November 2024 You are 18.116.85.108, pleased to meet you! |
|
mailto:
blog -at- heyrick -dot- eu
Why?
If you're asking that, you're new around here. There is no "Why". ☺
IMPORTANT NOTE: This has been tested on a Pi (1). It may or may not work on any other ARMv6 board. Unfortunately the page tables and MMU are one of the things that ARM has a propensity for changing, so there is no guarantee that this will work on other devices.
First, we need a few pieces of information. We need to know the address of the start of the ROM image. We need to know the location of the Level 1 page tables, and then we need to know where in the page table points to the ROM.
First up is the ROM base address. This can be easily determined by looking for the address of the first module (UtilityModule) and masking out the offset (as UtilityModule follows the hidden RISC OS module, which follows the HAL and boot code).
Now we need to determine the location of the Level 1 page table. In RISC OS 5 this is available via OS_Memory as follows:
Finally, since the Level 1 page table is linear (a word per megabyte), we need to work out the offset:
For what it is worth, the eventual address is &FAC03F00 - however you must always perform this calculation instead of making assumptions because the address was &FB403F00 in RISC OS 5.21.
Now we have an address, what do we do with it? If you enter
To understand what is here, we must now turn to an ARM datasheet for the ARMv6 architecture:
The value &0DB0880E is 1101101100001000100000001110 in binary. So let us merge the binary value into the section description, to explain what is going on:
This location may be buffered and cached (B + C = 1), but no write allocate (TEX = 0). It is not shared (S = 0), it is globally allocated (nG = 0), and my doc says "NS" should be zero, as it is.
We must always check that the lowest two bits are
So now some code to read the bit of memory, and check the page table descriptor is the correct type. It's a little bit complicated as we cannot read the descriptor in User mode, so we toddle off to assembler.
It is important to note that calling OS_EnterOS, then trying to read the word using something like
No worries. This'll do it (
To validate, then:
Now comes the good stuff. In order to make the ROM writeable, we need to clear the APX bit and set both of the AP bits.
This should yield the value &0DB00C0E, which now needs to be written back to the page table. Here's some code to do that, it's a lot like the last piece of assembler:
At this point, the first megabyte of the ROM will now be writeable. We can test this as follows:
Note that this may sometimes fail with an abort on data transfer. I think the MMU logic needs time or TLB flush or something. Trying it again usually works.
Here, then, is the full listing:
As before, homework for the user:
For the former task, you'll probably need to hit an ARM datasheet. It seems that if one splits up the page table tweaking and the checking into separate programs, doubleclicking on them individually doesn't fail. So that may be a simpler option? Filer_Run the tweaker, then Filer_Run whatever you want to write to ROM, perhaps?
Making ROMs writeable
In this little bit of geekery, we are going to do the impossible. We are going to make the ROM writeable!
Actually, it is not that complicated. You see, in all of the modern SoC ARM boards, the ROM image is loaded into a bit of memory which is then marked as being Read Only. So we just need to work out which part of the memory mapping to fiddle to Read/Write.
>SYS "OS_Module", 12, 0 TO ,,,mod%
>rom% = (mod% AND &FF000000)
>PRINT ~rom%
FC000000
>
<SYS "OS_Memory", 16 + ( 7 << 8) TO ,l1pt%
>PRINT ~l1pt%
FAC00000
>
pokeaddr% = l1pt% + ((rom% >>> 20) << 2)
We shift >>>20
to change the ROM address into a number of megabytes by shifting the value 20 places to the right. Note the use of three > symbols. This is because the original address (&FC000000) would appear as a negative number to BASIC. Had we used the usual >>
, the result would have preserved the sign bit across the shifting, leading to the invalid result &FFFFFFC0. By using >>>
, we tell BASIC to ignore the sign bit, providing us with the desired result of &FC0.
The final << 2
then shifts the address back up two places, to word align it.
Yes, we could have just shifted by 18, but this 20 then 2 method makes it clearer what is going on, if you know that 20 to the right converts to megabytes and 2 the the left word aligns.
*Memory FAC03F00 +4
, you will see the result showing the value &0DB0880E.
The important part, however, is the APX bit (which is 1) and the AP bits (which are 10). Taken together, 1 10
means that the access permission is Read only in both privileged and user mode.
10
and that bit 18 is 0
. This should be the case, as it indicates that this descriptor is a regular 1MiB section. The beauty of the ARM's MMU design is that the level 1 page table can hold different types of descriptor, such as a 16MiB "supersection", as well as entries into a second level page table. So by ensuring that the data is something we can recognise, we can reject unknown behaviour in the future or on other processors.
memword% = pokeaddr%!0
will hang the computer. BASIC doesn't much like operating in a privileged mode.
DIM code% 63
at the start):
P% = code%
[ OPT 2
SWI "OS_EnterOS"
LDR R0, [R0]
SWI "OS_LeaveOS"
MOV PC, R14
]
A% = pokeaddr%
memword% = USR(code%)
Setting A% has the side effect of setting R0. Calling the code with USR (instead of CALL) means we expect to see a result in R0 on exit. The code itself is dead easy - we enter SVC mode, we read the word pointed to by R0 into R0, we come out of SVC mode and back into USR mode, and finally return back to BASIC.
IF ( (((memword% >> 18) AND 1) <> 0) AND ((memword% AND 3) <> 2) ) THEN
PRINT "Unknown page descriptor."
END
ENDIF
memword% = memword% EOR (1 << 15) : REM Clear APX
memword% = memword% OR (3 << 10) : REM Set both AP bits
P% = code%
[ OPT 2
SWI "OS_EnterOS"
STR R1, [R0]
SWI "OS_LeaveOS"
MOV PC, R14
]
A% = pokeaddr%
B% = memword%
CALL code%
checkaddr% = rom% + &10000
check% = checkaddr%!0
IF (check% <> &6D49534F) THEN
PRINT "Unable to check ROM is writeable."
END
ENDIF
checkaddr%!0 = &4B434952
check% = checkaddr%!0
IF (check% <> &4B434952) THEN
PRINT "ROM is *NOT* writeable."
END
ENDIF
checkaddr%!0 = &6D49534F
PRINT "Success!"
END
The first part of the RISC OS image, after the HAL, is at offset +&10000. The first word is "OSIm". We temporarily change this to "RICK", check it, then change it back.
REM >ROMhack
REM Make the first megabyte of Pi 1's ROM writeable!
REM
REM By Rick Murray, 2016/08/03
REM
REM Open Source software - this code has been released under the EUPL v1.1 (only).
REM https://joinup.ec.europa.eu/community/eupl/og_page/european-union-public-licence-eupl-v11
REM
DIM code% 63
REM Work out where the ROM begins
SYS "OS_Module", 12, 0 TO ,,,mod%
rom% = (mod% AND &FF000000)
REM Read the address of the level 1 page table
SYS "OS_Memory", 16 + ( 7 << 8) TO ,l1pt%
REM Calculate our offset into the page table
pokeaddr% = l1pt% + ((rom% >>> 20) << 2)
REM Read the descriptor
P% = code%
[ OPT 2
SWI "OS_EnterOS"
LDR R0, [R0]
SWI "OS_LeaveOS"
MOV PC, R14
]
A% = pokeaddr%
memword% = USR(code%)
REM Validate the descriptor
IF ( (((memword% >> 18) AND 1) <> 0) AND ((memword% AND 3) <> 2) ) THEN
PRINT "Unknown page descriptor."
END
ENDIF
REM Give this descriptor Read/Write (all modes) permission
memword% = memword% EOR (1 << 15) : REM Clear APX
memword% = memword% OR (3 << 10) : REM Set both AP bits
REM Write it back
P% = code%
[ OPT 2
SWI "OS_EnterOS"
STR R1, [R0]
SWI "OS_LeaveOS"
MOV PC, R14
]
A% = pokeaddr%
B% = memword%
CALL code%
REM Verify it worked... this may sometimes fail so this part is
REM OPTIONAL and can be omitted if so desired.
checkaddr% = rom% + &10000
check% = checkaddr%!0
IF (check% <> &6D49534F) THEN
PRINT "Unable to check ROM is writeable."
END
ENDIF
checkaddr%!0 = &4B434952
check% = checkaddr%!0
IF (check% <> &4B434952) THEN
PRINT "ROM is *NOT* writeable."
END
ENDIF
checkaddr%!0 = &6D49534F
PRINT "Success!"
END
To help with the latter task, here's some code that will tell you how big the ROM image is:
SYS "OS_Memory", 8 + (5 << 8) TO , pagenum%, pagesize%
romsize% = (pagenum% * pagesize%)
No comments yet...
© 2016 Rick Murray |
This web page is licenced for your personal, private, non-commercial use only. No automated processing by advertising systems is permitted. RIPA notice: No consent is given for interception of page transmission. |