voron

experimental ARM OS
git clone git://git.2f30.org/voron
Log | Files | Refs | README | LICENSE

commit e6669b6ab2d027b1f21a416737d26d5788764888
parent 6ef96df11b3aa0a5367df9be0fcb1dc02f94a277
Author: oblique <psyberbits@gmail.com>
Date:   Sun,  9 Sep 2012 23:27:08 +0300

added:  spinlock, simple heap allocator, gcc constructors support, linked lists

Diffstat:
MMakefile | 18++++++++++--------
Ainclude/alloc.h | 10++++++++++
Minclude/errno.h | 6++++++
Ainclude/kernel.h | 18++++++++++++++++++
Ainclude/list.h | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Minclude/mmu.h | 3+++
Ainclude/spinlock.h | 54++++++++++++++++++++++++++++++++++++++++++++++++++++++
Minclude/stddef.h | 10++++++++++
Ainclude/string.h | 47+++++++++++++++++++++++++++++++++++++++++++++++
Akernel/alloc.c | 146+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mkernel/debug.c | 3+--
Mkernel/interrupts.S | 4++--
Mkernel/kmain.c | 7++++---
Mkernel/linker.ld | 13+++++++++++++
Mkernel/mm.c | 25+++++++++++++++++--------
Mkernel/mmu.c | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Mkernel/print.c | 4+---
Mkernel/rs232.c | 3+--
Mkernel/start.S | 27++++++++++++++++++++++++++-
Mkernel/syscalls.c | 3+--
Musbboot/usbboot.S | 4++--
21 files changed, 495 insertions(+), 37 deletions(-)

