Page 1 of 1

problems with setting up paging

Posted: Mon Aug 22, 2022 4:41 pm
by Schol-R-LEA
I am working on setting up paging in my Ordo design, and have run into a problem. As things stand, I can get the transition into p-mode to work correctly if I have paging disabled, but when I set up the page directory and a few relevant page tables (while still in real mode), setting CR3 to the page directory, and then transitioning to p-mode with the paging bit set, it seems to jump into hyperspace.

The code which calls the paging initialization and transfers to p-mode is:

Code: Select all

promote_pm:
        call init_page_directory

        mov eax, cr0
        or eax, Protection | Paging ; set PE (Protection Enable) and Paging bits in CR0 (Control Register 0)
        mov cr0, eax

        ; Perform far jump to selector 08h (offset into GDT, pointing at a 32bit PM code segment descriptor) 
        ; to load CS with proper PM32 descriptor)
        jmp system_code_selector:PModeMain
(Where Protection is 0x00000001 and Paging is 0x80000000.)

The code for initializing the page tables and page directory is:

Code: Select all

%ifndef _PAGING_CODE__INC__
%define _PAGING_CODE__INC__

bits 16

align 4096
page_directory     resw 0x0400

page_table_0       resw 0x0400
page_table_768     resw 0x0400

page_table_1023    resw 0x0400

init_page_directory:
        mov bx, page_directory
        memset_rm 0, 0x0400, bx               ; clear the page dir table

        ; start by setting up the base page table
        mov bx, page_table_0                  ; get index into the base page table
        memset_rm 0, 0x0400, bx               ; clear the table entries
        ; entries 0-1024 - identity mapping the first 1 MiB of memory
        mov cx, 0x0100                        ; 256 entries * 4KiB = 1 MiB
        mov eax, PTE_Page_Index_Mask | PTE_Present
    .pt_0_fill:
        mov [bx], dword eax
        add eax, 0x1000
        add bx, 4
        loop .pt_0_fill

        ; set up the kernel code table
        mov bx, page_table_768                ; get index into the kernel code page table
        memset_rm 0, 0x0400, bx               ; clear the table entries
        ; entries 0-4096 - mapping the start of higher half
        mov cx, 0x0010                        ; 8 entries * 4KiB = 32 KiB
        mov edx, PTE_Page_Index_Mask | PTE_Present
        add edx, 0x100000
    .pt_768_fill:
        mov [bx], dword edx
        add edx, 0x1000
        add bx, 4
        loop .pt_768_fill

        ; set up the kernel stack table
        mov bx, page_table_1023               ; get index into the kernel stack page table
        memset_rm 0, 0x0400, bx               ; clear the table entries
        ; entries 0-4096 - mapping the start of higher half
        mov cx, 0x0004                        ; 4 entries * 4KiB = 16KiB
        mov edx, PTE_Page_Index_Mask | PTE_Present
        add edx, 0xffffff00
    .pt_1023_fill:
        mov [bx], dword edx
        add edx, 0x1000
        add bx, 4
        loop .pt_1023_fill


    .setup_directory:
        mov bx, page_directory
    .pd_fill:
        mov eax, page_table_0
        shl eax, 12
        or eax, PDE_Present
        mov [bx], eax
        add bx, 768 * 4
        mov eax, page_table_768
        shl eax, 12
        or eax, PDE_Present
        mov [bx], eax
        add bx, 1023 * 4
        mov eax, page_table_1023
        shl eax, 12
        or eax, PDE_Present
        mov [bx], eax


        ; set the page directory
        mov eax, page_directory
        mov cr3, eax
        ret


%endif
I am following the wiki entry on paging regarding the location of the start of higher half (i.e., page table 768).

The constants used by the paging init routine are:

Code: Select all

%ifndef _PAGING__INC__
%define _PAGING__INC__


%define PDE_Present           0b00000000000000000000000000000001
%define PDE_Read_Write        0b00000000000000000000000000000010
%define PDE_User              0b00000000000000000000000000000100
%define PDE_Write_Thru        0b00000000000000000000000000001000
%define PDE_Cache_Disable     0b00000000000000000000000000010000
%define PDE_Acccessed         0b00000000000000000000000000100000
%define PDE_Dirty             0b00000000000000000000000001000000
%define PDE_Page_Size         0b00000000000000000000000010000000
%define PDE_Global            0b00000000000000000000000100000000
%define PDE_Availability_Mask 0b00000000000000000000111000000000
%define PDE_Page_Attr_Table   0b00000000000000000001000000000000
%define PDE_Page_Index_Mask   0b11111111111111111110000000000000


