ARM Integrator-CP IRQTimerPICTasksMMAndMods

From OSDev Wiki
Jump to navigation Jump to search

This page or section refers to its readers or editors using I, my, we or us. It should be edited to be in an encyclopedic tone.

ARM Integrator-CP IRQ, Timer, PIC, Tasks, Modules, And ELF32

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.
Virtual Memory With Paged Kernel This shows a method to have kernel space paged.

Author

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 [email protected] if you find this useful, have comments, or suggestions.


Intro

You should be coming from one of the previous pages. I prefer the last one if you have been following along. Well, the moment has arrived. You are now ready to see a method of attaching some modules and even a demonstration of loading a ELF32 module into user space.

First we will cover the method to attach files to the end of the kernel image. Then we will look at loading an ELF32 module into user space.

Attaching A Module

Python Script

This scripts pads the module to a 32-bit boundary so reads will be aligned, and inserts the special header so it can be located. The header contains a few fields, see C code further down to see a structure defined for the header. The module can present it's own fields in the header through the slots field.

#!/usr/bin/python3
import sys
import struct

def attach(srcf, dstf, typf):
	sfd = open(srcf, "rb")
	sfd.seek(0, 2)
	sz = sfd.tell()
	sfd.seek(0, 0)
	sd = sfd.read(sz)
	sfd.close()

	pad = len(sd) & 3
	if pad > 0:
		pad = 4 - pad
	else:
		pad = 0

	dfd = open(dstf, "r+b")
	dfd.seek(0, 2)
	psz = dfd.tell()
	ppad = psz & 3
	if ppad > 0:
		ppad = 4 - ppad
	else:
		ppad = 0
	dfd.write(b'B' * ppad)
	dfd.write(struct.pack('IIII', sz + pad, 0x12345678, 0xedcba987, int(typf)))
	dfd.write(sd)
	#print('sz+pad=%s' % (sz + pad))
	if pad > 0:
		dfd.write(b'A' * pad)
	#print('[ATTACHMOD] spad:%s tpad:%s %s [%s bytes] ----> %s (old sz:%s new sz:%s)' % (pad, ppad, srcf, sz, dstf,  hex(psz), hex(dfd.tell())))
	dfd.close()
	return sz

srcf = sys.argv[1]
dstf = sys.argv[2]
typf = sys.argv[3]

attach(srcf, dstf, typf)

To attach a module you could do something like this:

    ./attachmod.py mymodule mykernelimage 1

Where 1 is a 32-bit integer value. I use the value one in the kernel code further down to determine if it is an ELF32 module and if so I load it into a newly created user address space.

Module Enumeration Code

#ifndef KMOD_H
#define KMOD_H
#include "stdtypes.h"

typedef struct _KATTMOD {
	uint32			size;
	uint32			signatureA;
	uint32			signatureB;
	uint32			type;
	uint32			slot[];
} KATTMOD;

KATTMOD *kPkgGetNextMod(KATTMOD *mod);
KATTMOD *kPkgGetFirstMod();
uintptr kPkgGetTotalLength();
#endif

#include "kmod.h"

extern uint8 _EOI;

/*
	Get the next module after the specified module.
*/
KATTMOD *kPkgGetNextMod(KATTMOD *mod) {
	KATTMOD		*n;
	
	n = (KATTMOD*)((uintptr)mod + mod->size + sizeof(KATTMOD));
	
	/* make sure there actually exists another module after this one */
	if (n->signatureA != 0x12345678 || ~n->signatureA != n->signatureB) {
		kprintf("next mod not found\n");
		return 0;
	}
	
	return n;
}

uintptr kPkgGetTotalLength() {
	KATTMOD			*m, *lm;
	
	lm = 0;
	for (m = kPkgGetFirstMod(); m; m = kPkgGetNextMod(m)) {
		lm = m;
	}
	
	if (!lm) {
		return (uintptr)&_EOI;
	}
	
	return lm->size + (uintptr)lm + sizeof(KATTMOD) + 1;
}

