Calling "Higher Half" Code from Lower Half

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
mttarry
Posts: 6
Joined: Thu May 12, 2022 6:04 pm
Libera.chat IRC: Feemster

Calling "Higher Half" Code from Lower Half

Post by mttarry »

After setting up my higher half kernel and enabling paging, I started designing my PMM. Then I realized this is a bit backwards. So now I want to use my PMM to allocate memory for my paging structures. But in my PMM, I need to use functions that are mapped to the higher half (namely, memcpy() and memset()). So calling them outright before enabling paging causes a jump to unallocated memory because the MMU is not performing translations yet. As a workaround, I created a function pointer as such:

typedef void (*memset_func_ptr)(void *, const void *, size_t);
And set it to the VA of memset minus the higher half offset:
memset_func_ptr memset_lowerhalf = (void*)((uintptr_t)&memset - HIGHER_HALF_START_ADDR);

However I feel like this is bad design and leads to less readable code. Have any of you come up with a way to simplify interactions between the lower half and higher half? It just feels to me like setting up the linker file such that the bulk of the kernel is mapped to a virtual address is premature because paging hasn't been enabled yet. But maybe the purpose of this is for debugging the ELF binary? I'm not sure. Any insight would be greatly appreciated.
nullplan
Member
Member
Posts: 1779
Joined: Wed Aug 30, 2017 8:24 am

Re: Calling "Higher Half" Code from Lower Half

Post by nullplan »

Honestly, the cleanest approach is probably going to be to have the lower half and higher half in different executables. This is what I'm doing to avoid such problems, but then, I have to since I am developing a 64-bit OS (and the lower-half code must be 32-bit). In my case, the loader kernel gets the memory info from multiboot, puts itself in the reserved section, puts the main kernel in the reserved section, and then allocates all the paging structures. At the end, it removes itself from the reserved section and hands those structures in a defined way to the higher-half kernel. In this way, there is no added memory footprint since the loader kernel frees itself at the end.

If you insist on continuing on your path, you can probably automate the process somewhat with macros. Such as (untested)

Code: Select all

#define lower_half(x) ((__typeof__(&x))((uintptr_t)(x) - HIGHER_HALF_START_ADDR))
//and then you use it like:
lower_half(memcpy)(dest, src, len);
But I wouldn't want to use such an approach, because the compiler can emit undecorated calls to memcpy/memmove/memset literally everywhere. So that is why I would suggest separate executables. I think I remember Linux doing something similar, with a boot section that is separate from the rest of the kernel.
Carpe diem!
mttarry
Posts: 6
Joined: Thu May 12, 2022 6:04 pm
Libera.chat IRC: Feemster

Re: Calling "Higher Half" Code from Lower Half

Post by mttarry »

nullplan wrote:Honestly, the cleanest approach is probably going to be to have the lower half and higher half in different executables. This is what I'm doing to avoid such problems, but then, I have to since I am developing a 64-bit OS (and the lower-half code must be 32-bit). In my case, the loader kernel gets the memory info from multiboot, puts itself in the reserved section, puts the main kernel in the reserved section, and then allocates all the paging structures. At the end, it removes itself from the reserved section and hands those structures in a defined way to the higher-half kernel. In this way, there is no added memory footprint since the loader kernel frees itself at the end.

If you insist on continuing on your path, you can probably automate the process somewhat with macros. Such as (untested)

Code: Select all

#define lower_half(x) ((__typeof__(&x))((uintptr_t)(x) - HIGHER_HALF_START_ADDR))
//and then you use it like:
lower_half(memcpy)(dest, src, len);
But I wouldn't want to use such an approach, because the compiler can emit undecorated calls to memcpy/memmove/memset literally everywhere. So that is why I would suggest separate executables. I think I remember Linux doing something similar, with a boot section that is separate from the rest of the kernel.

This is very helpful, thank you!
Post Reply