%define PTE_Present           0b00000000000000000000000000000001
%define PTE_Read_Write        0b00000000000000000000000000000010
%define PTE_User              0b00000000000000000000000000000100
%define PTE_Write_Through     0b00000000000000000000000000001000
%define PTE_Cache_Disable     0b00000000000000000000000000010000
%define PTE_Acccessed         0b00000000000000000000000000100000
%define PTE_Dirty             0b00000000000000000000000001000000
%define PTE_Page_Attr_Table   0b00000000000000000000000010000000
%define PTE_Global            0b00000000000000000000000100000000
%define PTE_Availability_Mask 0b00000000000000000000111000000000
%define PTE_Page_Index_Mask   0b11111111111111111111000000000000

%endif
I can only imagine that the logic of my fill loops is flawed, but I am unclear as to how.

Re: problems with setting up paging

Posted: Mon Aug 22, 2022 4:52 pm
by Octocontrabass
Schol-R-LEA wrote:

Code: Select all

page_directory     resw 0x0400
That's not 4096 bytes.

Re: problems with setting up paging

Posted: Mon Aug 22, 2022 5:07 pm
by Schol-R-LEA
Gah, no, it isn't I meant to have it as resd, not resw, I have no idea how I made that mistake.

However, I now have another problem, which I will need to solve before I proceed: the second stage loader is now too large to fit in the first segment after the boot sector. This isn't the first time I've run into this issue, and there are a few ways I can resolve it before getting back to the immediate problem. In the short term, I will probably move the page directory and page tables to a separate segment, which is what I've already done to the kernel code loading (because having a 16KiB block of memory assigned for that out of the 20KiB available turned out to be a problem).

If this limitation keeps cropping up, though, the long-term solution may well be to change the first stage boot loader so that it loads everything to another, otherwise unused segment higher in memory.

Re: problems with setting up paging

Posted: Mon Aug 22, 2022 5:56 pm
by Octocontrabass
You've only hit that limitation because your second stage binary is more padding than code. Open it in a hex editor and see for yourself.

Plus, you've already finished loading the kernel binary, right? Why not switch to protected mode (or at least unreal mode) before you set up your page tables? Then you won't need to worry about segments.

Re: problems with setting up paging

Posted: Mon Aug 22, 2022 6:18 pm
by Schol-R-LEA
Octocontrabass wrote:You've only hit that limitation because your second stage binary is more padding than code. Open it in a hex editor and see for yourself.
That's true, and it is something I have considered. I did move the page tables to a separate segment, now, though the current result is that it is triple-faulting.
Octocontrabass wrote:Plus, you've already finished loading the kernel binary, right? Why not switch to protected mode (or at least unreal mode) before you set up your page tables? Then you won't need to worry about segments.
That's a fair point, actually. I'd have to pivot a bit, but that may be the better solution.

Re: problems with setting up paging

Posted: Mon Aug 22, 2022 7:36 pm
by Schol-R-LEA
I've moved the paging setup to after the p-mode transition, but it still doesn't seem to be working - the code which is supposed to immediately follow it doesn't run. The current version of the code is:

Code: Select all

PModeMain:
        ; set the segment selectors
        mov ax, system_data_selector
        mov ds, ax
        mov ss, ax
        mov es, ax
        mov fs, ax
        mov gs, ax
        mov esp, 0x00090000

        call init_page_directory
        mov eax, cr0
        or eax, Paging           ; set Paging bit in CR0 (Control Register 0)
        mov cr0, eax

    ;    call clear_screen

        ; write 'Kernel started' to text buffer
        write32 kernel_start, 7

;;; halt the CPU
halted:
    .halted_loop:
        hlt
        jmp short .halted_loop
and

Code: Select all

%ifndef _PAGING_CODE__INC__
%define _PAGING_CODE__INC__

%line 0, "paging.asm"
bits 32