diff --git a/Makefile b/Makefile @@ -14,15 +14,17 @@ LD = $(CROSS_COMPILE)ld OBJCOPY = $(CROSS_COMPILE)objcopy NM = $(CROSS_COMPILE)nm -objs = kernel/start.o kernel/kmain.o kernel/rs232.o kernel/print.o \ - kernel/debug.o kernel/interrupts.o kernel/syscall_table.o \ - kernel/syscalls.o kernel/mmu.o kernel/mm.o +KERNEL_SRCS_C = $(wildcard kernel/*.c) +KERNEL_SRCS_S = $(wildcard kernel/*.S) +KERNEL_SRCS = $(KERNEL_SRCS_C) $(KERNEL_SRCS_S) +KERNEL_OBJS = $(KERNEL_SRCS_C:%.c=%.o) $(KERNEL_SRCS_S:%.S=%.o) +HDRS = $(wildcard include/*.h) all: uImage usbbootImage kernel.syms -kernel.elf: $(objs) kernel/linker.ld +kernel.elf: $(KERNEL_OBJS) kernel/linker.ld @echo -e " LD\t$@" - @$(CC) $(LDFLAGS) -o $@ $(objs) $(LIBGCC) + @$(CC) $(LDFLAGS) -o $@ $(KERNEL_OBJS) $(LIBGCC) kernel.bin: kernel.elf @$(OBJCOPY) $< -O binary $@ @@ -46,16 +48,16 @@ usbbootImage: usbboot/usbboot.o usbboot/usbboot.ld usbboot/kernel_image.o usbboot/kernel_image.o: kernel.bin @$(LD) -r -b binary -o $@ $< -%.o: %.c +%.o: %.c $(HDRS) @echo -e " CC\t$<" @$(CC) $(CFLAGS) -c -o $@ $< -%.o: %.S +%.o: %.S $(HDRS) @echo -e " AS\t$<" @$(CC) $(ASFLAGS) -c -o $@ $< clean: - @rm -f $(objs) kernel.elf kernel.bin kernel.syms uImage usbbootImage \ + @rm -f $(KERNEL_OBJS) kernel.elf kernel.bin kernel.syms uImage usbbootImage \ usbboot/kernel_image.o usbboot/usbboot.o voron.tar.gz targz: diff --git a/include/alloc.h b/include/alloc.h @@ -0,0 +1,10 @@ +#ifndef __ALLOC_H +#define __ALLOC_H + +#include <inttypes.h> + +void *kmalloc(size_t size); +void kfree(void *addr); +void kdump(); + +#endif /* __ALLOC_H */ diff --git a/include/errno.h b/include/errno.h @@ -36,4 +36,10 @@ #define EDOM 33 /* Math argument out of domain of func */ #define ERANGE 34 /* Math result not representable */ +#define MAX_ERRNO 34 +#define IS_ERR(x) ((unsigned long)(x) >= (unsigned long)-MAX_ERRNO) +#define PTR_ERR(x) ((unsigned long)(x)) +#define ERR_PTR(x) ((void*)(x)) + + #endif /* __ERRNO_H */ diff --git a/include/kernel.h b/include/kernel.h @@ -0,0 +1,18 @@ +#ifndef __KERNEL_H +#define __KERNEL_H + +#include <inttypes.h> +#include <stddef.h> +#include <print.h> +#include <io.h> +#include <errno.h> +#include <varg.h> + +#define container_of(ptr, type, member) ({ \ + const typeof(((type*)0)->member) *__mptr = (ptr); \ + (type*)((uintptr_t)__mptr - offsetof(type, member)); \ +}) + +#define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(arr[0])) + +#endif /* __KERNEL_H */ diff --git a/include/list.h b/include/list.h @@ -0,0 +1,67 @@ +#ifndef __LIST_H +#define __LIST_H + +#include <kernel.h> + +struct list_head { + struct list_head *prev; + struct list_head *next; +}; + +#define LIST_HEAD_INIT(name) { &(name), &(name) } +#define LIST_HEAD(name) struct list_head name = LIST_HEAD_INIT(name) + +#define list_entry(ptr, type, member) container_of(ptr, type, member) + +/* list_for_each - iterate over a list + * pos: the &struct list_head to use as a loop cursor. + * head: the head for your list. + */ +#define list_for_each(pos, head) \ + for (pos = (head)->next; pos != (head); \ + pos = pos->next) + +/* list_for_each_safe - iterate over a list safe against removal of list entry + * pos: the &struct list_head to use as a loop cursor. + * n: another &struct list_head to use as temporary storage + * head: the head for your list. + */ +#define list_for_each_safe(pos, n, head) \ + for (pos = (head)->next, n = pos->next; pos != (head); \ + pos = n, n = pos->next) + +static inline void INIT_LIST_HEAD(struct list_head *list) { + list->prev = list; + list->next = list; +} + +/* Note: list_empty() on entry does not return true after this, the entry is + * in an undefined state. + */ +static inline void list_del(struct list_head *entry) { + entry->prev->next = entry->next; + entry->next->prev = entry->prev; + entry->next = NULL; + entry->prev = NULL; +} + +static inline void list_add(struct list_head *entry, struct list_head *head) { + head->next->prev = entry; + entry->next = head->next; + entry->prev = head; + head->next = entry; +} + +static inline void list_add_tail(struct list_head *entry, struct list_head *head) { + head->prev->next = entry; + entry->next = head; + entry->prev = head->prev; + head->prev = entry; +} + +static inline int list_empty(struct list_head *head) { + return head->next == head; +} + + +#endif /* __LIST_H */ diff --git a/include/mmu.h b/include/mmu.h @@ -6,10 +6,12 @@ #define PAGE_MASK (~(PAGE_SIZE - 1)) #define PAGE_ALIGN(x) (((x) + PAGE_SIZE - 1) & PAGE_MASK) +#define L1_TYPE_MASK 3 #define L1_FAULT 0 #define L1_PAGE_TABLE 1 #define L1_SECTION 2 +#define L2_TYPE_MASK 3 #define L2_PAGE_FAULT 0 #define L2_LARGE_PAGE 1 #define L2_SMALL_PAGE 2 @@ -38,5 +40,6 @@ void mmu_init(); void mmu_enable(); void mmu_disable(); int mmu_map_page(void *phys, void *virt, uint_t npages, mmu_ap_t perms); +int kmmap(void *virt, uint_t npages, mmu_ap_t perms); #endif /* __MMU_H */ diff --git a/include/spinlock.h b/include/spinlock.h @@ -0,0 +1,54 @@ +#ifndef __SPINLOCK_H +#define __SPINLOCK_H + +#define SPINLOCK_INIT ((spinlock_t)0) + +typedef unsigned int spinlock_t; + +static inline void spinlock_lock(spinlock_t *sl) { + asm volatile ( + "1: \n\t" + "ldrex v1, [%0] \n\t" + "teq v1, #0 \n\t" + "strexeq v1, %1, [%0] \n\t" + "teq v1, #0 \n\t" + "bne 1b \n\t" + : + : "r" (sl), "r" (0x80000000) + : "v1", "memory" + ); +} + +static inline void spinlock_unlock(spinlock_t *sl) { + asm volatile ( + "str %1, [%0]" + : + : "r" (sl), "r" (0) + : "memory" + ); +} + +/* returns 1 if locked and 0 if not */ +static inline int spinlock_trylock(spinlock_t *sl) { + unsigned int tmp; + + asm volatile ( + "ldrex %0, [%1] \n\t" + "teq %0, #0 \n\t" + "strexeq %0, %2, [%1] \n\t" + : "=r" (tmp) + : "r" (sl), "r" (0x80000000) + : "memory" + ); + + if (tmp == 0) + return 1; + else + return 0; +} + +static inline void INIT_SPINLOCK(spinlock_t *sl) { + *sl = 0; +} + +#endif /* __SPINLOCK_H */ diff --git a/include/stddef.h b/include/stddef.h @@ -1,6 +1,16 @@ #ifndef __STDDEF_H #define __STDDEF_H +#include <inttypes.h> + #define NULL ((void*)0) +enum { + false = 0, + true = 1 +}; + + +#define offsetof(type, member) ((uintptr_t)&((type*)0)->member) + #endif /* __STDDEF_H */ diff --git a/include/string.h b/include/string.h @@ -0,0 +1,47 @@ +#ifndef __STRING_H +#define __STRING_H + +static inline void *memset(void *s, int c, size_t n) { + unsigned char *us = s; + + while (n) { + *us = c; + us++; + n--; + } + + return s; +} + +static inline void *memcpy(void *s1, const void *s2, size_t n) { + unsigned char *us1 = s1; + const unsigned char *us2 = s2; + + while (n) { + *us1 = *us2; + us1++; + us2++; + n--; + } + + return s1; +} + +static inline int memcmp(const void *s1, const void *s2, size_t n) { + const unsigned char *us1, *us2; + + us1 = s1; + us2 = s2; + + while (n) { + if (*us1 != *us2) + return (*us1 - *us2 < 0) ? -1 : 1; + us1++; + us2++; + n--; + } + + return 0; +} + +#endif /* __STRING_H */ diff --git a/kernel/alloc.c b/kernel/alloc.c @@ -0,0 +1,146 @@ +#include <kernel.h> +#include <list.h> +#include <spinlock.h> +#include <mmu.h> +#include <alloc.h> + +struct mem_chunk { + union { + void *start; + uintptr_t start_a; + }; + size_t size; + struct list_head list; +}; + +#define get_mem_chunk(addr) (((struct mem_chunk*)(addr)) - 1) + +static struct list_head freelist; +static struct list_head alloclist; +static spinlock_t freelist_lock = SPINLOCK_INIT; +static spinlock_t alloclist_lock = SPINLOCK_INIT; +static void *heap_last; + +extern void *_kernel_heap_start; +extern void *_kernel_heap_end; + +static size_t roundup(size_t size) { + size_t ret; + + if (size <= 16) + return 16; + + /* get the number of leading zeros */ + asm volatile("clz %0, %1" : "=r" (ret) : "r" (size)); + + /* round up to the next 'power of 2' number */ + ret = 1 << (32 - ret); + + if (size != (ret >> 1)) + return ret; + else + return ret >> 1; +} + +void *kmalloc(size_t size) { + int ret; + uint_t npages; + uintptr_t heap_last_a, tmp_addr; + struct mem_chunk *memc, *tmp_mc; + struct list_head *pos; + + size = roundup(size); + heap_last_a = (uintptr_t)heap_last; + + spinlock_lock(&freelist_lock); + list_for_each(pos, &freelist) { + memc = list_entry(pos, struct mem_chunk, list); + if (memc->size == size) { + list_del(&memc->list); + spinlock_unlock(&freelist_lock); + spinlock_lock(&alloclist_lock); + list_add(&memc->list, &alloclist); + spinlock_unlock(&alloclist_lock); + return memc->start; + } + } + spinlock_unlock(&freelist_lock); + + npages = PAGE_ALIGN(sizeof(struct mem_chunk) + size) >> PAGE_SHIFT; + + if (heap_last_a + npages * PAGE_SIZE > (uintptr_t)&_kernel_heap_end) + return NULL; + + ret = kmmap(heap_last, npages, MMU_AP_RW_NONE); + if (ret < 0) + return NULL; + + memc = heap_last; + memc->start_a = sizeof(struct mem_chunk) + heap_last_a; + memc->size = size; + + spinlock_lock(&alloclist_lock); + list_add(&memc->list, &alloclist); + spinlock_unlock(&alloclist_lock); + + heap_last_a += npages * PAGE_SIZE; + heap_last = (void*)heap_last_a; + + tmp_addr = memc->start_a + memc->size; + while (tmp_addr + sizeof(struct mem_chunk) + memc->size < heap_last_a) { + tmp_mc = (void*)tmp_addr; + tmp_mc->start_a = sizeof(struct mem_chunk) + tmp_addr; + tmp_mc->size = memc->size; + tmp_addr += sizeof(struct mem_chunk) + tmp_mc->size; + spinlock_lock(&freelist_lock); + list_add(&tmp_mc->list, &freelist); + spinlock_unlock(&freelist_lock); + } + + return memc->start; +} + +void kfree(void *addr) { + struct mem_chunk *memc, *tmp; + struct list_head *pos; + + memc = get_mem_chunk(addr); + + spinlock_lock(&alloclist_lock); + list_for_each(pos, &alloclist) { + tmp = list_entry(pos, struct mem_chunk, list); + if (tmp == memc) { + list_del(&memc->list); + spinlock_lock(&freelist_lock); + list_add(&memc->list, &freelist); + spinlock_unlock(&freelist_lock); + break; + } + } + spinlock_unlock(&alloclist_lock); +} + + +void kdump() { + struct list_head *pos, *tmp; + struct mem_chunk *memc; + + kprintf("alloc list\n"); + list_for_each_safe(pos, tmp, &alloclist) { + memc = list_entry(pos, struct mem_chunk, list); + kprintf("%p %p %d\n", memc, memc->start, memc->size); + } + + kprintf("\nfree list\n"); + list_for_each_safe(pos, tmp, &freelist) { + memc = list_entry(pos, struct mem_chunk, list); + kprintf("%p %p %d\n", memc, memc->start, memc->size); + } +} + +__attribute__ ((__constructor__)) +static void alloc_init() { + INIT_LIST_HEAD(&freelist); + INIT_LIST_HEAD(&alloclist); + heap_last = &_kernel_heap_start; +} diff --git a/kernel/debug.c b/kernel/debug.c @@ -1,6 +1,5 @@ -#include <inttypes.h> +#include <kernel.h> #include <debug.h> -#include <io.h> static u32 *gpio_wk7 = (u32*)0x4A31E058; static u32 *gpio_wk8 = (u32*)0x4A31E05C; diff --git a/kernel/interrupts.S b/kernel/interrupts.S @@ -1,6 +1,6 @@ .section .text -.global init_vector_table -init_vector_table: +.global vector_table_init +vector_table_init: mrc p15, 0, r0, c1, c0, 0 @ read CP15 SCTRL register bic r0, r0, #(1 << 13) @ set V flag to 0 (disable high vectors) mcr p15, 0, r0, c1, c0, 0 @ write CP15 SCTRL register diff --git a/kernel/kmain.c b/kernel/kmain.c @@ -1,8 +1,9 @@ -#include <print.h> -#include <debug.h> +#include <kernel.h> +#include <alloc.h> +#include <string.h> void kmain(void) { - kprintf("voron initial stage\n"); + kprintf("voron initial stage\n\n"); while (1) asm volatile("wfi"); diff --git a/kernel/linker.ld b/kernel/linker.ld @@ -18,6 +18,13 @@ SECTIONS *(.data) } + .init_array : ALIGN(4096) + { + _kernel_ctors_start = .; + *(.init_array) + _kernel_ctors_end = .; + } + .rodata : ALIGN(4096) { *(.rodata) @@ -25,10 +32,16 @@ SECTIONS .bss : ALIGN(4096) { + _kernel_bss_start = .; *(.bss) } . = ALIGN(4096); + _kernel_bss_end = .; + + _kernel_heap_start = .; + . = . + 0x10000000; + _kernel_heap_end = .; . = . + 0x2000; _svc_stack_top = .; diff --git a/kernel/mm.c b/kernel/mm.c @@ -1,6 +1,5 @@ -#include <inttypes.h> +#include <kernel.h> #include <mmu.h> -#include <errno.h> #define ALIGN32(x) (((x) + 31) & ~31) @@ -8,11 +7,15 @@ extern void *_ram_start; extern void *_ram_end; extern void *_kernel_bin_start; extern void *_kernel_bin_end; +extern void *_kernel_heap_start; +extern void *_kernel_heap_end; static u32 *bitmap; static uint_t bitmapsz; static uint_t kbound_s; static uint_t kbound_e; +static uint_t kheapbound_s; +static uint_t kheapbound_e; /* set range of bits */ static int set_bitmap_bits(uint_t start_bit, uint_t n, int flag) { @@ -65,6 +68,8 @@ void mm_init() { kbound_s = PAGE_ALIGN(PTR_DIFF(&_kernel_bin_start, &_ram_start)) >> PAGE_SHIFT; kbound_e = PAGE_ALIGN(PTR_DIFF(&_kernel_bin_end, &_ram_start)) >> PAGE_SHIFT; + kheapbound_s = PAGE_ALIGN(PTR_DIFF(&_kernel_heap_start, &_ram_start)) >> PAGE_SHIFT; + kheapbound_e = PAGE_ALIGN(PTR_DIFF(&_kernel_heap_end, &_ram_start)) >> PAGE_SHIFT; ramsz = PTR_DIFF(&_ram_end, &_ram_start) + 1; bitmapsz = ALIGN32(ramsz >> PAGE_SHIFT) / 32; @@ -74,7 +79,8 @@ void mm_init() { for (i = 0; i < bitmapsz; i++) bitmap[i] = 0; - set_bitmap_bits(kbound_s, kbound_e - kbound_s, 1); + set_bitmap_bits(kbound_s, kheapbound_s - kbound_s, 1); + set_bitmap_bits(kheapbound_e, kbound_e - kheapbound_e, 1); mmu_init(); @@ -83,7 +89,9 @@ void mm_init() { 0x40000, MMU_AP_RW_NONE); /* map kernel memory */ mmu_map_page(&_kernel_bin_start, &_kernel_bin_start, - kbound_e - kbound_s, MMU_AP_RW_NONE); + kheapbound_s - kbound_s, MMU_AP_RW_NONE); + mmu_map_page(&_kernel_heap_end, &_kernel_heap_end, + kbound_e - kheapbound_e, MMU_AP_RW_NONE); mmu_enable(); } @@ -94,7 +102,7 @@ void *palloc(uint_t npages) { int ret; if (npages == 0) - return (void*)-EINVAL; + return ERR_PTR(-EINVAL); n = 0; for (i = 0; i < bitmapsz; i++) { @@ -114,12 +122,12 @@ void *palloc(uint_t npages) { n = 0; } - return (void*)-EINVAL; + return ERR_PTR(-EINVAL); out: ret = set_bitmap_bits(sbit, npages, 1); if (ret < 0) - return (void*)ret; + return ERR_PTR(ret); return (void*)((uintptr_t)sbit * PAGE_SIZE + (uintptr_t)&_ram_start); } @@ -135,7 +143,8 @@ int pfree(void *paddr, uint_t npages) { sbit = (paddr_a - (uintptr_t)&_ram_start) >> PAGE_SHIFT; /* we cannot free the memory of ourself */ - if (sbit >= kbound_s && sbit < kbound_e) + if ((sbit >= kbound_s && sbit < kheapbound_s) || + (sbit >= kheapbound_e && sbit < kbound_e)) return -EINVAL; return set_bitmap_bits(sbit, npages, 0); diff --git a/kernel/mmu.c b/kernel/mmu.c @@ -1,8 +1,6 @@ -#include <inttypes.h> +#include <kernel.h> #include <mmu.h> -#include <errno.h> -#include <debug.h> -#include <print.h> +#include <mm.h> static u32 mmu_ttb[4096] __attribute__((__aligned__ (16 * 1024))); static u32 l2[4096][256] __attribute__((__aligned__ (1024))); @@ -59,6 +57,32 @@ void mmu_disable() { ); } +static int is_mapped_virt(void *virt) { + uint_t pte_idx, pde_idx; + u32 *pde; + uintptr_t virt_a; + + virt_a = (uintptr_t)virt; + pde_idx = virt_a >> 20; + + switch (mmu_ttb[pde_idx] & L1_TYPE_MASK) { + case 1: /* page table */ + pde = (u32*)(mmu_ttb[pde_idx] & ~0x3ff); + pte_idx = (virt_a & 0xff000) >> 12; + if (pde[pte_idx] & L2_TYPE_MASK) + return 1; + else + return 0; /* fault */ + case 2: /* section */ + return 1; + case 0: /* fault */ + default: + return 0; + } + + return 0; +} + /* map physical memory to virtual memory */ int mmu_map_page(void *phys, void *virt, uint_t npages, mmu_ap_t perms) { u32 pte, pte_perms; @@ -126,3 +150,31 @@ int mmu_map_page(void *phys, void *virt, uint_t npages, mmu_ap_t perms) { return 0; } + +int kmmap(void *virt, uint_t npages, mmu_ap_t perms) { + uint_t i; + uintptr_t virt_a; + void *pa; + + if (npages == 0) + return -EINVAL; + + virt_a = (uintptr_t)virt; + + /* overflow */ + if (virt_a + npages * PAGE_SIZE < virt_a) + return -EFAULT; + + for (i = 0; i < npages; i++) { + if (is_mapped_virt((void*)virt_a)) { + kprintf("WARNING: %p virtual address is already maped\n", virt); + virt_a += PAGE_SIZE; + continue; + } + pa = palloc(1); + mmu_map_page(pa, virt, 1, perms); + virt_a += PAGE_SIZE; + } + + return 0; +} diff --git a/kernel/print.c b/kernel/print.c @@ -1,7 +1,5 @@ -#include <inttypes.h> +#include <kernel.h> #include <rs232.h> -#include <print.h> -#include <varg.h> int kputs(const char *s) { return rs232_puts(s); diff --git a/kernel/rs232.c b/kernel/rs232.c @@ -1,7 +1,6 @@ -#include <inttypes.h> +#include <kernel.h> #include <rs232.h> #include <uart.h> -#include <io.h> static struct uart *uart = (struct uart*)0x48020000; diff --git a/kernel/start.S b/kernel/start.S @@ -14,8 +14,18 @@ _start: ldr r0, =boot_msg bl kputs + /* init .bss section */ + ldr r0, =_kernel_bss_start + ldr r1, =_kernel_bss_end + mov r2, #0 + mov r3, #0 +1: + stmia r0!, { r2, r3 } + cmp r0, r1 + blo 1b + /* initialize vector table */ - bl init_vector_table + bl vector_table_init /* disable caches for now */ mrc p15, 0, r0, c1, c0, 0 @@ -34,6 +44,21 @@ _start: bl mm_init + /* run constructors */ + ldr r0, =_kernel_ctors_start + ldr r1, =_kernel_ctors_end +1: + teq r0, r1 + beq 2f + ldr r2, [r0], #4 + stmfd sp!, { r0, r1 } + ldr lr, =.Lctor_ret + mov pc, r2 +.Lctor_ret: + ldmfd sp!, { r0, r1 } + b 1b +2: + /* example of calling a system call with at least 6 arguments */ mov r0, #1 mov r1, #2 diff --git a/kernel/syscalls.c b/kernel/syscalls.c @@ -1,5 +1,4 @@ -#include <print.h> -#include <inttypes.h> +#include <kernel.h> long sys_test_6_args(u32 a1, u32 a2, u32 a3, u32 a4, u32 a5, u32 a6) { kprintf("sys_test_6_args(%d, %d, %d, %d, %d, %d)\n", a1, a2, a3, a4, a5, a6); diff --git a/usbboot/usbboot.S b/usbboot/usbboot.S @@ -9,11 +9,11 @@ _start: ldr r1, =_image_end ldr r2, =_image_load_addr -.L1: +1: ldmia r0!, { r3 - r12 } stmia r2!, { r3 - r12 } cmp r0, r1 - blo .L1 + blo 1b ldmfd sp!, { r0 - r12 } ldr pc, =_image_load_addr @ jump to _image_load_addr