From 661f869c429170ec81339dc31afb49ca624c9c3f Mon Sep 17 00:00:00 2001 From: SharpCoder Date: Fri, 17 Jul 2015 14:32:11 -0700 Subject: [PATCH] Added a lot of functionality including memory management, threading, keyboard, and other stuff. --- README.md | 20 +++- code/common.h | 126 ++++++++++++++++++++++++ code/console.cpp | 4 + code/console.h | 4 +- code/gpu2d.cpp | 6 +- code/gpu2d.h | 2 +- code/irq.S | 58 +++++++---- code/irq.cpp | 237 +++++++++++++++++++++++++++++++++++++++++---- code/keyboard.S | 210 +++++++++++++++++++++++++++++++++++++++ code/keyboard.h | 23 +++++ code/kmain.cpp | 94 +++++++++++------- code/libs/math.h | 223 ++++++++++++++++++++++++++++++++++++++++++ code/libs/mem.h | 79 +++++++++++++++ code/libs/string.h | 101 +++++++++++++++++++ code/linker.ld | 22 +++-- code/meta.h | 6 +- lib/libcsud.a | Bin 0 -> 51370 bytes 17 files changed, 1125 insertions(+), 90 deletions(-) create mode 100755 code/keyboard.S create mode 100755 code/keyboard.h create mode 100755 code/libs/math.h create mode 100755 code/libs/mem.h create mode 100755 code/libs/string.h create mode 100755 lib/libcsud.a diff --git a/README.md b/README.md index d3251d1..3305ce0 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,18 @@ -Raspberry Pi C++ Kernel -==== +## RasPI C++ Kernel (Mindflayer) -This is a project I'm working on dedicated to writing a kernel in C++ specifically targetted towards the Raspberry Pi. This is not a Linux Kernel, it is a bare-metal project completely from scratch. -Included in the main code directory are a few files called "RaspberryLib.cpp"/.h which provide basic functionality necessary for interfacing with the raspberry pi. They have been thoroughly tested and I encourage anyone who is interested to try it out, use my code, whatever. +This bare-metal project was written entirely from scratch and is the product of countless months of research and trial. It was my attempt at learning how to write a kernel, but this project transcends that goal. _Not only do I present to you a reasonably functional kernel, but also a lot of well commented code and a beautiful MIT license._ Use this project to your hearts content, in any way imaginable. It was born out of a search for knowledge, and I hope the blood and sweat of my journey will assist others out there. +### Features + +Mindflayer sports a hearty array of features, functions, helpers, etc. Most of the functionality lies within the framework itself, but here's a rather comprehensive list: + +- Ability to set GPIO pins. +- Super basic drawing library. +- Examples of mapping to the IVT (interrupt vector table). +- Keyboard input (thanks to Alex Chadwick from Baking Pi). +- Memory management (and support for the _new_ keyword). +- Simple threading (no scheduler, also a work-in-progress). + +### Future of This Project + +The sky is the limit! I have some really odd ideas for my kernel, but they will probably go in a separate repository. I hope to keep this project pure and simply dedicated to the nuances of kernel development. diff --git a/code/common.h b/code/common.h index 908192c..d3f9cd6 100755 --- a/code/common.h +++ b/code/common.h @@ -11,11 +11,137 @@ #ifndef __COMMON_H_ #define __COMMON_H_ +#include "./libs/mem.h" + +#define NULL 0 + typedef unsigned long ulong; typedef unsigned int uint32; +typedef volatile unsigned long int uint32_t; typedef unsigned short uint16; typedef unsigned char byte; +// Stack data structure. +template +class iterator { + public: + T val; + iterator* prev; + iterator(iterator* previous, T value ) { + this->val = value; + this->prev = previous; + } + + volatile iterator* next() { + if ( this->prev == NULL ) return NULL; + return this->prev; + } + + T getVal() { + return this->val; + } +}; + +template +class Stack { + private: + iterator* top; + int length; + + public: + void push(T* val) { + iterator* next = new iterator( this->top, val ); + this->top = next; + this->length++; + } + + T* pop( void ) { + // NULL Checking + if ( this->top == NULL ) return NULL; + + // Do the pop + T* result = this->top->getVal(); + this->top = this->top->next(); + this->length--; + + // Return the result + return result; + } + + int getLength() { + return this->length; + } + + iterator* getIterator() { + return this->top; + } +}; + +template +class List { + public: + iterator* first; + uint32 length; + + List() { + this->first = NULL; + this->length = 0; + } + + void add(T val) { + // Allocate it. + void* ptrN = malloc(sizeof(iterator)); + + iterator* newItem = new (ptrN) iterator (NULL, val); + iterator* last = this->first; + if ( last == NULL ) { + this->first = newItem; + } else if ( last->prev == NULL ) { + this->first->prev = newItem; + } else { + do { + last = last->prev; + } while ( last->prev != NULL ); + last->prev = newItem; + } + + this->length++; + } + + T getAt(int index) { + if ( this->first == NULL ) return (T)NULL; + + if ( index == 0 ) { + return this->first->val; + } else { + iterator* last = this->first; + do { + last = last->prev; + index--; + } while ( last->prev != NULL && index > 0 ); + return last->val; + } + } + + T pop() { + volatile iterator* last = this->first; + do { + last = last->prev; + } while ( last->prev != NULL ); + volatile iterator* result = last->prev; + last->prev = NULL; + return result->val; + } + + int getLength() { + return this->length; + } + + iterator* getIterator() { + return this->first; + } +}; + // Linked list structure/class class LinkedList { public: diff --git a/code/console.cpp b/code/console.cpp index 256f204..40258d0 100755 --- a/code/console.cpp +++ b/code/console.cpp @@ -64,6 +64,10 @@ void Console::kprint( const char* string ) { this->kprint( (char*) string ); } +void Console::kprint( char c ) { + this->printChar( c, 0xFFFFFF ); +} + // Clearscreen function. void Console::clear( void ) { this->charx = 0; diff --git a/code/console.h b/code/console.h index 9780e87..0cb093e 100755 --- a/code/console.h +++ b/code/console.h @@ -2,7 +2,8 @@ #define __CONSOLE_H_ #include "common.h" -#include "math.h" +#include "./libs/mem.h" +#include "./libs/math.h" #include "raspberrylib.h" #include "gpu2d.h" @@ -20,6 +21,7 @@ class Console { void kprintf( const char* string, T value ); // Standard printf functions. + void kprint( char c ); void kprint( char* string ); void kprint( const char* string ); void kbase( long value, long base, long size ); diff --git a/code/gpu2d.cpp b/code/gpu2d.cpp index 9db9f8e..d8d7059 100755 --- a/code/gpu2d.cpp +++ b/code/gpu2d.cpp @@ -1,7 +1,7 @@ // ******************************* // FILE: gpu2d.cpp // AUTHOR: SharpCoder -// DATE: 2012-03-28 +// DATE: 2013-03-28 // ABOUT: This is the 2D graphics engine (re written) for my // raspberry pi kernel. I'm trying to implement some // nicer functions and a backbuffer to make everything @@ -29,8 +29,8 @@ gpu2dCanvas::gpu2dCanvas( bool useDoubleBuffer ) { this->fbInfo = (FB_Info*)KERNEL_FB_LOC; // Setup some information about the canvas. - this->fbInfo->screen_width = 1024; - this->fbInfo->screen_height = 768; + this->fbInfo->screen_width = 800; + this->fbInfo->screen_height = 600; this->fbInfo->virtual_width = this->fbInfo->screen_width; // If we're using double buffer... diff --git a/code/gpu2d.h b/code/gpu2d.h index 1e2dd31..714b44a 100755 --- a/code/gpu2d.h +++ b/code/gpu2d.h @@ -15,7 +15,7 @@ // Include the common library. #include "common.h" #include "raspberrylib.h" -#include "mem.h" +#include "./libs/mem.h" class FB_Info { public: diff --git a/code/irq.S b/code/irq.S index 83db74d..5f2d486 100755 --- a/code/irq.S +++ b/code/irq.S @@ -1,7 +1,8 @@ .global init_entry_point +.extern kmain .extern init -.extern irq_handler +.extern interrupt_vector ;@ This is the actual entr point to the application. ;@ I need to look into how to do this another way, but it seems like @@ -44,11 +45,8 @@ init_entry_point: ;@ Next up is a trick to basically force the compiler ;@ to store the branch command as a value ;@ into the respective register. -reset_handler: - .word reset - -basic_handler: - .word arm_interrupt_handler +reset_handler: .word reset +basic_handler: .word arm_interrupt_handler ;@ Then this is our "reset" function ;@ which is what will actually get automatically @@ -66,25 +64,51 @@ reset: ldmia r0!,{r2,r3,r4,r5,r6,r7,r8,r9} stmia r1!,{r2,r3,r4,r5,r6,r7,r8,r9} + ;@ (PSR_IRQ_MODE|PSR_FIQ_DIS|PSR_IRQ_DIS) + mov r0,#0xD2 + msr cpsr_c,r0 + mov sp,#0x8000 + + ;@ (PSR_FIQ_MODE|PSR_FIQ_DIS|PSR_IRQ_DIS) + mov r0,#0xD1 + msr cpsr_c,r0 + mov sp,#0x4000 + + ;@ (PSR_SVC_MODE|PSR_FIQ_DIS|PSR_IRQ_DIS) + mov r0,#0xD3 + msr cpsr_c,r0 + mov sp,#0x800000 + ;@ And then call the bootstrapper init function. ;@ NOTE: I delegated these two assembly files because I don't ;@ want to marry my kernel to the ivt setup code. - b init - bx lr - + bl kmain + b hang hang: b hang - + +.globl enable_irq +enable_irq: + mrs r0,cpsr + bic r0,r0,#0x80 + msr cpsr_c,r0 + bx lr + ;@ And here is the actual interrupt handler code. arm_interrupt_handler: - - ;@ Store the return link. - sub r14, r14, #4 - stmfd sp!, {r0,r1,r2,r3,r4,r14} + + ;@ Store the return link. + stm sp, {r0,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12} + + ;@ Setup the arguments for our method. + mov r0, lr + mov r1, sp ;@ Invoke our C++ irq handler. - bl irq_handler + bl interrupt_vector + ;@ subs pc, r14, #4 + b hang ;@ Restore to the original caller. - ldmfd sp!, {r0,r1,r2,r3,r4,pc}^ - bx lr + ;@ ldmfd sp!, {r0,r1,r2,r3,r4,pc}^ + ;@ bx lr diff --git a/code/irq.cpp b/code/irq.cpp index ff8efd4..bc16851 100755 --- a/code/irq.cpp +++ b/code/irq.cpp @@ -1,37 +1,232 @@ #include "raspberrylib.h" +#include "./libs/mem.h" #include "console.h" -Console* irq_console; -bool use_irq_console; +#define RPI_INTERRUPT_CONTROLLER_BASE 0x2000B200 +#define RPI_ARMTIMER_BASE 0x2000B400 -extern "C" void irq_handler( void ) { +#define RPI_ARMTIMER_CTRL_23BIT ( 1 << 1 ) +#define RPI_ARMTIMER_CTRL_PRESCALE_1 ( 0 << 2 ) +#define RPI_ARMTIMER_CTRL_PRESCALE_16 ( 1 << 2 ) +#define RPI_ARMTIMER_CTRL_PRESCALE_256 ( 2 << 2 ) +#define RPI_ARMTIMER_CTRL_INT_ENABLE ( 1 << 5 ) +#define RPI_ARMTIMER_CTRL_INT_DISABLE ( 0 << 5 ) +#define RPI_ARMTIMER_CTRL_ENABLE ( 1 << 7 ) +#define RPI_ARMTIMER_CTRL_DISABLE ( 0 << 7 ) + +typedef struct { + volatile uint32_t Load; + volatile uint32_t Value; + volatile uint32_t Control; + volatile uint32_t IRQClear; + volatile uint32_t RAWIRQ; + volatile uint32_t MaskedIRQ; + volatile uint32_t Reload; + volatile uint32_t PreDivider; + volatile uint32_t FreeRunningCounter; +} rpi_arm_timer_t; + +typedef struct { + volatile uint32_t IRQ_basic_pending; + volatile uint32_t IRQ_pending_1; + volatile uint32_t IRQ_pending_2; + volatile uint32_t FIQ_control; + volatile uint32_t Enable_IRQs_1; + volatile uint32_t Enable_IRQs_2; + volatile uint32_t Enable_Basic_IRQs; + volatile uint32_t Disable_IRQs_1; + volatile uint32_t Disable_IRQs_2; + volatile uint32_t Disable_Basic_IRQs; +} rpi_irq_controller_t; + +static rpi_irq_controller_t* rpiIRQController = (rpi_irq_controller_t*)RPI_INTERRUPT_CONTROLLER_BASE; +static rpi_arm_timer_t* rpiArmTimer = (rpi_arm_timer_t*)RPI_ARMTIMER_BASE; +rpi_arm_timer_t* RPI_GetArmTimer(void) { return rpiArmTimer; } +rpi_irq_controller_t* RPI_GetIrqController( void ) { return rpiIRQController; } +void irq_deactivate(void); +void irq_init(void); + +extern "C" struct thread { + uint32_t addr; + uint32_t lr; + uint32_t sptr; + uint32 pid; + bool isRun; + bool isDead; + bool isLock; +}; + +// Keep a queue of jobs. +static List threads; + +// These tell the control thread what's up. +static short irqStatus = 0; + +static short ind = 0; +static uint32 guid = 0; +static uint32 turn = 0; +static bool led_on = false; +Console* irqConsole; + +void fork(void (*ptr)(void)) { + thread* t = new thread(); + t->pid = guid++; + t->isRun = false; + t->isDead = false; + t->isLock = false; + t->addr = (uint32_t)ptr; + threads.add(t); +} + +thread* next_task() { + int len = threads.getLength(); + thread* result; - if ( use_irq_console ) - irq_console->kout("INTERRUPT"); - - // Blink once to show we've been here. - RaspberryLib::Wait( 100 ); - return; + // Iterate over the threads an increment until we come to a live thread. + for ( int i = 0; i < len; i++ ) { + if ( ++ind >= len ) + ind = 0; + + // Find the next one that is not dead. + result = threads.getAt(ind); + if ( !result->isDead ) + break; + } + + return result; +} + +void lock() { + irq_deactivate(); + // Set the current thread to locked. + uint32 length = threads.getLength(); + if ( length > 0 ) { + thread* active = threads.getAt(ind); + active->isLock = true; + } + // Resume interrupts. + irq_init(); +} + +void unlock() { + irq_deactivate(); + uint32 length = threads.getLength(); + thread* active = threads.getAt(ind); + active->isLock = false; + irq_init(); } -bool irq_enable( void ) { +extern "C" void interrupt_vector() { + // Clear the arm timer interrupt. + uint32_t ptr_sp; + uint32_t ptr_lr; + + asm volatile("mov %0,r0\n\t" + "mov %1,r1\n\t" + : "=r"(ptr_lr), "=r"(ptr_sp) : : "r0", "r1", "memory"); + + + if ( threads.getLength() == 0 ) { + return; + } + + // Flip the LED each iteration, to show we're actively capturing + // interrupts. + RaspberryLib::SetGPIO(16, !led_on); + led_on = !led_on; + + // We get the active thread (that which last run) and we + // store the return pointer and other related information + // in the thread control block. + thread* t = threads.getAt(ind); + bool resumeThread = false; + + // Check the irqStatus and finish processing if necessary. + if ( irqStatus == 1 ) { + // Enable interrupts again. + irq_init(); + } else if ( irqStatus == 2 ) { + // This is bad... We don't actually want to jump to another thread then. + irq_deactivate(); + resumeThread = true; + } + + // Reset IRQ Status. + irqStatus = 0; - volatile uint32* address = (volatile uint32*)( 0x2000b000 ); + if ( t->isRun ) { + t->sptr = ptr_sp; + t->lr = ptr_lr; + + if ( !resumeThread ) { + thread* next = next_task(); + if (!t->isLock && turn == t->pid) + turn = next->pid; + t = next; + } + } - // ENABLE IRQ 1 - *( address + 0x210 ) = 0xFFFFFFFF; + if ( !t->isRun ) { + // Update the ish. + t->lr = t->addr; + t->sptr = (uint32_t)alloc_stack(128); + t->isRun = true; + RPI_GetArmTimer()->IRQClear = 1; + + // We need to allocate a new stack. + // Then we need to copy the registers over to it. + asm volatile("mov r0,%0\n\t" // new stack pointer + "mov r1,%1\n\t" // old stack pointer + "mov r14,%2\n\t" + "mov sp,r0\n\t" // move the stack pointer + "ldm r1,{r0-r12}\n\t" // load the registers from the old location + "subs pc,r14,#4\n\t" + : : "r"(t->sptr), "r"(ptr_sp), "r"(t->lr) : "memory", "r0", "r1", "sp", "r14", "pc" ); + + // This code gets executed after, theoretically, the thread finishes. + t->isLock = false; + t->isDead = true; + while(true) { } + return; + } - // ENABLE IRQ 2 - *( address + 0x214 ) = 0xFFFFFFFF; + RPI_GetArmTimer()->IRQClear = 1; - // ENABLE BASIC IRQ - *( address + 0x218 ) = 0x000000FF; + // If the thread is locked, spin. + while(t->isLock && turn != t->pid) { } - return true; + // Otherwise, we can execute our ish. + asm volatile("mov r14,%0\n\t" + "mov r0,%1\n\t" + "mov sp,r0\n\t" + "ldm sp,{r0-r12}\n\t" + "subs pc,r14,#4\n\t": : "r"(t->lr), "r"(t->sptr) : "memory","sp", "r14", "r0", "pc" ); + + return; } -void irq_test( void ) { +void irq_init( void ) { + // Tell the control thread what's what. + irqStatus = 1; + RPI_GetIrqController()->Enable_Basic_IRQs = (1<<0); - asm volatile( "SWI #0x0000FF" ); - + /* Timer frequency = Clk/256 * 0x400 */ + RPI_GetArmTimer()->Load = 0x100; // Fast 0xa0 + + /* Setup the ARM Timer */ + RPI_GetArmTimer()->Control = + RPI_ARMTIMER_CTRL_23BIT | + RPI_ARMTIMER_CTRL_ENABLE | + RPI_ARMTIMER_CTRL_INT_ENABLE | + RPI_ARMTIMER_CTRL_PRESCALE_256; + + irqStatus = 0; +} + +void irq_deactivate() { + // Tell the control thread what's what. + irqStatus = 2; + RPI_GetIrqController()->Enable_Basic_IRQs = 0; + RPI_GetIrqController()->Disable_Basic_IRQs = (1<<0); + irqStatus = 0; } diff --git a/code/keyboard.S b/code/keyboard.S new file mode 100755 index 0000000..c34d443 --- /dev/null +++ b/code/keyboard.S @@ -0,0 +1,210 @@ +/****************************************************************************** +* keyboard.s +* by Alex Chadwick +* +* A sample assembly code implementation of the input02 operating system. +* See main.s for details. +* +* keyboard.s contains code to do with the keyboard. +******************************************************************************/ + +.section .text +/* +* The address of the keyboard we're reading from. +* C++ Signautre: u32 KeyboardAddress; +*/ +.align 2 +KeyboardAddress: + .int 0 + +/* +* The scan codes that were down before the current set on the keyboard. +* C++ Signautre: u16* KeyboardOldDown; +*/ +KeyboardOldDown: + .rept 6 + .hword 0 + .endr + +/* +* KeysNoShift contains the ascii representations of the first 104 scan codes +* when the shift key is up. Special keys are ignored. +* C++ Signature: char* KeysNoShift; +*/ +.align 3 +KeysNormal: + .byte 0x0, 0x0, 0x0, 0x0, 'a', 'b', 'c', 'd' + .byte 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l' + .byte 'm', 'n', 'o', 'p', 'q', 'r', 's', 't' + .byte 'u', 'v', 'w', 'x', 'y', 'z', '1', '2' + .byte '3', '4', '5', '6', '7', '8', '9', '0' + .byte '\n', 0x0, '\b', '\t', ' ', '-', '=', '[' + .byte ']', '\\', '#', ';', '\'', '`', ',', '.' + .byte '/', 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 + .byte 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 + .byte 0xFF, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x0, 0x0 + .byte 0x0, 0x0, 0x0, 0x0, '/', '*', '-', '+' + .byte '\n', '1', '2', '3', '4', '5', '6', '7' + .byte '8', '9', '0', '.', '\\', 0x0, 0x0, '=' + +/* +* KeysShift contains the ascii representations of the first 104 scan codes +* when the shift key is held. Special keys are ignored. +* C++ Signature: char* KeysShift; +*/ +.align 3 +KeysShift: + .byte 0x0, 0x0, 0x0, 0x0, 'A', 'B', 'C', 'D' + .byte 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L' + .byte 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T' + .byte 'U', 'V', 'W', 'X', 'Y', 'Z', '!', '"' + .byte '$', '$', '%', '^', '&', '*', '(', ')' + .byte '\n', 0x0, '\b', '\t', ' ', '_', '+', '{' + .byte '}', '|', '~', ':', '@', '$', '<', '>' + .byte '?', 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 + .byte 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 + .byte 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 + .byte 0x0, 0x0, 0x0, 0x0, '/', '*', '-', '+' + .byte '\n', '1', '2', '3', '4', '5', '6', '7' + .byte '8', '9', '0', '.', '|', 0x0, 0x0, '=' + +.section .text +/* +* Updates the keyboard pressed and released data. +* C++ Signature: void KeyboardUpdate(); +*/ +.globl KeyboardUpdate +KeyboardUpdate: + push {r4,r5,lr} + + kbd .req r4 + ldr r0,=KeyboardAddress + ldr kbd,[r0] + + teq kbd,#0 + bne haveKeyboard$ + +getKeyboard$: + bl UsbCheckForChange + bl KeyboardCount + teq r0,#0 + ldreq r1,=KeyboardAddress + streq r0,[r1] + beq return$ + + mov r0,#0 + bl KeyboardGetAddress + ldr r1,=KeyboardAddress + str r0,[r1] + teq r0,#0 + beq return$ + mov kbd,r0 + +haveKeyboard$: + mov r5,#0 + + saveKeys$: + mov r0,kbd + mov r1,r5 + bl KeyboardGetKeyDown + + ldr r1,=KeyboardOldDown + add r1,r5,lsl #1 + strh r0,[r1] + add r5,#1 + cmp r5,#6 + blt saveKeys$ + + mov r0,kbd + bl KeyboardPoll + teq r0,#0 + bne getKeyboard$ + +return$: + pop {r4,r5,pc} + .unreq kbd + +/* +* Returns r0=0 if a in r1 key was not pressed before the current scan, and r0 +* not 0 otherwise. +* C++ Signature bool KeyWasDown(u16 scanCode) +*/ +.globl KeyWasDown +KeyWasDown: + ldr r1,=KeyboardOldDown + mov r2,#0 + + keySearch$: + ldrh r3,[r1] + teq r3,r0 + moveq r0,#1 + moveq pc,lr + + add r1,#2 + add r2,#1 + cmp r2,#6 + blt keySearch$ + + mov r0,#0 + mov pc,lr + +/* +* Returns the ascii character last typed on the keyboard, with r0=0 if no +* character was typed. +* C++ Signature char KeyboardGetChar() +*/ +.globl KeyboardGetChar +KeyboardGetChar: + ldr r0,=KeyboardAddress + ldr r1,[r0] + teq r1,#0 + moveq r0,#0 + moveq pc,lr + + push {r4,r5,r6,lr} + + kbd .req r4 + key .req r6 + + mov r4,r1 + mov r5,#0 + + keyLoop$: + mov r0,kbd + mov r1,r5 + bl KeyboardGetKeyDown + + teq r0,#0 + beq keyLoopBreak$ + + mov key,r0 + bl KeyWasDown + teq r0,#0 + bne keyLoopContinue$ + + cmp key,#104 + bge keyLoopContinue$ + + mov r0,kbd + bl KeyboardGetModifiers + + tst r0,#0b00100010 + ldreq r0,=KeysNormal + ldrne r0,=KeysShift + + ldrb r0,[r0,key] + teq r0,#0 + bne keyboardGetCharReturn$ + + keyLoopContinue$: + add r5,#1 + cmp r5,#6 + blt keyLoop$ + + keyLoopBreak$: + mov r0,#0 +keyboardGetCharReturn$: + pop {r4,r5,r6,pc} + .unreq kbd + .unreq key + diff --git a/code/keyboard.h b/code/keyboard.h new file mode 100755 index 0000000..c5c20a4 --- /dev/null +++ b/code/keyboard.h @@ -0,0 +1,23 @@ +#ifndef __KEYBOARD_H_ +#define __KEYBOARD_H_ + +// Include core files. +#include "common.h" + +// List all of the functions that our library exports. +extern "C" { + uint32 KeyboardAddress; + uint16* KeyboardOldDown; + char* KeysNoShift; + char* KeysShift; + void KeyboardUpdate(); + bool KeyWasDown(uint16 scanCode); + char KeyboardGetChar(); + extern void UsbInitialise(); + extern void UsbCheckForChange(); + extern int KeyboardCount(); + extern void KbdLoad(); +}; + + +#endif diff --git a/code/kmain.cpp b/code/kmain.cpp index 4e333df..1019ec4 100755 --- a/code/kmain.cpp +++ b/code/kmain.cpp @@ -11,67 +11,93 @@ #include "raspberrylib.cpp" #include "gpu2d.cpp" #include "console.cpp" +#include "keyboard.h" // Include the meta data generate at compile time -// #include "meta.h" -#include "mem.h" -#include "math.h" +#include "./libs/mem.h" +#include "./libs/math.h" +#include "./libs/string.h" #include "meta.h" using namespace RaspberryLib; +Console* console; + // Define any functions. void print_header( Console* console ); +void printf( const char* c); +void assert( const char* c); +extern "C" void enable_irq(); + +void run1() { + while(true) { + // NOTE: locking mechanism has issues... + lock(); + // NOTE: printf() is not thread safe. + //printf("Hello from thread 1\n"); + unlock(); + Wait(1000); + } +} + +void run2() { + while(true) { + // NOTE: locking mechanism has issues... + lock(); + // NOTE: printf() is not thread safe. + //printf("Hello from thread 2\n"); + unlock(); + Wait(1000); + } +} // Define the entry point for our application. // Note: It must be marked as "extern" in order for the linker // to see it properly. extern "C" void kmain( void ) { - + + // Initialize the irq + irq_init(); + + // Initialize at zero. + sbrk(0); + // Create a canvas. gpu2dCanvas canvas(false); // Create a console. - Console console(&canvas); + console = new Console(&canvas); - // Wire up the interrupts. - irq_console = &console; - use_irq_console = true; + // setup the IRQ console + irqConsole = console; // Draw to the console. - print_header( &console ); - - console.kprint("Waiting: "); - int index; - for(index = 18; index > 0; index-- ) { - console.kprint("."); - Wait( 300 ); - } - console.kprint("\n[STARTING]\n\n"); - - // Initialize memory management first. - init_page_table(); - console.kout("Initialized page table"); - - // Turn on the green light to signify the end - // of our initial kernel code. - irq_enable(); - Wait(500); - console.kout("Interrupt vectors ENABLED"); - console.kprint("About to throw an SWI exception...\n"); + print_header( console ); - Wait( 5000 ); - irq_test(); + //UsbInitialise(); + //assert("Keyboard Initialized"); - Wait(500); - console.kout("SWI Exception Thrown"); + // Queue up a few jobs. + fork(&run1); + fork(&run2); - SetGPIO( 16, 1 ); + // Setup IRQ + assert("IRQ Enabled"); + enable_irq(); - console.kprint("\n\nKernel shutting down..."); + // Hang forever. + while(1) { } return; } +void assert(const char* val) { + console->kout(val); +} + +void printf(const char* val) { + console->kprint(val); +} + void print_header( Console* console ) { meta info = getBuildInfo(); diff --git a/code/libs/math.h b/code/libs/math.h new file mode 100755 index 0000000..b4cab17 --- /dev/null +++ b/code/libs/math.h @@ -0,0 +1,223 @@ +#ifndef __MATH_H_ +#define __MATH_H_ + +#include "../common.h" +#include "./mem.h" +#include "./string.h" + +typedef long mint; +typedef unsigned long umint; + + +namespace Math { + + // Basic methods not bound to anything. + template + T divide( T top, T bottom, T* result, T* remainder ) { + + // Calculate whether or not we need to flip the sign + // afterwards. + bool flipTop = (top < 0 ); + bool flipBottom = (bottom < 0); + + if ( flipTop ) top *= -1; + if ( flipBottom ) bottom *= -1; + + // Test for some edge cases first. + if ( bottom == 0 ) { + *result = -1; + *remainder = -1; + return *result; + } + + if ( top < bottom ) { + *result = 0; + *remainder = top; + return *result; + } + + // Reset the pointer variables and create some temp + // containers. + int topVar = top, bottomVar = bottom; + *result = 0; + *remainder = 0; + + // Do long division (note: everything should be positive now). + for (; topVar >= bottomVar; topVar -= bottomVar ) { + *(result) = *(result) + 1; + } + + // Calculate the remainder. + *(remainder) = top - (*(result) * bottom); + + // Do the flips. + if ( flipTop ) *(result) = *(result) * -1; + if ( flipBottom ) *(result) = *(result) * -1; + + // Return the result. + return *(result); + + } + + template + T pow( T x, T y, bool standard ) { + if ( standard ) { + if ( y == 0 ) return 1; + if ( y == 1 ) return x; + } + + T i = 0, r = x; + for ( ; i < y; i++ ) + r = r * x; + return r; + } + + + template + T pow(T x, T y) { + return pow( x, y, true ); + } + + + char* itoa(uint32 number) { + char digits = 0; + do { } while ( digits < 10 && (number / pow(10, digits++, false)) != 0 ); + + char map[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' }; + char* res = (char*)malloc((digits+1) * sizeof(char)); + res[digits] = '\0'; + int base = 0; + + // Iterate over the digits; + for ( int i = digits - 1; i >= 0; i-- ) { + + int index; + if ( i == 0 ) + index = number - base; + else + index = (number - base) / pow(10,i-1,false); + + if ( index < 10 && index >= 0) + res[digits - i - 1] = map[index]; + + if ( i == 0 ) break; + base += index * pow(10,i-1,false); + } + + // Return the result. + return res; + } + + template + T getDigitCount(T num, T base) { + T top = num, result = 0, remainder = 0, returnVal = 0; + while ( top > 0 ) { + divide( top, base, &result, &remainder ); + top = result; + returnVal++; + } + + return returnVal; + } + + class kfloat { + public: + // Division operator override. + kfloat operator/(const kfloat &target){ + if ( this == &target ) return *this; + + // Do the math. + mint top = this->major, bottom = target.major, result = 0, remainder = 0, i; + + // Now calculate the proper. + divide( top, bottom, &result, &remainder); + + // Store the result. + this->major = result; + this->minor = 0; + + // Calculate the decimal. + for ( i = this->precision; i > 0; i-- ) { + // Do the long division. + divide( remainder * 10, bottom, &result, &remainder ); + this->minor += ( pow( 10, i - 1 ) * result ); + } + + // Return + return *this; + } + + kfloat operator=(const mint &target) { + this->init( target, 0, 2 ); + return *this; + } + + // Constructors + kfloat( void ) { this->init( 0, 0, 0); } + kfloat( mint num ) { this->init( num, 0, 2 ); } + kfloat( mint num, mint dec ) { this->init( num, dec, 2 ); } + kfloat( mint num, mint dec, mint prec) { this->init( num, dec, prec ); } + + // Basic assignment operations. + mint getMajor() { + return this->major; + } + + mint getMinor() { + return this->minor; + } + + mint getPrecision() { + return this->precision; + } + + mint setPrecision(mint p) { + this->precision = p; + } + + mint getBig1() { + return this->big1; + } + + mint getBig2() { + return this->big2; + } + + bool getIsLarge() { + return this->isLarge; + } + + private: + mint major; + mint minor; + mint precision; + + /* Note: this is used exclusivly for very large numbers */ + bool isLarge; + mint big1; + mint big2; + + void init( mint num, mint dec, mint prec ) { + + // Check if we need to incorporate the big major. + if ( num > (umint)0xFFFFFF) { + // Yes. + this->big1 = (num & 0xFFFF0000) >> 16;// >> (4 * 4); + this->big2 = (num & 0x0000FFFF); + this->isLarge = true; + } else { + // No + this->big1 = num; + this->isLarge = false; + } + + this->major = num; + this->minor = dec; + this->precision = prec; + } + }; + +}; + + +#endif diff --git a/code/libs/mem.h b/code/libs/mem.h new file mode 100755 index 0000000..3abbd3f --- /dev/null +++ b/code/libs/mem.h @@ -0,0 +1,79 @@ +// This is a completely blank test arena for memory management. +// The pre-implemented test case functions (defined at the bottom) are +// already wired up to the drawing engine. An array of possible output colors +// are defined which reveal what test(s) passed/failed. + +// The idea is to write up a variety of methods and general +// functionality based around memory management to experiment and see +// what we have available. + +// Please note: I've never written any memory management system, nor have I +// taken a class on it. I'm just winging it with my own ideas. +// Nothing fancy here. + +#ifndef __MEM_H_ +#define __MEM_H_ + +typedef unsigned int uint32; + +#define page_size (uint32)512 +#define MAX_BRK 0xf0000 + +extern char _end; + +uint32 cur_brk = 0; + +uint32 alloc_stack(uint32 bytes) { + cur_brk += bytes; + return cur_brk; +} + +void *sbrk(uint32 increment) +{ + if(cur_brk == 0) + { + cur_brk = (uint32)&_end; + if(cur_brk & 0xfff) + { + cur_brk &= 0xfffff000; + cur_brk += 0x1000; + } + } + + uint32 old_brk = cur_brk; + cur_brk += increment; + + // Align up to 512 + if(cur_brk & 0x1ff) + { + cur_brk &= ~0x1ff; + cur_brk += 0x200; + } + + // Fix if it's too big. + if ( cur_brk > MAX_BRK ) { + cur_brk = old_brk; + return (void*)-1; + } + + // Zero out the memory. + for ( int i = 0; i < increment; i++ ) + *((uint32*)old_brk + i) = 0; + + // return the address. + return (void*)old_brk; +} + +void* malloc(uint32 bytes) { + return sbrk(bytes); +} + +void* operator new( uint32 bytes ) { + return malloc(bytes); +} + +void* operator new( uint32 bytes, void*& ptr ) { + return ptr; +} + +#endif diff --git a/code/libs/string.h b/code/libs/string.h new file mode 100755 index 0000000..0a9ee74 --- /dev/null +++ b/code/libs/string.h @@ -0,0 +1,101 @@ +// ******************************* +// FILE: kstring.h +// AUTHOR: SharpCoder +// DATE: 2015-03-30 +// ABOUT: This is a terrible, horrible, repulsive +// string library I wrote. +// +// LICENSE: Provided "AS IS". USE AT YOUR OWN RISK. +// ******************************* +#ifndef __PI_STRING_H_ +#define __PI_STRING_H_ + +#include "./mem.h" + +int strlen(char* input); + +class string { + private: + char* value; + + public: + int length; + + string(void) { + this->value = (char*)"\0"; + } + string(const char* val) { + this->value = (char*)"\0"; + this->append((char*)val); + } + + void append(const char* c) { + this->append((char*)c); + } + + void append(char* c) { + int a = strlen(this->value); + int b = strlen(c); + int len = a + b; + + char* newVal = (char*)malloc((len + 1) * sizeof(char)); + // Clear it out. + for ( int i = 0; i < len + 1; i++ ) + *(newVal + i) = '\0'; + + for ( int i = 0; i < a; i++ ) + *(newVal + i) = this->value[i]; + + for ( int i = 0; i < b; i++ ) + *(newVal + a + i) = *(c + i); + + this->value = newVal; + this->length = a + b + 1; + } + + void append(char c) { + char vals[] = { c, '\0' }; + this->append(vals); + } + + char getAt(int index) { + if ( index > this->length ) return (char)NULL; + return *(this->value + index); + } + + string* substr(int start, int end) { + string* der = new string(); + for ( int i = start; i < end; i++ ) { + der->append(*(this->value + i)); + } + return der; + } + + char* toString() { + return this->value; + } +}; + +int strlen(char* input) { + int index = 0; + char temp = ' '; + do { + temp = input[index++]; + } while ( temp != '\0' ); + return index - 1; +} + +bool strcmp(string* a, string* b) { + if ( a->length != b->length ) return false; + + const char* one = a->toString(); + const char* two = b->toString(); + + for ( int i = 0; i < a->length; i++ ) { + if ( one[i] != two[i] ) return false; + } + + return true; +} + +#endif diff --git a/code/linker.ld b/code/linker.ld index 2fd77b4..e0ca623 100755 --- a/code/linker.ld +++ b/code/linker.ld @@ -1,13 +1,23 @@ +ENTRY (init_entry_point) MEMORY { - ram : ORIGIN = 0x8000, LENGTH = 0x5000 + ram : ORIGIN = 0x8000, LENGTH = 0x10000 } SECTIONS { - .text : { - irq.o; - bootstrap.o; - kmain.o; - } > ram + .text : { *(.text*) } > ram + . = ALIGN(4096); + + .data : { *(.data*) } > ram + . = ALIGN(4096); + + .bss : { *(.bss*) } > ram + . = ALIGN(4096); + + _bss_end = .; + . = ALIGN(4096); + + . = . + 0x1000; + _end = .; } diff --git a/code/meta.h b/code/meta.h index 5d69bea..fc60d65 100644 --- a/code/meta.h +++ b/code/meta.h @@ -25,11 +25,11 @@ static meta getBuildInfo() { meta properties; properties.AUTHOR="SharpCoder"; properties.EMAIL="Josuha@debuggle.com"; - properties.KERNEL_NAME="0xrpi Kernel"; + properties.KERNEL_NAME="Mindflayer"; properties.KERNEL_NAME_CODE="Mindflayer"; properties.KERNEL_REPO="https://github.com/SharpCoder/rpi-kernel"; - properties.BUILD_DATE="2013-04-02 11:48:47 PM"; - properties.VERSION="0.0.623"; + properties.BUILD_DATE="2015-07-17 02:04:55 PM"; + properties.VERSION="0.0.723"; properties.LOGO_TEXT=""; properties.LOGO_WIDTH= 30; properties.LOGO_HEIGHT= 50; diff --git a/lib/libcsud.a b/lib/libcsud.a new file mode 100755 index 0000000000000000000000000000000000000000..f76ecd2071f0e4f25be96120d302f7637718e9c5 GIT binary patch literal 51370 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 literal 0 HcmV?d00001