User:Pancakes/ARM Integrator-CP VMM2

From OSDev Wiki
Jump to: navigation, search



Well, hopefully, you are coming from some of the earlier examples. But, if not here are the links.

Page Description
IRQ, Timer, And PIC This demonstration just uses the IRQ, Timer, And PIC.
IRQ, Timer, PIC, And Tasks This shows how to switch between tasks using the timer, and builds from the previous page.
IRQ, Timer, PIC, Tasks, And MM This shows how to integrate virtual memory, and builds from the previous page.


I Pancakes wrote this to help jump start you into developing for the ARM using QEMU or even a real piece of hardware. I have wrote software for both emulators and real hardware, but this has only been tested on QEMU so far. Please make any needed changes if you find problems. Also let me know at if you find this useful, have comments, or suggestions.


I am going to extend the page IRQ, Timer, PIC, Tasks, And MM. On that page we build a functional virtual memory management system. It had a limitation and that was that the kernel space was identity mapped. This can create problems especially for MMIO access since if the kernel space is less than the address of a MMIO device then you will have to map it into every process's virtual memory just to access it which is much more difficult than just mapping it into kernel space. It also lets us catch kernel bugs by having gaps so stray memory accesses might throw an exception, and it helps to prevent kernel bugs from corrupting the user space and introducing very hard to track down bugs.

So this design basically gives more protection and flexibility at a cost of complexity and slightly more memory consumption.

About Previous Design

Lets try something a bit more robust and to eliminate the need for 2 page heaps, and instead have a single page heap that can service both kernel and user space. The problem is this design is a little more complex and has a lot more moving parts, but I am going to try to gently lower you down into it in hopes you come out with a better memory management system. And as you know the more moving parts the more that can break!

Major Factor

The main problem with doing this is that once we go into paging mode we need memory to map more memory. A good example would be imagine having your level one paging table with only one table entry, and that table having all 256 entries filled. You need to allocate and map at least 1k to make another table. Well, you will have no way to make that happen if your not using 1MB sections (maps 1MB with out second level table). And, the only other way would be to disable paging which could cause problems if you need to access data that is not identity mapped including your kernel's instruction stream. Also, in my code I am not going to use 1MB sections. So you would be unable to alloc and map another 4K to at least make a table with 1K.

Disabling paging might not incur any performance penalty as I am not sure if it would flush the TLB, but if you need to access data that is not identity mapped then you are going to run into problems.

I decided not to use 1MB because it would complicate existing code further, and I wanted this to be portable to the X86 by simply changing the shift operations and flags.


You will also need the source for the new VMM functions. Since the source is quite large I have uploaded it elsewhere. You can also find the complete kernel (for this page) here also:

Updated Source

If you inspect the above directory you will find the original functions. I have since made minor bug fixes and added more functionality. So I have left the above links for historical purposes, but the links below have the most recent source.

These functions have been fairly well tested, and should be bug free. I have have very few problems. I hope these can be used as a guide on how you might want to construct your own VM functions for manipulating VM tables. If your looking for the complete source then consider cloning the repository and pulling out what you need. The links are vmm.c and vmm.h.

Extracting Out Just The VMM

If you want to use the VMM in your own project you will need vmm.c, vmm.h, stdtypes.h, main.h, kheap.h, and kheap_bm.c. You major core files are just vmm.c and kheap_bm.c which contain all the implementation code.

The stdtypes.h is a small header that just defines the types. The kheap.h and heap_bm.c are also only need stdtypes.h. The main.h contains the kernel state structure, which you could modify and also modify in the vmm.c to work a different way if needed.

New Kernel State Structure

We need to add and remove a few fields from our kernel structure. One big change is the removal of husr, and the changing of the vmmk field to vmm. I change it to vmm so any old code will break during compilation allowing you to remove it if you basing everything on the previous page.

