aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile31
-rw-r--r--TODOs20
-rw-r--r--boot.S2
-rw-r--r--linker.ld13
-rw-r--r--loader_stage1.c25
-rw-r--r--loader_stage1.ld47
-rw-r--r--loader_stage2.c40
-rw-r--r--loader_stage2.ld44
-rw-r--r--pipe_image.c55
9 files changed, 269 insertions, 8 deletions
diff --git a/Makefile b/Makefile
index c296d1c..67aa3e0 100644
--- a/Makefile
+++ b/Makefile
@@ -17,13 +17,40 @@ kernel.elf : boot.o kernel.o uart.o
kernel7.img : kernel.elf
arm-none-eabi-objcopy $^ -O binary $@
+loader_stage2.o : loader_stage2.c
+ arm-none-eabi-gcc $(CFLAGS) -c $^ -o $@
+
+loader_stage2.elf : loader_stage2.o uart.o
+ arm-none-eabi-gcc -T loader_stage2.ld -o $@ -ffreestanding -O2 -nostdlib $^ -lgcc
+
+loader_stage2.img : loader_stage2.elf
+ arm-none-eabi-objcopy $^ -O binary $@
+
+loader_stage2_embeddable.o : loader_stage2.img
+ arm-none-eabi-objcopy -I binary -O elf32-littlearm -B arm --rename-section .data=.rodata $^ $@
+
+loader_stage1.o : loader_stage1.c
+ arm-none-eabi-gcc $(CFLAGS) -c $^ -o $@
+
+loader.elf : boot.o loader_stage1.o loader_stage2_embeddable.o
+ arm-none-eabi-gcc -T loader_stage1.ld -o $@ -ffreestanding -O2 -nostdlib $^ -lgcc
+
+loader.img : loader.elf
+ arm-none-eabi-objcopy $^ -O binary $@
+
qemu-elf : kernel.elf
qemu-system-arm -m 256 -M raspi2 -serial stdio -kernel $^
-qemu-bin : kernel7.img
+qemu-bin : loader.img kernel7.img pipe_image
+ ./pipe_image | qemu-system-arm -m 256 -M raspi2 -serial stdio -kernel $<
+
+qemu-loader : loader.img
qemu-system-arm -m 256 -M raspi2 -serial stdio -kernel $^
+pipe_image : pipe_image.c
+ gcc -Wall -std=gnu99 -O3 $^ -o $@
+
clean :
- -rm kernel7.img kernel.elf boot.o kernel.o uart.o
+ -rm *.img *.elf *.o pipe_image
.PHONY: all qemu-elf qemu-bin clean
diff --git a/TODOs b/TODOs
new file mode 100644
index 0000000..dcc08d5
--- /dev/null
+++ b/TODOs
@@ -0,0 +1,20 @@
+* Remove duplications in Makefile... i.e. use generic recipes for .c -> .o compilations and many other things, that can be shortened this was
+
+* Implement some basic utilities for us to use (memcpy, printf, etc...)
+
+* ensure .bss section is zeroed properly in stage2 (stage1 and actual kernel do it in common boot.S file; stage2 doesn't use boot.S); Note, that:
+ - It works as it is right now. If we have no uninitialized static variables in stage2 code, then .bss is probably empty... so this is not really important
+ - What if wiki.osdev was wrong about this and objcopy includes .bss in it's output image? Then also no work needs to be done
+ - Stage2 gets loaded between 0x4000 and 0x8000, so that piece of memory could be zeroed-out before by stage1 and that would solve the issue
+
+* Add sanity-check at build-time, that stage2 blob is smaller than 0x4000 in size
+
+* Races might occur, when one processor starts overwriting stuff at image load address before other processors execute the initial piece of code that puts them to sleep... This should be fixed in bootloader and will need to be taken into account when we develop the actual kernel to manage it's own memory
+
+* Real RPi firmware would jump to the kernel on all cores after loading it from SD... So it'd be good if bootloader did a simillar thing - i.e. bootloader, when started, first shuts off all cores but one, it loads it's stage2, which downloads the kernel by uart, turns all cores back on and jumps to kernel on all of them... Additional kudos if U make this race-free (see, TODO above)
+
+* Finally, the most important thing - move forward and start working with the MMU already!
+
+* Start doing this on hardware already... (Hey, whole making of a bootloader was with this in mind!)
+
+* Find a way to pipe kernel through uart (I'm afraid `./pipe_image | screen ...` might not work) \ No newline at end of file
diff --git a/boot.S b/boot.S
index 23ed2ea..5cce37c 100644
--- a/boot.S
+++ b/boot.S
@@ -1,4 +1,4 @@
-// AArch32 mode
+// armv7 mode
// To keep this in the first portion of the binary.
.section ".text.boot"
diff --git a/linker.ld b/linker.ld
index 0cbd1fb..c9a91df 100644
--- a/linker.ld
+++ b/linker.ld
@@ -2,12 +2,15 @@ ENTRY(_start)
SECTIONS
{
- /* Starts at LOADER_ADDR. */
- /* Warning! Internet says RPis in 32-bit mode load kernel at 0x8000! */
+ /* Starts at LOADER_ADDR, which is 0x8000 - that's where RPis in */
+ /* 32-bit mode load kernel at. */
/* My experiments do, however, show, that qemu emulating RPi2 */
- /* Loads the kernel at 0x10000! (took some pain to find out) */
- . = 0x10000;
- /* For AArch64, use . = 0x80000; Unless this too is wrong */
+ /* loads the kernel at 0x10000! (took some pain to find out). */
+ /* Since we're using a bootloader now, we can compile the kernel */
+ /* for 0x8000 and bootloader will load it properly (although it */
+ /* itself still has to be compiled for 0x10000) */
+ . = 0x8000;
+ /* For AArch64, use . = 0x80000; Unless this too is wrong in qemu… */
__start = .;
__text_start = .;
.text :
diff --git a/loader_stage1.c b/loader_stage1.c
new file mode 100644
index 0000000..d209c15
--- /dev/null
+++ b/loader_stage1.c
@@ -0,0 +1,25 @@
+#include <stddef.h>
+#include <stdint.h>
+#include <global.h>
+
+char *const stage2_addr = ((void*) 0x4000);
+
+// there's one tricky thing about embedding file in executable;
+// mainly, symbols are visible to c code as extern chars, but the actual
+// values are their adresses... see the code below
+extern char
+ _binary_loader_stage2_img_start,
+ _binary_loader_stage2_img_end,
+ _binary_loader_stage2_img_size;
+
+void kernel_main(uint32_t r0, uint32_t r1, uint32_t atags)
+{
+ // stage2 of the bootloader is a blob embedded in executable;
+ // copy it over to it's destination place
+ // TODO implement a memcpy() somewhere and use it instead of loops
+ for (size_t i = 0; i < (size_t) &_binary_loader_stage2_img_size; i++)
+ stage2_addr[i] = (&_binary_loader_stage2_img_start)[i];
+
+ // jump to stage2
+ ((void(*)(uint32_t, uint32_t, uint32_t))stage2_addr)(r0, r1, atags);
+}
diff --git a/loader_stage1.ld b/loader_stage1.ld
new file mode 100644
index 0000000..ce11095
--- /dev/null
+++ b/loader_stage1.ld
@@ -0,0 +1,47 @@
+ENTRY(_start)
+
+SECTIONS
+{
+ /* Starts at LOADER_ADDR. */
+ /* Warning! Internet says RPis in 32-bit mode load binary at 0x8000! */
+ /* My experiments do, however, show, that qemu emulating RPi2 */
+ /* loads it at 0x10000! (took some pain to find out) */
+ . = 0x10000;
+ /* For AArch64, use . = 0x80000; Unless this too is wrong */
+ __start = .;
+ __text_start = .;
+ .text :
+ {
+ KEEP(*(.text.boot))
+ *(.text)
+ }
+ . = ALIGN(4096); /* align to page size */
+ __text_end = .;
+
+ __rodata_start = .;
+ .rodata :
+ {
+ *(.rodata)
+ }
+ . = ALIGN(4096); /* align to page size */
+ __rodata_end = .;
+
+ __data_start = .;
+ .data :
+ {
+ *(.data)
+ }
+ . = ALIGN(4096); /* align to page size */
+ __data_end = .;
+
+ __bss_start = .;
+ .bss :
+ {
+ bss = .;
+ *(.bss)
+ }
+ . = ALIGN(4096); /* align to page size */
+ __bss_end = .;
+ __bss_size = __bss_end - __bss_start;
+ __end = .;
+}
diff --git a/loader_stage2.c b/loader_stage2.c
new file mode 100644
index 0000000..e221dda
--- /dev/null
+++ b/loader_stage2.c
@@ -0,0 +1,40 @@
+#include <stddef.h>
+#include <stdint.h>
+#include <uart.h>
+#include <global.h>
+
+void *const kernel_load_addr = ((void*) 0x8000);
+
+void __attribute__((section(".text.stage2main")))
+stage2(uint32_t r0, uint32_t r1, uint32_t atags)
+{
+ // Declare as unused
+ (void) r0;
+ (void) r1;
+ (void) atags;
+
+ uart_init();
+
+ // get kernel size via uart (little endian)
+ uint32_t b0, b1, b2, b3;
+
+ b0 = uart_getc();
+ b1 = uart_getc();
+ b2 = uart_getc();
+ b3 = uart_getc();
+
+ uint32_t kernel_size = b0 | (b1 << 8) | (b2 << 16) | (b3 << 24);
+
+ // load kernel at kernel_load_addr
+ char *dst = kernel_load_addr, *end = dst + kernel_size;
+
+ while (dst < end)
+ *(dst++) = uart_getc();
+
+ // jump to kernel
+ // TODO also forward arguments (r0, r1, atags)
+ asm volatile("bx %0" :: "r" (kernel_load_addr) : "memory");
+}
+
+void *const _start = ((void*) stage2); // for linker script
+
diff --git a/loader_stage2.ld b/loader_stage2.ld
new file mode 100644
index 0000000..8f215e9
--- /dev/null
+++ b/loader_stage2.ld
@@ -0,0 +1,44 @@
+ENTRY(_start)
+
+SECTIONS
+{
+ /* stage2 bootloader gets loaded at 0x4000 */
+ . = 0x4000;
+ __start = .;
+ __text_start = .;
+ .text :
+ {
+ /* have entry point at the beginning */
+ KEEP(*(.text.stage2main))
+ *(.text)
+ }
+ . = ALIGN(4096); /* align to page size */
+ __text_end = .;
+
+ __rodata_start = .;
+ .rodata :
+ {
+ *(.rodata)
+ }
+ . = ALIGN(4096); /* align to page size */
+ __rodata_end = .;
+
+ __data_start = .;
+ .data :
+ {
+ *(.data)
+ }
+ . = ALIGN(4096); /* align to page size */
+ __data_end = .;
+
+ __bss_start = .;
+ .bss :
+ {
+ bss = .;
+ *(.bss)
+ }
+ . = ALIGN(4096); /* align to page size */
+ __bss_end = .;
+ __bss_size = __bss_end - __bss_start;
+ __end = .;
+}
diff --git a/pipe_image.c b/pipe_image.c
new file mode 100644
index 0000000..7e24ea7
--- /dev/null
+++ b/pipe_image.c
@@ -0,0 +1,55 @@
+#include <stdio.h>
+#include <err.h>
+#include <endian.h>
+#include <stdint.h>
+#include <sys/types.h>
+
+#define ANSI_FG_RED "\033[0;31m"
+
+/* This program pipes it's argument file to stdout prepending it */
+/* with it's size (4 bytes, little endian). It is intended to be used */
+/* with our bootloader (i.e. by piping kernel image to UART). */
+
+int main(int argc, char **argv) {
+ char *image_file_name = argc > 1 ? argv[1] : "kernel7.img";
+
+ FILE *image_file_handle = fopen(image_file_name, "r");
+
+ if (!image_file_handle)
+ err(-1, "couldn't open" ANSI_FG_RED "%s", image_file_name);
+
+ if (fseek(image_file_handle, 0, SEEK_END))
+ err(-1, "error navigating through file");
+
+ ssize_t image_size = ftell(image_file_handle);
+ if (image_size < 0)
+ err(-1, "couldn't get image file size");
+
+ if (image_size >> 32)
+ err(-1, "file to big (should be smaller than 4G)");
+
+ if (fseek(image_file_handle, 0, SEEK_SET))
+ err(-1, "error navigating through file");
+
+ uint32_t image_size_le = htole32(image_size);
+
+ if (fwrite(&image_size_le, 4, 1, stdout) != 1)
+ err(-1, "couldn't write to stdout");
+
+ ssize_t bytes_left = image_size;
+
+ char buf[1024];
+
+ while (bytes_left)
+ {
+ size_t bytes_read;
+ if ((bytes_read = fread(buf, 1, sizeof(buf), image_file_handle))
+ < 1)
+ err(-1, "error reading the file");
+
+ if (fwrite(buf, bytes_read, 1, stdout) != 1)
+ err(-1, "error writing to stdout");
+
+ bytes_left -= bytes_read;
+ }
+}