page_directory           equ 0xffff0000
page_table_0             equ page_directory + 0x1000
page_table_768           equ page_directory + 0x2000
page_table_1018          equ page_directory + 0x3000
page_table_1023          equ page_directory + 0x4000
init_page_directory:
        mov ebx, dword page_directory
        memset32 0, 0x0400, ebx                ; clear the page dir table

        ; start by setting up the base page table
        mov ebx, dword page_table_0            ; get index into the base page table
        memset32 0, 0x0400, ebx                ; clear the table entries
        ; entries 0-1024 - identity mapping the first 1 MiB of memory
        mov ecx, 0x0100                        ; 256 entries * 4KiB = 1 MiB
        mov eax, PTE_Page_Index_Mask | PTE_Present
    .pt_0_fill:
        mov [ebx], dword eax
        add eax, 0x1000
        add ebx, 4
        loop .pt_0_fill

        ; set up the kernel code table
        mov ebx, page_table_768                ; get index into the kernel code page table
        memset32 0, 0x0400, ebx                ; clear the table entries
        ; entries 0-4096 - mapping the start of higher half
        mov ecx, 0x0010                        ; 8 entries * 4KiB = 32 KiB
        mov edx, PTE_Page_Index_Mask | PTE_Present
        add edx, 0x100000
    .pt_768_fill:
        mov [ebx], dword edx
        add edx, 0x1000
        add ebx, 4
        loop .pt_768_fill

        ; set up the page table mapping
        mov ebx, page_table_1018               ; get index into the kernel stack page table
        memset32 0, 0x0400, ebx                ; clear the table entries
        ; entries 0-4096 - mapping the start of higher half
        mov ecx, 0x0008                        ; 8 entries * 4KiB = 32KiB
        mov edx, PTE_Page_Index_Mask | PTE_Present
        add edx, 0xffff0000
    .pt_1018_fill:
        mov [ebx], dword edx
        add edx, 0x1000
        add ebx, 4
        loop .pt_1018_fill

        ; set up the kernel stack table
        mov ebx, page_table_1023               ; get index into the kernel stack page table
        memset32 0, 0x0400, ebx                ; clear the table entries
        ; entries 0-4096 - mapping the start of higher half
        mov ecx, 0x0004                        ; 4 entries * 4KiB = 16KiB
        mov edx, PTE_Page_Index_Mask | PTE_Present
        add edx, 0xffffff00
    .pt_1023_fill:
        mov [ebx], dword edx
        add edx, 0x1000
        add ebx, 4
        loop .pt_1023_fill


    .setup_directory:
        mov ebx, page_directory
    .pd_fill:
        mov eax, page_table_0
        shl eax, 12
        or eax, PDE_Present
        mov [ebx], eax
        add ebx, 768 * 4
        mov eax, page_table_768
        shl eax, 12
        or eax, PDE_Present
        mov [ebx], eax
        add ebx, 1018 * 4
        mov eax, page_table_1018
        shl eax, 12
        or eax, PDE_Present
        mov [ebx], eax
        add ebx, 1023 * 4
        mov eax, page_table_1023
        shl eax, 12
        or eax, PDE_Present
        mov [ebx], eax


        ; set the page directory
        mov eax, page_directory
        mov cr3, eax
        ret


%endif

Re: problems with setting up paging

Posted: Mon Aug 22, 2022 7:41 pm
by Octocontrabass
Schol-R-LEA wrote:

Code: Select all

page_directory           equ 0xffff0000
Try putting your page tables in RAM instead of the firmware ROM.

Re: problems with setting up paging

Posted: Mon Aug 22, 2022 8:06 pm
by Schol-R-LEA
OK, good point. Though I am not certain where system firmware is located on a modern system, so... yeah. I probably need to consult the documentation, as well as the memory map I went to such trouble to collect earlier.

sigh I think I am thrashing a bit at this point. I may need to step away from the project for a while.

I did try to place the pages at 0xb000000 instead, just to see if it made a difference, but no change. I wasn't certain how to compute where that would be in the page directory when I went to map the page tables themselves. I need to work out just where I can and should place the stack and page tables where it would be safe.

Re: problems with setting up paging

Posted: Mon Aug 22, 2022 9:44 pm
by nullplan
You know, building a simple allocator from the memory map is not very hard. Here's mine from the boot loader.

Code: Select all

struct memblock {
    uint32_t start, len;
};
static struct memblock memavail[32];
static struct memblock memreserved[64];
static size_t navail, nreserved;

static void add_range_to_memblock(struct memblock* arr, size_t *n, size_t limit, uint32_t start, uint32_t len)
{
    size_t i;
    for (i = 0; i < *n; i++)
        if (arr[i].start > start)
            break;
    /* can we just add this to the preceding block? */
    if (i && arr[i-1].start + arr[i-1].len == start)
        arr[i-1].len += len;
    /* can we extend the current block? */
    else if (i < *n && start + len == arr[i].start)
    {
        arr[i].start = start;
        arr[i].len += len;
    }
    else if (*n < limit)
    {
        if (i < *n)
            memmove(arr + i + 1, arr + i, (*n - i - 1) * sizeof (struct memblock));
        arr[i].start = start;
        arr[i].len = len;
        ++*n;
    }
    else
    {
        dbg_printf("Out of memory adding block at %x\n", start);
        halt();
    }
}

static void add_avail_range(uint32_t addr, uint32_t len)
{
    add_range_to_memblock(memavail, &navail, sizeof memavail / sizeof *memavail, addr, len);
}

