Page 1 of 1

Paging initialization in x86_64

Posted: Mon Dec 02, 2024 9:49 pm
by gamingjam60
I am using [limine][https://github.com/limine-bootloader/limine] bootloader to load x86_64 Kernel. I know limine starts a basic Paging system but I want to load my own custom Paging System by following way :

[paging.h]

Code: Select all

// Function to extract parts of a virtual address
#define PML4_INDEX(va)   (((va) >> 39) & 0x1FF)  // Bits 39-47
#define PDPT_INDEX(va)   (((va) >> 30) & 0x1FF)  // Bits 30-38
#define PD_INDEX(va)     (((va) >> 21) & 0x1FF)  // Bits 21-29
#define PT_INDEX(va)     (((va) >> 12) & 0x1FF)  // Bits 12-20
#define PAGE_OFFSET(va)  ((va) & 0xFFF)          // Bits 0-11


typedef struct page {
    uint64_t present   : 1;
    uint64_t rw        : 1;
    uint64_t user      : 1;
    uint64_t pwt       : 1;
    uint64_t pcd       : 1;
    uint64_t accessed  : 1;
    uint64_t dirty     : 1;
    uint64_t pat       : 1;
    uint64_t global    : 1;
    uint64_t ignored   : 3;
    uint64_t frame     : 40;
    uint64_t reserved  : 11;
    uint64_t nx        : 1;
} __attribute__((packed)) page_t;


typedef struct entry {
    uint64_t present   : 1;
    uint64_t rw        : 1;
    uint64_t user      : 1;
    uint64_t pwt       : 1;
    uint64_t pcd       : 1;
    uint64_t accessed  : 1;
    uint64_t dirty     : 1;
    uint64_t pat       : 1;
    uint64_t global    : 1;
    uint64_t ignored   : 3;
    uint64_t frame     : 40;
    uint64_t reserved  : 11;
    uint64_t nx        : 1;
} __attribute__((packed)) entry_t;


typedef struct pt {
    page_t pages[512];
} __attribute__((aligned(PAGE_SIZE))) pt_t;

typedef struct pd {
    uint64_t entry_t[512]; // Each entry have Physical addresses of PTs
} __attribute__((aligned(PAGE_SIZE))) pd_t;

typedef struct pdpt {
    uint64_t entry_t[512]; // Each entry have Physical addresses of PDs
} __attribute__((aligned(PAGE_SIZE))) pdpt_t;

typedef struct pml4 {
    uint64_t entry_t[512]; // Each entry have Physical addresses of PDPTs
} __attribute__((aligned(PAGE_SIZE))) pml4_t;
[paging.c]

Code: Select all

extern uint64_t *frames;
extern uint64_t nframes;

extern uint64_t placement_address; // The value of it will set in kernel.c 
extern uint64_t mem_end_address;    // The value of it will set in kernel.c 

pml4_t * current_pml4;
pml4_t * kernel_pml4;

// Function to allocate a frame.
void alloc_frame(page_t *page, int is_kernel, int is_writeable)
{
   if (page->frame != 0)
   {
       return; // Frame was already allocated, return straight away.
   }
   else
   {
       uint64_t idx = first_frame(); // idx is now the index of the first free frame.
       if (idx == (uint64_t)-1)
       {
           // PANIC is just a macro that prints a message to the screen then hits an infinite loop.
           print("No free frames!");
       }
       set_frame(idx*0x1000); // this frame is now ours!
       page->present = 1; // Mark it as present.
       page->rw = (is_writeable)?1:0; // Should the page be writeable?
       page->user = (is_kernel)?0:1; // Should the page be user-mode?
       page->frame = placement_address + idx*PAGE_SIZE;
       placement_address += PAGE_SIZE;
   }
}

// Function to deallocate a frame.
void free_frame(page_t *page)
{
   uint64_t frame;
   if (!(frame=page->frame))
   {
       return; // The given page didn't actually have an allocated frame!
   }
   else
   {
       clear_frame(frame); // Frame is now free again.
       page->frame = 0x0; // Page now doesn't have a frame.
   }
}

void print_cr3() {
    uint64_t cr3;
    // Read the CR3 register
    asm volatile("mov %%cr3, %0" : "=r"(cr3));
    
    // Print the value of CR3
    print("CR3 register value: ");
    print_hex(cr3);
    print("\n");
}


void initialise_paging()
{   /*
        placement_address = 0x100000
        mem_end_address   = 0x105000
        mem_range = 0x105000 - 0x100000 = 0x5000 = 20 KB
        total_no_of_pages = mem_range / PAGE_SIZE = 0x5000 / 0x1000 = 0x5
        required_pages = nframes(8B) + PML4 (4KB) + PDPT (4KB) + PD (4KB) + PT (4KB) + PAGES(4n KB) = 8B + 16 KB + 4n KB
        * for single page required memory = 20.1 KB
        * for double page required memory = 24.1 KB
        * for triple page required memory = 28.1 KB (*****)
        * for qudra page required memory = 32.1 KB
    */

    print("Paging Start\n");
    print("=>placement_address : ");
    print_hex(placement_address);
    print(", ");
    print("Memory End Address  : ");
    print_hex(mem_end_address);
    print("\n");

    nframes = (mem_end_address - placement_address) / 0x1000;
    frames = (uint64_t *) kmalloc(INDEX_FROM_BIT(nframes)); // less of 4 kb memory aligned
    memset(frames, 0, INDEX_FROM_BIT(nframes));
    // Allocate and zero-initialize the PML4 table
    kernel_pml4 = (pml4_t *) kmalloc_a(sizeof(pml4_t), 1); // 4 kb memory aligned
    memset(kernel_pml4, 0, sizeof(pml4_t));
    current_pml4 = kernel_pml4;

    print("Total number of frames : ");
    print_dec(nframes);
    print("\n");

    // Identity-map the physical memory up to `placement_address`
    uint64_t i = placement_address;

    while( i < mem_end_address) // increment 4kb i.e. 0x1000
    {
        // Kernel code is readable but not writeable from user space
        page_t *page = get_page(i, 1, kernel_pml4);
        alloc_frame(page, 0, 0);
        i += 0x1000;
    }
    
    print("placement_address : ");
    print_hex(placement_address);
    print("\n");

    // switch_to_page_table(kernel_pml4);  // Enable paging
}


void switch_to_page_table(pml4_t *pml4) {
    // Update the global current PML4 pointer
    uint64_t physical_address = (uint64_t) pml4;

    // Load the physical address of the new PML4 table into CR3
    asm volatile("mov %0, %%cr3" :: "r"((uint64_t) physical_address) : "memory");

    // Enable paging by setting the paging (PG) bit in CR0
    // uint64_t cr0;
    // asm volatile("mov %%cr0, %0" : "=r"(cr0));  // Read CR0
    // cr0 |= 0x80000000;                          // Set the PG bit (bit 31)
    // asm volatile("mov %0, %%cr0" :: "r"(cr0));  // Write CR0
}

page_t *get_page(uint64_t address, int make, pml4_t *pml4) {
    uint64_t pml4_idx = PML4_INDEX(address);
    uint64_t pdpt_idx = PDPT_INDEX(address);
    uint64_t pd_idx = PD_INDEX(address);
    uint64_t pt_idx = PT_INDEX(address);

    print("-----------------------------------------------------------------------------------------------------------------------------\n");
    print("PML4 index : ");
    print_dec(pml4_idx);
    print(", ");
    print("PDPT index : ");
    print_dec(pdpt_idx);
    print(", ");
    print("PD index : ");
    print_dec(pd_idx);
    print(", ");
    print("PT index : ");
    print_dec(pt_idx);  
    print("\n");

    // Resolve PML4 entry
    if (!(pml4->entry_t[pml4_idx] & 0x1)) { // Check if entry is present
        if (make) {
            pdpt_t *new_pdpt = (pdpt_t *) kmalloc_a(sizeof(pdpt_t), 1); // create a new pdpt with 4 kb aligned
            memset(new_pdpt, 0, sizeof(pdpt_t)); // Zero out memory for safety
            pml4->entry_t[pml4_idx] = ((uint64_t)new_pdpt & ~0xFFF) << 12 | 0x3; // pml4 will hold address of new_pdpt and make it present and writable
        } else {
            return NULL; // Entry not present and not creating
        }
    }

    print("pml4 addr : ");
    print_hex((uint64_t) pml4);
    print(", ");

    print("content of pml4.entries[pml4_idx] : ");
    print_hex(pml4->entry_t[pml4_idx]);
    print("\n");

    pdpt_t *pdpt = (pdpt_t *)((pml4->entry_t[pml4_idx] >> 12 & ~0xFFF)); // create  pdpt from pml4->entry_t[pml4_idx]

    // Resolve PDPT entry
    if (!(pdpt->entry_t[pdpt_idx] & 0x1)) { // Check if entry is present
        if (make) {
            pd_t *new_pd = (pd_t *)kmalloc_a(sizeof(pd_t), 1); // making new pd with 4 kb aligned
            memset(new_pd, 0, sizeof(pd_t));
            pdpt->entry_t[pdpt_idx] = ((uint64_t)new_pd & ~0xFFF) << 12 | 0x3;
        } else {
            return NULL;
        }
    }

    print("pdpt addr : ");
    print_hex((uint64_t) pdpt);
    print(", ");

    print("content of pdpt.entries[pdpt_idx] : ");
    print_hex(pdpt->entry_t[pdpt_idx]);
    print("\n");

    pd_t *pd = (pd_t *)((pdpt->entry_t[pdpt_idx] >> 12 & ~0xFFF));

    // Resolve PD entry
    if (!(pd->entry_t[pd_idx] & 0x1)) {// Check if entry is present
        if (make) {
            pt_t *new_pt = (pt_t *)kmalloc_a(sizeof(pt_t), 1); // making new pt with 4 kb aligned
            memset(new_pt, 0, sizeof(pt_t));
            pd->entry_t[pd_idx] = ((uint64_t)new_pt & ~0xFFF) << 12 | 0x3;
        } else {
            return NULL;
        }
    }

    print("pd addr : ");
    print_hex((uint64_t) pd);
    print(", ");

    print("content of pd.entries[pd_idx] : ");
    print_hex(pd->entry_t[pd_idx]);
    print("\n");

    // Resolve Page Table
    pt_t *pt = (pt_t *)((pd->entry_t[pd_idx] >> 12 & ~0xFFF)); // Creating Page Table from pd->entry_t[pd_idx]

    print("pt addr : ");
    print_hex((uint64_t)pt);
    print(", ");

    print("content of pt->pages[pt_idx] : ");
    print_hex((uint64_t)&pt->pages[pt_idx]);
    print("\n");

    // Resolve Page
    page_t *page =(page_t *) &pt->pages[pt_idx];
    if(!(page->present)){ // Check if entry is present
         if (make) {
            page = (page_t *) kmalloc_a(sizeof(page_t), 1); // making new page  with 4 kb aligned
            memset(page, 0, sizeof(page_t));
            alloc_frame(page, 1, 1); // Allocate frame for the page
        }else{
            return NULL;
        }
    }

    print("page frame address : ");
    print_hex((uint64_t) page->frame);
    print("\n");

    return page;
}


[pmm.c]

Code: Select all

#include "pmm.h"

// one row of bitmap can store information(free/use) of 8 * 4 KB = 32 Kb memory page(8 pages)


// A bitset of frames - used or free.
uint64_t *frames;
uint64_t nframes;


// Static function to set a bit in the frames bitset
void set_frame(uint64_t frame_addr)
{
   uint64_t frame = frame_addr/0x1000;
   uint64_t idx = INDEX_FROM_BIT(frame);
   uint64_t off = OFFSET_FROM_BIT(frame);
   frames[idx] |= (0x1 << off);
}

// Static function to clear a bit in the frames bitset
void clear_frame(uint64_t frame_addr)
{
   uint64_t frame = frame_addr/0x1000;
   uint64_t idx = INDEX_FROM_BIT(frame);
   uint64_t off = OFFSET_FROM_BIT(frame);
   frames[idx] &= ~(0x1 << off);
}

// Static function to test if a bit is set.
uint64_t test_frame(uint64_t frame_addr)
{
   uint64_t frame = frame_addr/0x1000;
   uint64_t idx = INDEX_FROM_BIT(frame);
   uint64_t off = OFFSET_FROM_BIT(frame);
   return (frames[idx] & (0x1 << off));
}

// Static function to find the first free frame.
uint64_t first_frame()
{
   uint64_t i, j;
   for (i = 0; i < INDEX_FROM_BIT(nframes); i++)
   {
       if (frames[i] != 0xFFFFFFFFFFFFFFFF) // nothing free, exit early.
       {
           // at least one bit is free here.
           for (j = 0; j < 64; j++)
           {
               uint64_t toTest = 0x1 << j;
               if ( !(frames[i]&toTest) )
               {
                   return i*8*8+j;
               }
           }
       }
   }
}

The System crashed. When I am trying to uncomment // switch_to_page_table(kernel_pml4); // Enable paging is causing system crash. For debug I am gettin the following output :
[img][https://github.com/baponkar/KeblaOS/blo ... isplay.png]
I also have written a stackoverflow [question][https://stackoverflow.com/questions/792 ... 3-register]. How can I solve this issue?

Reference :
[jamesmolloy.co.uk][https://web.archive.org/web/20160326061 ... aging.html]

Re: Paging initialization in x86_64

Posted: Mon Dec 02, 2024 10:28 pm
by nullplan
I already see a huge problem. Since you are using bit fields for the page type, the member "frame" must be assigned a page number, not an address. But it is assigned an address, so all the assignments end up being off by 12 bits.

Re: Paging initialization in x86_64

Posted: Tue Dec 03, 2024 12:34 am
by gamingjam60
I also have tried with page->frame = idx; instead of page->frame = placement_address + idx*PAGE_SIZE; the system crash also persists..

Re: Paging initialization in x86_64

Posted: Wed Dec 04, 2024 7:09 am
by nexos
Do “(placement_address/4096) + idx”

Also I would recommend staying away from OSDev tutorials in general, but James Molloy in particular has a large number of known bugs especially in the memory management code. Personally I would recommend using the resources on this wiki, looking at how more successful OSes do things (like HelenOS, NewOS, even more commercial ones like 386BSD and Mach if you’re decent at understanding code) and using those as a guide. Also read books on OSDev theory. Just try to stay away from tutorials. As an example of what I mean, you use kmalloc to allocate memory for page tables. The problem is that generally malloc should be allocating virtual addresses, while page table allocations should be from physical memory. This means that your MM should have a structure where you have:

- a physical memory manager that reads a memory map to determine usable regions of memory, and allows allocations and frees either using a bitmap or a free list
- a page mapping layer that manages the CPUs paging structures and allocates the physical memory of the page tables behind it. In order to access page table memory, it needs to map these page tables into virtual memory, which is done by a few methods (like recursive mapping, identity mapping physical memory, or maintaining and caching temporary mappings
- and a virtual memory manager that allocates page of virtual memory using some data structure and uses the physical memory manager to allocate the physical pages and then maps them using the page mapping layer.

Finally, malloc is build on top of this using one of a number algorithms.

Re: Paging initialization in x86_64

Posted: Sun Dec 08, 2024 8:02 am
by gamingjam60
Thankyou nexos for your valuable comments cum guide.