#limine on arm progress
1 messages ยท Page 1 of 1 (latest)
yoo
so at least the bug is shared between kvm and bare metal
which makes it potentially easier to deebug
can you send me source code?
i can rebase my local fixups and push to my fork ig
Anything for non-pi?
this is not Pi specific
it's uefi
it's just that the only uefi arm64 hw qookie has is a pi4
pi4
lmfao
try the up arrow
neve tgonna give you up
am on phone
long press on an old msg
still quote annoying
what's that you got connected to the RPI on the right side?
usb uart adapter
did you make it yourself
no it's just a generic ftdi breakout board
could you send me a link to buy it, my old adapter sucked
i bought it a long while ago from china
I google up "ftdi breakout board"?
use another Pi as an adapter
if you build a single level shifrr
you can use an Arduino too
don't got another one
and I blew my arduino up
if you want serial rx only you don't even need a level shifter
yeah
beware of one thing though, if you are on windows, the official ftdi driver will brick clone chips by overwriting their vendor and product id
how do I know if I have a clone chip (since I'm on windows rn)
idr how it detects them but i remember hearing about it a while ago
have you tried running florence? ๐
i haven't, and i'm working on limine trunk which doesn't have stivale2 anymore
okay so the glitchy output is because the framebuffer is mapped as write-back
I don't get it
Nvm i got it
AMAXING!
update on that, i have implemented large page splitting and should be good to go to have the proper caching mode for the framebuffer
hopefully i got it right on the first try but i wouldn't be surprised if it just fucks up the pts completeely
and indeed, it doesn't work even in qemu tcg
it reaches the kernel and then fails to print anything
wtf?
aarch64 does so many aborts its a criminal according to federal us law now
yeah but a synchronous external abort on an instruction fetch?
while still in limine (after switching pts in el1 i presume)
with tcg too
(can't do -d int with kvm, same as on x86)
ig i just accidentally tried to execute code from mmio
okay that was a really dumb mistake
uint64_t old_virt = virt & (old_page_size - 1);
``` vs ```c
uint64_t old_virt = virt & ~(old_page_size - 1);
lets see if this still works on real hw
lets see if this still works on real hw
the logic for it is pretty generic too
okay great, screen output is no longer messed up on my pi4
last thing that remains is seeing if smp works or not
since it used to be broken but maybe it got fixed along with everything else
okay so it doesn't die a horrible death, but the ap doesn't seem to make it into the kernel eithere
although it makes it far enough that limine says it succeeded
smp: BSP MPIDR is 0x0
smp: Found candidate AP for bring-up. Interface no.: 0x1, MPIDR: 0x1
smp: Successfully brought up AP
smp: Found candidate AP for bring-up. Interface no.: 0x2, MPIDR: 0x2
smp: Successfully brought up AP
smp: Found candidate AP for bring-up. Interface no.: 0x3, MPIDR: 0x3
smp: Successfully brought up AP
if this is in asm check your atomics
the ap sets the boot flag correctly
otherwise limine would complain that it failed to start
but it doesn't seem to make it into the kernel
since i added ap startup to the test kernel, where the aps just print some text to the screen
inb4 it gets stuck in the __atomic_store_n
oh, seq_cst __atomic_store_n uses ll/sc and not stlr
so i imagine the ldar on the other side just doesn't notice it
hm no i am saying retarded things
yeah no it uses ldar/stlr too, ll/sc is for fetch_add
okay update, so the issue with limine not crashing in get_memmap anymore was because i wasn't calling it anymore :^)
so i still have that to debug
bruv
at least i know whereabout it's broken ig
@weak panther I'd map the framebuffer using 0b00100010
where 0b0010 is WT R alloc transient
so it only takes a cacheline if you read from it
otherwise uncacheable
the proper way to map it is WB and flushing after each cacheline sized chunk tho ofc
but that's not part of the boot proto
my suspicion is that after setting SCTLR.M=0 (regardless of .C value since both mean uncached), the cpu stops looking into the dcache, because nothing can possibly be committed to it anymore
similar to how x86 cpus handle uncacheable memory that comes from mtrrs (as per intel's manual)
so the plan is to just flush the entire dcache before turning off the mmu before reclaiming uefi memory
and yes, adding uc mtrrs is one place where you can lose coherence between the caches and ram on x86, the intel manual says you have to flush the cache backing the memory before marking it as uc in the mtrr
that sounds fun
honestly quite incredible ```armasm
mrs x0, clidr_el1
bfxil x0, x0, #3, #24
add x0, x0, x0 lsl #1
x0 = clidr_el1
x0 = (x0 >> 24) & ((1 << 3) - 1)
x0 = x0 + x0 << 1
bfm ๐
quite risc indeed
I think you're right but I also believe it doesn't apply to icache
but yes I believe dcache is disabled by setting M=0
well icache can't have data that's not in the dcache or ram
I mean ๐คทโโ๏ธ
it can be out of date but not ahead as in
oh that's what you meant sure
i am currently reverse engineering linux asm to figure out how it flushes the whole dcache :^)
it goes through all the cache levels, and if they're data or unified it computes the line size, max way and index and does an invalidate by set/way for each index and way combination
hmmmm this was dropped in a later patch, citing that it's impossible to actually do properly
hmmmmmm i wonder, what if i just create alternative page tables like the x86_64 code does
that seems easier frankly
note, that if you dont flush the d-cache, but then modify the paging tables to say something shouldnt be catched
youll get an unexpected cache hit exception!
turns out, arm always checks the d-cache, even if the pagetables and C bit say not to
those bits only control if a line-fill happens!
huh
the behavior i'm observing (or at least i think i am observing) seems to suggest that after i turn the mmu (and caches by extension) off, reads of data that was in the cache but hasn't been written to ram yield data from ram and not cache
since i enter the function fine, i disable caches, i can call other functions after that point, but returning out of the function that disabled the caches fails with a jump to a bogus address
like ```c
void foo() {
sctlr.{m, c, i} = {0, 0, 0}
print("caches off");
} // crash
some of what i said may only apply to certain implementations
i'm currently making it so that the code creates new page tables, so that it can keep caches enabled
and i have to create new page tables because the uefi ones are stored in uefi reclaimable memory, and when limine tries to reclaim it, it fucks them up
ah, ive not interacted with efi or aarch64 much yet
most of my work has been baremetal arm32
this is what made pitust give up on his attempt to port limine to aarch64
since he didn't manage to figure that out
and like, this was enough to upset it even when running in qemu
one thing to note, is that qemu ignores all cache control flags, and everything is always coherent
more specifically, qemu just doesnt have any cache
mmio always traps and emulates
ram load/store benefits from the host cache
and kvm emulates caches somewhat but not fully
so like disabling caches will break atomics, but wrong device memory type doesn't matter
i also took a quick look over some uefi stuff, and it looks like uefi starts with either identity paging or no mmu?
and if your switching from identity->higherhalf, then you dont want all of the relocation patches to be applied to your kernel...
afaicu, the uefi spec says you are entered with the mmu enabled, and the page size is guaranteed to be 4K (if i understood the spec right)
in this case i'm transitioning from the uefi identity mapping to my own identity mapping
because there's still some code to run between limine reclaiming uefi memory (incl uefi page tables) and limine entering the kernel
but you also need to call SetVirtualAddressMap() at some point, when you leave identity mode
we're doing this long after ExitBootServices()
if you want the runtime services to survive
x86 code doesn't seem to care about it so i'm not gonna either for now
or well, the page tables limine gives to the kernel have an identity mapping as well as a higher half mapping
whatever the kernel does then is up to it
i was thinking along the lines of just taking an existing kernel, and running it thru a pe32 linker rather then an elf linker, and generation relocations
and then skip the start.S stage, and just call main() directly
but if the entire kernel gets relocated by the firmware, to suit the identity mapping, you break yourself when going into higher-half mode
you could go the gnuefi route, generate a pe32 header in assembly, implement the elf relocator yourself, then objcopy the elf into a flat binary
linux avoids the entire relocation mess, by using the MMU to relocate the code
so uefi starts the gnuefi _start, which calls the elf relocator and then calls the elf entry
i've no clue how gnuefi relocates stuff (afaict it just parses _DYNAMIC?)
but from what ive seen, it seems like its always best to have an efi stub?
well when compiled for uefi, limine is a full blown uefi program until it's time to enter the kernel
yeah, and thats where LK/linux differ, they are the kernel
it does a bunch of last minute preparations, exits boot services, reclaims uefi memory then enters the kernel
i was thinking, could i make a single pe32 binary, that is both the uefi application AND the kernel?
for booting linux from limine you can just do efi chainloading until i bother to implement the linux boot proto
but when the firmware loads that binary, it will apply relocations to make it runnable at some random load addr, in the identity mapping
and when i try to switch to higher-half mode, it will break
hmm
ig you could try doing that by combining 2 executables
one lower half and one higher half
and transitioning from one to the other in the middle
and at that point, you can just make an efi stub
a tiny uefi application, that will ExitBootServices, map the real kernel at a higher addr, turn on the mmu, and jump over
true
and the real kernel is linked differently, for that addr
each chunk has its own version of libc
but the stub could also just lack any print, which greatly reduces the deps
u-boot has a good example of that, one sec
and as a hint about what they are doing, this file even contains a memset and memcpy implementation
so its likely being linked fully stand-alone
but i'm also seeing a lot of signs of x86 in here
yeah the code segment stuff is kinda odd
* U-Boot only works in 32-bit mode at present, so when booting from 64-bit
* EFI we must first change to 32-bit mode. To do this we need to find the
* correct code segment to use (an entry in the Global Descriptor Table).
i think this is a 64bit efi app, that then drops to 32bit mode to launch the real uboot?
and then uboot may go back to 64bit, depending on the final kernel?
kinda similar to what limine does. limine runs in whatever mode uefi left it for the most part, except for when it's time to enter the kernel
when in 64-bit mode, it drops to 32-bit mode so that the code to enter the kernel can set up a well known state without being different on different bitnesses
what does uefi wind up doing, if it can potentially support 2 bit sizes?
can a 64bit uefi run a 32bit app?
not sure, but limine supports being compiled for uefi x86_64 and uefi ia32 (and uefi aarch64 once it's done
)
but on x86, the common kernel entry code is always run in 32-bit protected mode
even if the rest of limine is 64-bit
and that common code then sets up 64-bit long mode again, this time with everything well known
and also, some netbooks or whatever have a 64-bit cpu, but a 32-bit uefi firmware
which is why uefi-ia32 still supports 64-bit kernels (and checks if the cpu supports 64-bit mode at runtime)
my rough understanding, is that GetMemoryMap tells you the entire memory map
normal ram
runtime services ram?
and probably special mmio ranges?
SetVirtualAddressMap() is then used to tell the firmware what virtual address your moving the runtime services code to
and then you can create your own paging tables, and switch to them
and then run your kernel from wherever you want
and the firmware will automatically patch the set of function pointers for the runtime services?
i don't really know how the runtime services work
as far as i'm aware, the limine boot protocol doesn't say anything about them
from what ive been able to gather, the runtime services is just a chunk of native code, that is declared in the memory map
when you switch to proper virtual mode, you must assign it a virtual address, update the map, and inform the firmware with SetVirtualAddressMap()
the firmware will then patch itself, to assume its now at that base addr
and you must now only call it via that addr
it will also patch the table of runtime service function pointers
and that then provides you with some basic functions:
get/set efi vars
reboot/shutdown
get/set time
interesting
hmm, lets see if smp just happens to work now or if i need to debug it some more
[root@system76:~]# efibootmgr -v
BootCurrent: 0004
Timeout: 1 seconds
BootOrder: 0004,0000,0001,0002,0003
Boot0000* ubuntu VenHw(99e275e7-75a0-4b37-a2e6-c5385e6c00cb)
Boot0001* UEFI:CD/DVD Drive BBS(129,,0x0)
Boot0002* UEFI:Removable Device BBS(130,,0x0)
Boot0003* UEFI:Network Device BBS(131,,0x0)
Boot0004* UEFI OS HD(1,GPT,27c99b08-455d-4dfe-a44f-6150cbc09ef8,0x800,0x100000)/File(\EFI\BOOT\BOOTX64.EFI)..BO
this linux tool will then have the kernel call into the runtime services, in this case to print the efi vars
but the tool can also modify them
BootCurrent tells you that the currently running os, was loaded by Boot0004
BootOrder is the order the firmware will try each option in
HD(1,GPT,27c99b08-455d-4dfe-a44f-6150cbc09ef8,0x800,0x100000)/File(\EFI\BOOT\BOOTX64.EFI)
this says to search a GPT disk for a given PARTUUID:
[root@system76:~]# blkid /dev/nvme0n1p1
/dev/nvme0n1p1: UUID="7DBC-2698" BLOCK_SIZE="512" TYPE="vfat" PARTUUID="27c99b08-455d-4dfe-a44f-6150cbc09ef8"
and then run a specific path on that partition:
-rwxr-xr-x 1 root root 136K Mar 1 10:53 /boot/EFI/BOOT/BOOTX64.EFI
so in thoery, each OS can be its own entry in the efi vars, and you can change the boot order with the runtime services
and via BootCurrent, you know which one is yourself, so you can edit your own entry and not mess up others
quite unfortunate, smp is still borked
the cores wakes up and enter the limine stub, but when the kernel then tries to gain control of them, they don't make it
did you setup the paging tables? and discard the L1's?
the core seems to manage to enable the mmu, since if it failed, core 0 would time out waiting for it to set a flag and panic
i'll look into it later, it's nearly 4 am and i should go to bed
heh, i always do that
so, for tomorrow what's left: debug smp and rebase onto limine trunk
and it should be good to submit as an initial support pr
dtb support and linux boot proto can come at a later time
arm linux is easy
yeah its not difficult, apart from the fact that it wants to be placed as close to start of ram as possible
and having to patch the dtb to set cmdline and initrd pointers
thats easy, and i have example code
255-270 is probably the most complex part, filling in the memory map and ranges
i could probably make it so that linux boot proto is only supported if uefi gives us the dtb (otherwise just go use efi chainloading)
and inb4, you can get acpi in place of a dtb
the pi4 uefi can give you both acpi and dtb
yeah its configurable, but the arm systemready sr specification explicitly prohibits dtbs
another thing, is that every time i bring up efi infront of the open source guys, they freak out and say its not needed ๐
the hard-core ones, that complain about all of the bloat in tianocore and having even a single byte not backed by source ๐
What gets me is the remaining blobs needed to boot a rockchip system
Looks like there's quite a bit of magic ram timing tools and magic firmware formats that are undocumented
Which is fine if uboot somehow wraps that in a gpl compatible way or whatever, but what about tianocore
I found some uefi port for this board I have, but no uboot source, so ๐คทโโ๏ธ
i think arm tianocore is usually just appended to an arm trusted firmware build
and ATF is a multi-stage system, where one of the stages is ram-init
it doesn't need to
since limine's default pagemap preserves the identity map, you can just call the runtime services via the system table
including the call to relocate to a new pagemap
Oh interesting, I just never reclaim it
I just pass it on as bootloader reclaimable
yeah limine reclaims it and passes it as available