/*
	Locate first module, if any exists, at end of our image.
*/
KATTMOD *kPkgGetFirstMod() {
	uint32		*p;
	uint32		x;
	uintptr		_p;
	
	_p = (uintptr)&_EOI - 4 * 20;
        /* OR... _p = (uintptr)&kPkgGetFirstMod - 4 * 20.. if not _EOI symbol 
      
          With GCC and -Os the code below might emit the signature. So you might
          need to create a dummy function below this one (should come after this
          one in text section), and reference that instead. With _EOI symbol you
          should have no problems unless this function appears with in the range
          and your using GCC -Os (optimize for size)
        */
	_p = _p & ~0x3;
	
	/* find signature of first module */
	for (p = (uint32*)_p, x = 0; x < 256; ++x) {
		/*
			This is a very tricky situation. We do not want the compiler
			to emit these sequence of bytes neither through an immediate
			operand or through optimization.
		*/
		if (p[x] == 0x12345678 && p[x + 1] == ~p[x]) {
			/* backup 32-bits (4 bytes) so we grab the size placed by attachmod.py */
			return (KATTMOD*)&p[x - 1];
		}
	}
	
	return 0;
}

To enumerate modules attached to the image:

        KATTMOD   *m;

	for (m = kPkgGetFirstMod(); m; m = kPkgGetNextMod(m)) {
        }

Loading An Elf Module

The code below is the absolute minimal needed to load an ELF32 into memory. It skips a lot of checks and other advanced features.

ELF Binary Code

int _start(unsigned int *smmio) {
	int		x;

	for (;;) {
		for (x = 0; x < 0xfffff; ++x);
		smmio[0] = 'G';
		smmio[0] = 'K';
	}
	return 0;
}

To compile:

arm-eabi-gcc -nostdlib -nostartfiles -ffreestanding -std=gnu99 main.c -o main 
arm-eabi-ld main.o -o main -Ttext 0x80000000 -N


For the -N flag. LD produces large files where the file offset of sections are equal to the virtual address. See https://sourceware.org/ml/binutils/2009-04/msg00099.html

Kernel Code

The first part of the kernel code here assumes you have been following along with the other pages in this series of pages. So I am not going to go too deep to explain much of it. But, essentially I check that the module is of type KMODTYPE_ELFUSER and if so then I find a free task slot and hand that to a function that will do the actual loading of the image.

	#define KMODTYPE_ELFUSER			1
	/*
		create a task for any attached modules of the correct type
	*/
	kprintf("looking at attached modules\n");
	for (m = kPkgGetFirstMod(); m; m = kPkgGetNextMod(m)) {
		kprintf("looking at module\n");
		if (m->type == KMODTYPE_ELFUSER) {
			/* find free task structure */
			for (x = 0; x < 0x10; ++x) {
				if (!ks->threads[x].valid) {
					break;
				}
			}
			
			if (x >= 0x10) {
				PANIC("out-of-task-slots");
			}
			
			kelfload(&ks->threads[x], (uintptr)&m->slot[0], m->size);
		}
	}

And, of course the kelfload implementation. This is just a very minimal implementation intended to just get the image into memory as a demonstration and nothing advanced.

typedef uint16 Elf32_Half;
typedef uint32 Elf32_Word;
typedef uint32 Elf32_Off;
typedef uint32 Elf32_Addr;

#define EI_NIDENT 16
typedef struct {
        unsigned char   e_ident[EI_NIDENT];
        Elf32_Half      e_type;
        Elf32_Half      e_machine;
        Elf32_Word      e_version;
        Elf32_Addr      e_entry;
        Elf32_Off       e_phoff;
        Elf32_Off       e_shoff;
        Elf32_Word      e_flags;
        Elf32_Half      e_ehsize;
        Elf32_Half      e_phentsize;
        Elf32_Half      e_phnum;
        Elf32_Half      e_shentsize;
        Elf32_Half      e_shnum;
        Elf32_Half      e_shtrndx;
} ELF32_EHDR;

#define EM_ARM			40

