ARM Integrator-CP IRQTimerPICTasksMMAndMods
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.