static void add_reserved_range(uint32_t addr, uint32_t len)
{
    add_range_to_memblock(memreserved, &nreserved, sizeof memreserved / sizeof *memreserved, addr, len);
}
static void *alloc(size_t sz, size_t algn)
{
    size_t avidx, residx = 0;
    for (avidx = 0; avidx < navail; avidx++)
    {
        uint32_t start, len;
        while (memreserved[residx].start + memreserved[residx].len < memavail[avidx].start && residx < nreserved)
            residx++;
        while (residx < nreserved && memreserved[residx].start < memavail[avidx].start + memavail[avidx].len)
        {
            start = memavail[avidx].start;
            if (memreserved[residx].start + memreserved[residx].len > start)
                start = memreserved[residx].start + memreserved[residx].len;
            len = memavail[avidx].start + memavail[avidx].len - start;
            if (residx < nreserved && memreserved[residx+1].start - start < len)
                len = memreserved[residx+1].start - start;
            if (((start + algn - 1) & -algn) + sz <= start + len)
            {
                start = (start + algn - 1) & -algn;
                add_reserved_range(start, sz);
                dbg_printf("Allocating address %8x\n", start);
                return (void*)start;
            }
        }
    }
    return 0;
}

static void *page_alloc_or_die(void)
{
    void* ret = alloc(0x1000, 0x1000);
    if (!ret)
        die("Out of memory!\n");
    return memset(ret, 0, 0x1000);
}
Not a very good allocator, but enough to set up 64-bit page tables from 32-bit mode.

Re: problems with setting up paging

Posted: Mon Aug 22, 2022 9:58 pm
by Octocontrabass
Schol-R-LEA wrote:I am not certain where system firmware is located on a modern system, so...
After power-on or reset, the CPU begins executing code at physical address 0xFFFFFFF0, so there's always firmware ROM in that general area. It'll be listed in your memory map as "reserved".
Schol-R-LEA wrote:I did try to place the pages at 0xb000000 instead, just to see if it made a difference, but no change.
That address is awfully high; you would have to use your memory map to see if it's available even if your VM is configured for enough memory. And even if it's available, I suspect there are other problems. Dumping the paging structures (to the screen or in your debugger) before using them might help.
Schol-R-LEA wrote:I wasn't certain how to compute where that would be in the page directory when I went to map the page tables themselves.
I've found this page to be very helpful for visualizing x86 page table structures. To work out which entry in your page directory and page table correspond to a virtual address, you can use the "virtual address translation (2-level and 3-level)" chart. The top row corresponds to what you're using now, which is 4kB pages in 2-level translation.

But that assumes you're identity mapping the page tables, which isn't required - you can map them wherever you like. You can even enable paging without mapping the page tables at all.
Schol-R-LEA wrote:I need to work out just where I can and should place the stack and page tables where it would be safe.
Unless your target is extremely old PCs, there's about half a megabyte of conventional memory that you haven't used for anything yet.

Re: problems with setting up paging

Posted: Mon Aug 22, 2022 10:52 pm
by Schol-R-LEA
Octocontrabass wrote:
Schol-R-LEA wrote:I am not certain where system firmware is located on a modern system, so...
After power-on or reset, the CPU begins executing code at physical address 0xFFFFFFF0, so there's always firmware ROM in that general area. It'll be listed in your memory map as "reserved".
I should have known that, I am slightly peeved with myself that it didn't occur to me until you mentioned it.
Octocontrabass wrote:
Schol-R-LEA wrote:I did try to place the pages at 0xb000000 instead, just to see if it made a difference, but no change.
That address is awfully high; you would have to use your memory map to see if it's available even if your VM is configured for enough memory. And even if it's available, I suspect there are other problems. Dumping the paging structures (to the screen or in your debugger) before using them might help.
I tried putting the page directory and the four stub page tables at 0x00200000, and that worked. Well, after I fixed some faults in how I was filling the page directory, that is.
Octocontrabass wrote:
Schol-R-LEA wrote:I wasn't certain how to compute where that would be in the page directory when I went to map the page tables themselves.
I've found this page to be very helpful for visualizing x86 page table structures. To work out which entry in your page directory and page table correspond to a virtual address, you can use the "virtual address translation (2-level and 3-level)" chart. The top row corresponds to what you're using now, which is 4kB pages in 2-level translation.

But that assumes you're identity mapping the page tables, which isn't required - you can map them wherever you like. You can even enable paging without mapping the page tables at all.
The charts will still probably help regardless. thank you.
Octocontrabass wrote:
Schol-R-LEA wrote:I need to work out just where I can and should place the stack and page tables where it would be safe.
Unless your target is extremely old PCs, there's about half a megabyte of conventional memory that you haven't used for anything yet.
I've been trying to avoid putting much in conventional memory, but that's a valid point. I'm not sure where this hesitancy came from, either, as there's no reason for it. I just tried putting them at 256KiB and it worked like a charm, which also means I can remove one of the page table entries. Two, actually, since I decided to move the stack to 0xC03fffff so it would be within the same page table as the kernel code (for the time being).