typedef struct {
       Elf32_Word      sh_name;
       Elf32_Word      sh_type;
       Elf32_Word      sh_flags;
       Elf32_Addr      sh_addr;
       Elf32_Off       sh_offset;
       Elf32_Word      sh_size;
       Elf32_Word      sh_link;
       Elf32_Word      sh_info;
       Elf32_Word      sh_addralign;
       Elf32_Word      sh_entsize;
} ELF32_SHDR;

int kelfload(KTHREAD *th, uintptr addr, uintptr sz) {
	ELF32_EHDR			*ehdr;
	ELF32_SHDR			*shdr;
	uint32				x, y;
	uintptr				page, oldpage;
	KSTATE				*ks;
	uint8				*fb;
	
	kprintf("loading elf into memory space\n");
	
	ks = (KSTATE*)KSTATEADDR;
	
	ehdr = (ELF32_EHDR*)addr;
	if (ehdr->e_machine != EM_ARM) {
		kprintf("kelfload: not ARM machine!\n");
		return 0;
	}
	
	if (ehdr->e_ident[4] != 0x1) {
		kprintf("kelfload: not ELF32 object\n");
		return 0;
	}
	
	kvmm2_init(&th->vmm);
	th->pc = ehdr->e_entry;
	th->valid = 1;
	th->cpsr = 0x60000000 | ARM4_MODE_USER;
	/* set stack */
	th->sp = 0x90001000;
	/* pass address of serial output as first argument */
	th->r0 = 0xa0000000;
	/* map serial output mmio */
	kvmm2_mapsingle(&th->vmm, 0xa0000000, 0x16000000, TLB_C_AP_FULLACCESS);
	/* map stack page (4K) */
	kvmm2_allocregionat(&th->vmm, 1, 0x90000000, TLB_C_AP_FULLACCESS);

	/* map address space so we can work directly with it */
	kvmm2_getphy(&ks->vmm, (uintptr)th->vmm.table, &page);
	oldpage = arm4_tlbget1();
	arm4_tlbset1(page);
	/* flush TLB */
        asm("mcr p15, #0, r0, c8, c7, #0");

	// e_shoff - section table offset
	// e_shentsize - size of each section entry
	// e_shnum - count of entries in table
	for (x = 0; x < ehdr->e_shnum; ++x) {
		shdr = (ELF32_SHDR*)(addr + ehdr->e_shoff + x * ehdr->e_shentsize);
		if (shdr->sh_addr != 0) {
			/* load this into memory */
			// sh_offset - byte offset in module
			// sh_size - size of section in module 
			// sh_addr - address to load at
			kvmm2_allocregionat(&th->vmm, kvmm2_rndup(shdr->sh_size), shdr->sh_addr, TLB_C_AP_FULLACCESS);
			fb = (uint8*)(addr + shdr->sh_offset);
			/* copy */
			for (y = 0; y < shdr->sh_size; ++y) {
				((uint8*)shdr->sh_addr)[y] = fb[y];
			}
		}
	}
	
	/* restore previous address space */
	arm4_tlbset1(oldpage);
        /* flush the TLB */
        asm("mcr p15, #0, r0, c8, c7, #0");
}

Of course it is just minimal but it should be enough for you to get started with and improve it!

Full Source

You can access the full source with:

    git clone http://kmcg3413.net/armthin.git
    git checkout origin/bINT_TIMER_PIC_TASKS_VMM_MOD_ELF

The head (master branch) may contain more work beyond this page.

Going Further

At this point you have almost a fully functional kernel. Your missing better support for device drivers, services, and such things. Your also at the point where you can build different kernel types depending on your design. Your missing system calls for user space for memory allocation. Also, you need processes and threads instead of the more primitive task like design we have now. Also, you need to handle exceptions for user space correctly such as terminating the offending process or thread. Also, synchronization primitives and common services like timers and sleeping are missing. And, then again maybe you do not need these and you have some really awesome design!? Well, code to your heart's content is my suggestion!

You can continue along with me or branch out in your own direction if you like, but in the next page we are going to create actual processes and threads, improve our exception handling, and add just a few basic services through system calls to threads.

For lack of a better name, continue onward to, Phase 2.