typedef struct _KSTATE {
	/* process/thread support */
	KTHREAD			threads[0x10];
	uint8			threadndx;	
	uint8			iswitch;
	/* physical and heap memory management */
	KHEAPBM			hphy;			/* kernel physical page heap */
	KHEAPBM			hchk;			/* data chunk heap */
	/* virtual memory management */
	KVMMTABLE		vmm;			/* kernel virtual memory map */
	uint32			vmm_ucte;		/* unused coarse table entries */
	KSTACK			tstack;			/* 1K table stack */
	uint32			*vmm_rev;		/* reverse map */

Overview Of Bootstrapping Before We Enable Paging

This should look familiar. It is basically the same process except we are using the functions with kvmm2_ prefix and whom work differently. The kvmm2_baseinit prepares a few critical structures and can be found in vmm.c. You should also notice we map the kernel code, exception table, and the entire hchk heap. The heap initialization code is basically the same as the previous page.

	arm4_xrqinstall(ARM4_XRQ_RESET, &k_exphandler_reset_entry);
	arm4_xrqinstall(ARM4_XRQ_UNDEF, &k_exphandler_undef_entry);
	arm4_xrqinstall(ARM4_XRQ_SWINT, &k_exphandler_swi_entry);
	arm4_xrqinstall(ARM4_XRQ_ABRTP, &k_exphandler_abrtp_entry);
	arm4_xrqinstall(ARM4_XRQ_ABRTD, &k_exphandler_abrtd_entry);
	arm4_xrqinstall(ARM4_XRQ_IRQ, &k_exphandler_irq_entry);
	arm4_xrqinstall(ARM4_XRQ_FIQ, &k_exphandler_fiq_entry);
	/* create physical page heap */
	/* get a bit of memory to start with for small chunk */
	k_heapBMAddBlock(&ks->hchk, 4 * 7, KRAMADDR - (4 * 7), KCHKHEAPBSIZE);
	/* state structure */
	k_heapBMSet(&ks->hchk, KSTATEADDR, sizeof(KSTATE), 5);
	/* stacks (can free KSTACKSTART later) */
	k_heapBMSet(&ks->hchk, KSTACKSTART - 0x1000, 0x1000, 6);
	k_heapBMSet(&ks->hchk, KSTACKEXC - 0x1000, 0x1000, 7);
        /* remove kernel image from hchk (if it is there) */
	k_heapBMSet(&ks->hchk, (uintptr)&_BOI, (uintptr)&_EOI - (uintptr)&_BOI, 8);
	/* add block but place header in chunk heap to keep alignment */
	bm = (uint8*)k_heapBMAlloc(&ks->hchk, k_heapBMGetBMSize(KRAMSIZE - KRAMADDR, KPHYPAGESIZE));
	k_heapBMAddBlockEx(&ks->hphy, KRAMADDR, KRAMSIZE - KRAMADDR, KPHYPAGESIZE, (KHEAPBLOCKBM*)k_heapBMAlloc(&ks->hchk, sizeof(KHEAPBLOCKBM)), bm, 0);
        /* remove kernel image from hphy (if it is there) */
	k_heapBMSet(&ks->hphy, (uintptr)&_BOI, (uintptr)&_EOI - (uintptr)&_BOI, 8);
	/* vmm */
	/* map kernel image */
					(uintptr)&_BOI, (uintptr)&_BOI,
					kvmm2_rndup((uintptr)&_EOI - (uintptr)&_BOI), 
	/* map reverse table (ALREADY MAPPED WITH HCHK BELOW) */
	/* map interrupt table, and chunk heap (hchk) */
	kvmm2_mapmulti(&ks->vmm, 0, 0, kvmm2_rndup(KRAMADDR), TLB_C_AP_PRIVACCESS | KVMM_DIRECT | KVMM_SKIP);
	/* map serial out register, PIC, and timer */
	kvmm2_mapsingle(&ks->vmm, 0x16000000, 0x16000000, TLB_C_AP_PRIVACCESS | KVMM_DIRECT);
	kvmm2_mapsingle(&ks->vmm, 0x14000000, 0x14000000, TLB_C_AP_PRIVACCESS | KVMM_DIRECT);
	kvmm2_mapsingle(&ks->vmm, 0x13000000, 0x13000000, TLB_C_AP_PRIVACCESS | KVMM_DIRECT);
	/* load location of TLB */
	arm4_tlbset1((uintptr)ks->vmm.table);	/* user space */
	arm4_tlbset0((uintptr)ks->vmm.table);	/* kernel space */
	/* set that all domains are checked against the TLB entry access permissions */
	/* enable TLB 0x1 and disable subpages 0x800000 */
	arm4_tlbsetctrl(arm4_tlbgetctrl() | 0x1 | (1 << 23));

At the end we enable paging, and if all is well execution should resume. That in my opinion is not a heavily complex or long section of code if you consider you now have a paged kernel. Also, you should note the usage of KVMM_DIRECT. This tells the implementation that paging is not yet enabled and all references can be followed directly. This happens when walking the tables to perform the various operations. Once paging has been enabled you simply omit that flag from the calls and they will perform the translation using what I call the reverse lookup table.

The reverse lookup table just translates physical addresses to virtual ones.

Creating A Thread And Mapping It

The next part is mapping an address space for a user level thread.

	ks->threads[0].pc = 0x80000000;
	ks->threads[0].valid = 1;
	ks->threads[0].cpsr = 0x60000000 | ARM4_MODE_USER;
	ks->threads[0].sp = 0x90001000;
	ks->threads[0].r0 = 0xa0000000;
	kvmm2_mapsingle(&ks->threads[0].vmm, 0xa0000000, 0x16000000, TLB_C_AP_FULLACCESS);
	kvmm2_allocregionat(&ks->threads[0].vmm, 1, 0x80000000, TLB_C_AP_FULLACCESS);
	kvmm2_allocregionat(&ks->threads[0].vmm, 1, 0x90000000, TLB_C_AP_FULLACCESS);
	kvmm2_getphy(&ks->vmm, (uintptr)ks->threads[0].vmm.table, &page);
	asm("mcr p15, #0, r0, c8, c7, #0");
	/* copy some code there */
	for (x = 0; x < 1024; ++x) {
		((uint8*)0x80000000)[x] = ((uint8*)&thread1)[x];

I try to flush the TLBs, but QEMU seems to work fine with out it, but I left it in just in case. Since GCC by default emits position independent code (-fPIC) for ARM due to the nature of ARM instructions so therefore I am able to just plainly copy the thread1 function. However, any references to segments other than .text by it may not be. But, this is just a demonstration. By using this method you could load an ELF image into the address space for example.

Because, GRUB may not be possible to use I would like to introduce you to a method to not only attach modules to your kernel but enumerate them in the next page, IRQ, Timer, PIC, Tasks, MM, And Modules.

Personal tools