Initial Commit

master
rubenwardy 2017-03-13 16:22:39 +00:00
commit bb84f09914
39 changed files with 1734 additions and 0 deletions

72
.gitignore vendored Normal file
View File

@ -0,0 +1,72 @@
# Created by https://www.gitignore.io/api/linux,c
### C ###
# Prerequisites
*.d
# Object files
*.o
*.ko
*.obj
*.elf
# Linker output
*.ilk
*.map
*.exp
# Precompiled Headers
*.gch
*.pch
# Libraries
*.lib
*.a
*.la
*.lo
# Shared objects (inc. Windows DLLs)
*.dll
*.so
*.so.*
*.dylib
# Executables
*.exe
*.out
*.app
*.i*86
*.x86_64
*.hex
# Debug files
*.dSYM/
*.su
*.idb
*.pdb
# Kernel Module Compile Results
*.mod*
*.cmd
modules.order
Module.symvers
Mkfile.old
dkms.conf
### Linux ###
*~
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
# .nfs files are created when an open file is removed but is still being accessed
.nfs*
# End of https://www.gitignore.io/api/linux,c

54
Makefile Normal file
View File

@ -0,0 +1,54 @@
# part 1: variables
PROJECT_PATH = $(shell find . -mindepth 1 -maxdepth 1 -type d)
PROJECT_SOURCES = $(shell find ${PROJECT_PATH} -name *.c -o -name *.s)
PROJECT_HEADERS = $(shell find ${PROJECT_PATH} -name *.h )
PROJECT_OBJECTS = $(addsuffix .o, $(basename ${PROJECT_SOURCES}))
PROJECT_TARGETS = image.elf image.bin
QEMU_PATH = /usr
QEMU_GDB = 127.0.0.1:1234
QEMU_UART = stdio
#QEMU_UART += telnet:127.0.0.1:1235,server
#QEMU_UART += telnet:127.0.0.1:1236,server
QEMU_DISPLAY = -nographic -display none
#QEMU_DISPLAY = -display sdl
LINARO_PATH = /usr/local/gcc-linaro-5.1-2015.08-x86_64_arm-eabi
LINARO_PREFIX = arm-eabi
# part 2: build commands
%.o : %.s
@${LINARO_PATH}/bin/${LINARO_PREFIX}-as $(addprefix -I , ${PROJECT_PATH} ${LINARO_PATH}/arm-eabi/libc/usr/include) -mcpu=cortex-a8 -g -o ${@} ${<}
%.o : %.c
@${LINARO_PATH}/bin/${LINARO_PREFIX}-gcc $(addprefix -I , ${PROJECT_PATH} ${LINARO_PATH}/arm-eabi/libc/usr/include) -mcpu=cortex-a8 -mabi=aapcs -ffreestanding -std=gnu99 -g -c -O -o ${@} ${<}
%.elf : ${PROJECT_OBJECTS}
@${LINARO_PATH}/bin/${LINARO_PREFIX}-ld $(addprefix -L , ${LINARO_PATH}/arm-eabi/libc/usr/lib ) -T ${*}.ld -o ${@} ${^} -lc -lgcc
%.bin : %.elf
@${LINARO_PATH}/bin/${LINARO_PREFIX}-objcopy -O binary ${<} ${@}
# part 3: targets
.PRECIOUS : ${PROJECT_OBJECTS} ${PROJECT_TARGETS}
build : ${PROJECT_TARGETS}
launch-qemu : ${PROJECT_TARGETS}
@${QEMU_PATH}/bin/qemu-system-arm -M realview-pb-a8 -m 128M ${QEMU_DISPLAY} -gdb tcp:${QEMU_GDB} $(addprefix -serial , ${QEMU_UART}) -S -kernel $(filter %.bin, ${PROJECT_TARGETS})
launch-gdb : ${PROJECT_TARGETS}
@${LINARO_PATH}/bin/${LINARO_PREFIX}-gdb -ex "file $(filter %.elf, ${PROJECT_TARGETS})" -ex "target remote ${QEMU_GDB}"
kill-qemu :
@-killall --quiet --user ${USER} qemu-system-arm
kill-gdb :
@-killall --quiet --user ${USER} ${LINARO_PREFIX}-gdb
clean :
@rm -f core ${PROJECT_OBJECTS} ${PROJECT_TARGETS}
include Makefile.console
include Makefile.disk

9
Makefile.console Normal file
View File

@ -0,0 +1,9 @@
# part 1: variables
CONSOLE_HOST = 127.0.0.1
CONSOLE_PORT = 1235
# part 3: targets
launch-console :
@nc ${CONSOLE_HOST} ${CONSOLE_PORT}

18
Makefile.disk Normal file
View File

@ -0,0 +1,18 @@
# part 1: variables
DISK_FILE = disk.bin
DISK_HOST = 127.0.0.1
DISK_PORT = 1236
DISK_BLOCK_NUM = 65536
DISK_BLOCK_LEN = 16
# part 3: targets
create-disk :
@dd of=${DISK_FILE} if=/dev/zero count=${DISK_BLOCK_NUM} bs=${DISK_BLOCK_LEN}
inspect-disk :
@hexdump -C ${DISK_FILE}
launch-disk :
@python device/disk.py --host=${DISK_HOST} --port=${DISK_PORT} --file=${DISK_FILE} --block-num=${DISK_BLOCK_NUM} --block-len=${DISK_BLOCK_LEN}

10
device/GIC.c Normal file
View File

@ -0,0 +1,10 @@
#include "GIC.h"
volatile GICC_t* GICC0 = ( volatile GICC_t* )( 0x1E000000 );
volatile GICD_t* GICD0 = ( volatile GICD_t* )( 0x1E001000 );
volatile GICC_t* GICC1 = ( volatile GICC_t* )( 0x1E010000 );
volatile GICD_t* GICD1 = ( volatile GICD_t* )( 0x1E011000 );
volatile GICC_t* GICC2 = ( volatile GICC_t* )( 0x1E020000 );
volatile GICD_t* GICD2 = ( volatile GICD_t* )( 0x1E021000 );
volatile GICC_t* GICC3 = ( volatile GICC_t* )( 0x1E030000 );
volatile GICD_t* GICD3 = ( volatile GICD_t* )( 0x1E031000 );

114
device/GIC.h Normal file
View File

@ -0,0 +1,114 @@
#ifndef __GIC_H
#define __GIC_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#define RSVD(x,y,z) uint8_t reserved##x[ z - y + 1 ];
/* Although the GIC architecture is documented at
*
* http://infocenter.arm.com/help/topic/com.arm.doc.ihi0048b/index.html
*
* the platform includes a bespoke implementation based on the combination
* of 4 GIC components in total.
*
* - Section 3.14 gives a high-level overview of the interrupt mechanism,
* noting in particular that GIC0 and GIC1 are those associated with the
* ARM core (managing IRQ- and FIQ-based interrupts respectively),
* - Section 4.11 describes the GIC implementation, which sub-divides each
* GIC into interface and distributor components: it includes
*
* - Table 4.44, i.e., the mapping of interrupt signals from
* other devices to interrupt IDs wrt. each GIC,
* - Tables 4.46 and 4.55, i.e., the device register layout (including
* an offset from the device base address, in the memory map, for each
* register), plus
* - a summary of the internal structure of each device register.
*
* Note that the field identifiers used here follow the documentation in a
* general sense, but with a some minor alterations to improve clarity and
* consistency.
*/
typedef volatile struct {
uint32_t CTLR; // 0x0000 : control
uint32_t PMR; // 0x0004 : priority mask
uint32_t BPR; // 0x0008 : binary point
uint32_t IAR; // 0x000C : interrupt acknowledge
uint32_t EOIR; // 0x0010 : end of interrupt
uint32_t RPR; // 0x0014 : running interrupt
uint32_t HPPIR; // 0x0018 : highest pending interrupt
} GICC_t;
typedef volatile struct {
uint32_t CTLR; // 0x0000 : control
uint32_t TYPER; // 0x0004 : controller type
RSVD( 0, 0x0008, 0x00FC ); // 0x0008...0x00FC : reserved
uint32_t ISENABLER0; // 0x0100 : set-enable
uint32_t ISENABLER1; // 0x0104 : set-enable
uint32_t ISENABLER2; // 0x0108 : set-enable
RSVD( 1, 0x010C, 0x017C ); // 0x010C...0x017C : reserved
uint32_t ICENABLER0; // 0x0180 : clear-enable
uint32_t ICENABLER1; // 0x0184 : clear-enable
uint32_t ICENABLER2; // 0x0188 : clear-enable
RSVD( 2, 0x018C, 0x01FC ); // 0x018C...0x01FC : reserved
uint32_t ISPENDR0; // 0x0200 : set-pending
uint32_t ISPENDR1; // 0x0204 : set-pending
uint32_t ISPENDR2; // 0x0208 : set-pending
RSVD( 3, 0x020C, 0x027C ); // 0x020C...0x027C : reserved
uint32_t ICPENDR0; // 0x0280 : clear-pending
uint32_t ICPENDR1; // 0x0284 : clear-pending
uint32_t ICPENDR2; // 0x0288 : clear-pending
RSVD( 4, 0x028C, 0x02FC ); // 0x028C...0x02FC : reserved
uint32_t ISACTIVER0; // 0x0300 : set-active
uint32_t ISACTIVER1; // 0x0304 : set-active
uint32_t ISACTIVER2; // 0x0308 : set-active
RSVD( 5, 0x030C, 0x03FC ); // 0x030C...0x03FC : reserved
uint32_t IPRIORITYR[ 24 ]; // 0x0400...0x045C : priority
RSVD( 6, 0x0460, 0x07FC ); // 0x0460...0x07FC : reserved
uint32_t ITARGETSR[ 24 ]; // 0x0800...0x085C : processor target
RSVD( 7, 0x0860, 0x0BFC ); // 0x0760...0x0BFC : reserved
uint32_t ICFGR0; // 0x0C00 : configuration
uint32_t ICFGR1; // 0x0C04 : configuration
uint32_t ICFGR2; // 0x0C08 : configuration
uint32_t ICFGR3; // 0x0C0C : configuration
uint32_t ICFGR4; // 0x0C10 : configuration
uint32_t ICFGR5; // 0x0C14 : configuration
RSVD( 8, 0x0C18, 0x0EFC ); // 0x0C18...0x0EFC : reserved
uint32_t SGIR; // 0x0F00 : software interrupt
RSVD( 9, 0x0F04, 0x0FFC ); // 0x0F04...0x0FFC : reserved
} GICD_t;
#define GIC_SOURCE_TIMER0 ( 36 )
#define GIC_SOURCE_TIMER1 ( 37 )
#define GIC_SOURCE_TIMER2 ( 73 )
#define GIC_SOURCE_TIMER3 ( 74 )
#define GIC_SOURCE_UART0 ( 44 )
#define GIC_SOURCE_UART1 ( 45 )
#define GIC_SOURCE_UART2 ( 46 )
#define GIC_SOURCE_UART3 ( 47 )
#define GIC_SOURCE_PS20 ( 52 )
#define GIC_SOURCE_PS21 ( 53 )
/* Per Table 4.2 (for example: the information is in several places) of
*
* http://infocenter.arm.com/help/topic/com.arm.doc.dui0417d/index.html
*
* we know the registers are mapped to fixed addresses in memory, so we
* can just define a (structured) pointer to each one to support access.
*/
extern volatile GICC_t* GICC0;
extern volatile GICD_t* GICD0;
extern volatile GICC_t* GICC1;
extern volatile GICD_t* GICD1;
extern volatile GICC_t* GICC2;
extern volatile GICD_t* GICD2;
extern volatile GICC_t* GICC3;
extern volatile GICD_t* GICD3;
#endif

24
device/MMU.h Normal file
View File

@ -0,0 +1,24 @@
#ifndef __MMU_H
#define __MMU_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
// enable MMU
void mmu_enable();
// disable MMU
void mmu_unable();
// flush TLB
void mmu_flush();
// configure MMU: set page table pointer #0 to x
void mmu_set_ptr0( uint32_t* x );
// configure MMU: set page table pointer #1 to x
void mmu_set_ptr1( uint32_t* x );
// configure MMU: set 2-bit permission field of domain d to x
void mmu_set_dom( int d, uint8_t x );
#endif

63
device/MMU.s Normal file
View File

@ -0,0 +1,63 @@
@ Section B3.17 of
@
@ http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0406c/index.html
@
@ gives a (fairly) concise overview of co-processor 15, which is used
@ to control the MMU. It takes some effort to decipher, but, as an
@ example, Figure B3-29 says that if we use an mcr instruction
@
@ id = p15, opc1 = 0, CRn = c2, CRm = c0, opc2 = 0
@
@ then we are writing into the TTBR0 register, i.e., the first page
@ table pointer register. Clearly doing so requires very low-level
@ attention to detail that could and perhaps should be abstracted
@ via a higher-level API: the following functions do that, albeit
@ for an *extremely* limited sub-set of functionality wrt. the MMU.
.global mmu_enable
.global mmu_unable
.global mmu_flush
.global mmu_set_ptr0
.global mmu_set_ptr1
.global mmu_set_dom
mmu_enable: mrc p15, 0, r0, c1, c0, 0 @ read SCTLR
orr r0, r0, #0x1 @ set SCTLR[ M ] = 1 => MMU enable
mcr p15, 0, r0, c1, c0, 0 @ write SCTLR
mov pc, lr @ return
mmu_unable: mrc p15, 0, r0, c1, c0, 0 @ read SCTLR
bic r0, r0, #0x1 @ set SCTLR[ M ] = 0 => MMU disable
mcr p15, 0, r0, c1, c0, 0 @ write SCTLR
mov pc, lr @ return
mmu_flush: mov r0, #0x0
mcr p15, 0, r0, c8, c7, 0 @ write TLBIALL
mov pc, lr @ return
mmu_set_ptr0: mcr p15, 0, r0, c2, c0, 0 @ write TTBR0
mov pc, lr @ return
mmu_set_ptr1: mcr p15, 0, r0, c2, c0, 1 @ write TTBR1
mov pc, lr @ return
mmu_set_dom: add r0, r0, r0 @ compute i (index from domain)
mov r1, r1, lsl r0 @ compute j (permission from domain)
mov r2, #0x3
mov r2, r2, lsl r0 @ compute m (mask from domain)
mrc p15, 0, r0, c3, c0, 0 @ read DACR
bic r0, r0, r2 @ mask DACR &= ~m => DACR_{i+1,i} = 0
orr r0, r0, r1 @ set DACR_{i+1,i} = j
mcr p15, 0, r0, c3, c0, 0 @ write DACR
mov pc, lr @ return

67
device/PL011.c Normal file
View File

@ -0,0 +1,67 @@
#include "PL011.h"
volatile PL011_t* UART0 = ( volatile PL011_t* )( 0x10009000 );
volatile PL011_t* UART1 = ( volatile PL011_t* )( 0x1000A000 );
volatile PL011_t* UART2 = ( volatile PL011_t* )( 0x1000B000 );
volatile PL011_t* UART3 = ( volatile PL011_t* )( 0x1000C000 );
int xtoi( char x ) {
if ( ( x >= '0' ) && ( x <= '9' ) ) {
return ( 0 + ( x - '0' ) );
}
else if ( ( x >= 'a' ) && ( x <= 'f' ) ) {
return ( 10 + ( x - 'a' ) );
}
else if ( ( x >= 'A' ) && ( x <= 'F' ) ) {
return ( 10 + ( x - 'A' ) );
}
return -1;
}
char itox( int x ) {
if ( ( x >= 0 ) && ( x <= 9 ) ) {
return '0' + ( x - 0 );
}
else if( ( x >= 10 ) && ( x <= 15 ) ) {
return 'A' + ( x - 10 );
}
return -1;
}
bool PL011_can_putc( PL011_t* d ) {
// can putc iff. transmit FIFO is not full
return !( d->FR & 0x20 );
}
bool PL011_can_getc( PL011_t* d ) {
// can getc iff. receive FIFO is not empty
return !( d->FR & 0x10 );
}
void PL011_putc( PL011_t* d, uint8_t x, bool f ) {
// wait while blocking enabled and transmit FIFO is full
while( f && ( d->FR & 0x20 ) );
// transmit x
d->DR = x;
}
uint8_t PL011_getc( PL011_t* d, bool f ) {
// wait while blocking enabled and receive FIFO is empty
while( f && ( d->FR & 0x10 ) );
// recieve r
return d->DR;
}
void PL011_puth( PL011_t* d, uint8_t x, bool f ) {
PL011_putc( d, itox( ( x >> 4 ) & 0xF ), f );
PL011_putc( d, itox( ( x >> 0 ) & 0xF ), f );
}
uint8_t PL011_geth( PL011_t* d, bool f ) {
uint8_t r = ( xtoi( PL011_getc( d, f ) ) << 4 );
r |= ( xtoi( PL011_getc( d, f ) ) << 0 );
return r;
}

89
device/PL011.h Normal file
View File

@ -0,0 +1,89 @@
#ifndef __PL011_H
#define __PL011_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#define RSVD(x,y,z) uint8_t reserved##x[ z - y + 1 ];
/* The ARM PrimeCell UART (PL011) is documented at
*
* http://infocenter.arm.com/help/topic/com.arm.doc.ddi0183g/index.html
*
* In particular, Section 3 explains the programmer's model, i.e., how to
* interact with it: this includes
*
* - Section 3.2, which summarises the device register layout in Table 3.1
* (including an offset from the device base address, in the memory map,
* for each register), and
* - Section 3.3, which summarises the internal structure of each device
* register.
*
* Note that the field identifiers used here follow the documentation in a
* general sense, but with a some minor alterations to improve clarity and
* consistency.
*/
typedef volatile struct {
uint32_t DR; // 0x0000 : data
union { uint32_t RSR; // 0x0004 : receive status
uint32_t ECR; }; // | error clear
RSVD( 0, 0x0008, 0x0017 ); // 0x0008...0x0017 : reserved
uint32_t FR; // 0x0018 : flag
RSVD( 1, 0x001C, 0x001F ); // 0x001C...0x001F : reserved
uint32_t LPR; // 0x0020 : low-power counter
uint32_t IBRD; // 0x0024 : integer baud rate
uint32_t FBRD; // 0x0028 : fractional baud rate
uint32_t LCR; // 0x002C : line control
uint32_t CR; // 0x0030 : control
uint32_t IFLS; // 0x0034 : interrupt level select
uint32_t IMSC; // 0x0038 : interrupt mask
uint32_t RIS; // 0x003C : raw interrupt status
uint32_t MIS; // 0x0040 : masked interrupt status
uint32_t ICR; // 0x0044 : interrupt clear
uint32_t DMACR; // 0x0048 : DMA control
RSVD( 2, 0x004C, 0x0FDF ); // 0x004C...0x0FDF : reserved
uint32_t PeriphID0; // 0x0FE0 : peripheral ID
uint32_t PeriphID1; // 0x0FE4 : peripheral ID
uint32_t PeriphID2; // 0x0FE8 : peripheral ID
uint32_t PeriphID3; // 0x0FEC : peripheral ID
uint32_t PCellID0; // 0x0FF0 : PrimeCell ID
uint32_t PCellID1; // 0x0FF4 : PrimeCell ID
uint32_t PCellID2; // 0x0FF8 : PrimeCell ID
uint32_t PCellID3; // 0x0FFC : PrimeCell ID
} PL011_t;
/* Per Table 4.2 (for example: the information is in several places) of
*
* http://infocenter.arm.com/help/topic/com.arm.doc.dui0417d/index.html
*
* we know the registers are mapped to fixed addresses in memory, so we
* can just define a (structured) pointer to each one to support access.
*/
extern volatile PL011_t* UART0;
extern volatile PL011_t* UART1;
extern volatile PL011_t* UART2;
extern volatile PL011_t* UART3;
// convert a hexadecimal character x into an integer 0 < r < 16
extern int xtoi( char x );
// convert an integer 0 < x < 16 into a hexadecimal character r
extern char itox( int x );
// check whether transmitting via PL011 instance d will block
extern bool PL011_can_putc( PL011_t* d );
// check whether receiving via PL011 instance d will block
extern bool PL011_can_getc( PL011_t* d );
// transmit raw byte x via PL011 instance d (blocking iff. f = true)
extern void PL011_putc( PL011_t* d, uint8_t x, bool f );
// receive raw byte r via PL011 instance d (blocking iff. f = true)
extern uint8_t PL011_getc( PL011_t* d, bool f );
// transmit hexified byte x via PL011 instance d (blocking iff. f = true)
extern void PL011_puth( PL011_t* d, uint8_t x, bool f );
// receive hexified byte r via PL011 instance d (blocking iff. f = true)
extern uint8_t PL011_geth( PL011_t* d, bool f );
#endif

18
device/PL050.c Normal file
View File

@ -0,0 +1,18 @@
#include "PL050.h"
volatile PL050_t* PS20 = ( volatile PL050_t* )( 0x10006000 );
volatile PL050_t* PS21 = ( volatile PL050_t* )( 0x10007000 );
void PL050_putc( PL050_t* d, uint8_t x ) {
// wait while transmit register isn't empty
while( !( d->STAT & 0x40 ) );
// transmit x
d->DATA = x;
}
uint8_t PL050_getc( PL050_t* d ) {
// wait while receive register isn't full
while( !( d->STAT & 0x10 ) );
// recieve r
return d->DATA;
}

55
device/PL050.h Normal file
View File

@ -0,0 +1,55 @@
#ifndef __PL050_H
#define __PL050_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#define RSVD(x,y,z) uint8_t reserved##x[ z - y + 1 ];
/* The ARM PrimeCell PS2 Keyboard/Mouse Interface (PL050) is documented at
*
* http://infocenter.arm.com/help/topic/com.arm.doc.ddi0143c/index.html
*
* In particular, Section 3 explains the programmer's model, i.e., how to
* interact with it: this includes
*
* - Section 3.2, which summarises the device register layout in Table 3.1
* (including an offset from the device base address, in the memory map,
* for each register), and
* - Section 3.3, which summarises the internal structure of each device
* register.
*
* Note that the field identifiers used here follow the documentation in a
* general sense, but with a some minor alterations to improve clarity and
* consistency.
*/
typedef volatile struct {
uint32_t CR; // 0x0000 : control
uint32_t STAT; // 0x0004 : status
uint32_t DATA; // 0x0008 : data
uint32_t CLKDIV; // 0x000C : clock divisor
uint32_t IR; // 0x0010 : interrupt status
RSVD( 0, 0x0008, 0x0017 ); // 0x0014...0x003C : reserved
RSVD( 1, 0x001C, 0x001F ); // 0x0040...0x009C : reserved
RSVD( 2, 0x004C, 0x0FDF ); // 0x00A0...0x00FF : reserved
} PL050_t;
/* Per Table 4.2 (for example: the information is in several places) of
*
* http://infocenter.arm.com/help/topic/com.arm.doc.dui0417d/index.html
*
* we know the registers are mapped to fixed addresses in memory, so we
* can just define a (structured) pointer to each one to support access.
*/
extern volatile PL050_t* PS20; // keyboard
extern volatile PL050_t* PS21; // mouse
// transmit raw byte x via PL050 instance d
extern void PL050_putc( PL050_t* d, uint8_t x );
// recieve raw byte r via PL050 instance d
extern uint8_t PL050_getc( PL050_t* d );
#endif

3
device/PL111.c Normal file
View File

@ -0,0 +1,3 @@
#include "PL111.h"
volatile PL111_t* LCD = ( volatile PL111_t* )( 0x10020000 );

80
device/PL111.h Normal file
View File

@ -0,0 +1,80 @@
#ifndef __PL111_H
#define __PL111_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#define RSVD(x,y,z) uint8_t reserved##x[ z - y + 1 ];
/* The ARM PrimeCell ColorLCD Controller (PL111) is documented at
*
* http://infocenter.arm.com/help/topic/com.arm.doc.ddi0293c/index.html
*
* In particular, Section 3 explains the programmer's model, i.e., how to
* interact with it: this includes
*
* - Section 3.2, which summarises the device register layout in Table 3.1
* (including an offset from the device base address, in the memory map,
* for each register), and
* - Section 3.3, which summarises the internal structure of each device
* register.
*
* Note that the field identifiers used here follow the documentation in a
* general sense, but with a some minor alterations to improve clarity and
* consistency.
*/
typedef volatile struct {
uint32_t LCDTiming0; // 0x0000 : horizontal axis
uint32_t LCDTiming1; // 0x0004 : vertical axis
uint32_t LCDTiming2; // 0x0008 : clock and signal polarity
uint32_t LCDTiming3; // 0x000C : line end
uint32_t LCDUPBASE; // 0x0010 : upper panel base address
uint32_t LCDLPBASE; // 0x0014 : lower panel base address
uint32_t LCDControl; // 0x0018 : control
uint32_t LCDIMSC; // 0x001C : interrupt mask
uint32_t LCDRIS; // 0x0020 : raw interrupt status
uint32_t LCDMIS; // 0x0024 : masked interrupt status
uint32_t LCDICR; // 0x0028 : interrupt clear
uint32_t LCDUPCURR; // 0x002C : upper panel current address
uint32_t LCDLPCURR; // 0x0030 : lower panel current address
RSVD( 0, 0x0034, 0x01FC ); // 0x0034...0x01FC : reserved
uint16_t LCDPalette[ 256 ]; // 0x0200...0x03FC : color palette
RSVD( 1, 0x0400, 0x07FC ); // 0x0400...0x07FC : reserved
uint32_t ClcdCrsrImage[ 256 ]; // 0x0800...0x0BFC : cursor image
uint32_t ClcdCrsrCtrl; // 0x0C00 : cursor control
uint32_t ClcdCrsrConfig; // 0x0C04 : cursor configuration
uint32_t ClcdCrsrPalette0; // 0x0C08...0x0C0C : cursor palette
uint32_t ClcdCrsrPalette1; // 0x0C08...0x0C0C : cursor palette
uint32_t ClcdCrsrXY; // 0x0C10 : cursor position
uint32_t ClcdCrsrClip; // 0x0C14 : cursor clip position
RSVD( 2, 0x0C18, 0x0C1C ); // 0x0C18...0x0C1C : reserved
uint32_t ClcdCrsrIMSC; // 0x0C20 : cursor interrupt mask
uint32_t ClcdCrsrICR; // 0x0C24 : cursor interrupt clear
uint32_t ClcdCrsrRIS; // 0x0C28 : cursor raw interrupt status
uint32_t ClcdCrsrMIS; // 0x0C2C : cursor masked interrupt status
RSVD( 3, 0x0C30, 0x0DFC ); // 0x0C30...0x0DFC : reserved
RSVD( 4, 0x0F00, 0x0F08 ); // 0x0F00...0x0F08 : reserved
RSVD( 5, 0x0F0C, 0x0FDC ); // 0x0F0C...0x0FDC : reserved
uint32_t PeriphID0; // 0x0FE0 : peripheral ID
uint32_t PeriphID1; // 0x0FE4 : peripheral ID
uint32_t PeriphID2; // 0x0FE8 : peripheral ID
uint32_t PeriphID3; // 0x0FEC : peripheral ID
uint32_t PCellID0; // 0x0FF0 : PrimeCell ID
uint32_t PCellID1; // 0x0FF4 : PrimeCell ID
uint32_t PCellID2; // 0x0FF8 : PrimeCell ID
uint32_t PCellID3; // 0x0FFC : PrimeCell ID
} PL111_t;
/* Per Table 4.2 (for example: the information is in several places) of
*
* http://infocenter.arm.com/help/topic/com.arm.doc.dui0417d/index.html
*
* we know the registers are mapped to fixed addresses in memory, so we
* can just define a (structured) pointer to each one to support access.
*/
extern volatile PL111_t* LCD;
#endif

6
device/SP804.c Normal file
View File

@ -0,0 +1,6 @@
#include "SP804.h"
volatile SP804_t* TIMER0 = ( volatile SP804_t* )( 0x10011000 );
volatile SP804_t* TIMER1 = ( volatile SP804_t* )( 0x10012000 );
volatile SP804_t* TIMER2 = ( volatile SP804_t* )( 0x10018000 );
volatile SP804_t* TIMER3 = ( volatile SP804_t* )( 0x10019000 );

71
device/SP804.h Normal file
View File

@ -0,0 +1,71 @@
#ifndef __SP804_H
#define __SP804_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#define RSVD(x,y,z) uint8_t reserved##x[ z - y + 1 ];
/* The ARM Dual-Timer Module (SP804) is documented at
*
* http://infocenter.arm.com/help/topic/com.arm.doc.ddi0271d/index.html
*
* In particular, Section 3 explains the programmer's model, i.e., how to
* interact with it: this includes
*
* - Section 3.2, which summarises the device register layout in Table 3.1
* (including an offset from the device base address, in the memory map,
* for each register), and
* - Section 3.3, which summarises the internal structure of each device
* register.
*
* Note that the field identifiers used here follow the documentation in a
* general sense, but with a some minor alterations to improve clarity and
* consistency.
*/
typedef volatile struct {
uint32_t Timer1Load; // 0x0000 : load
uint32_t Timer1Value; // 0x0004 : current value
uint32_t Timer1Ctrl; // 0x0008 : control
uint32_t Timer1IntClr; // 0x000C : interrupt clear
uint32_t Timer1RIS; // 0x0010 : raw interrupt status
uint32_t Timer1MIS; // 0x0014 : masked interrupt status
uint32_t Timer1BGLoad; // 0x0018 : background load
RSVD( 0, 0x001C, 0x001F ); // 0x001C...0x001F : reserved
uint32_t Timer2Load; // 0x0020 : load
uint32_t Timer2Value; // 0x0024 : current value
uint32_t Timer2Ctrl; // 0x0028 : control
uint32_t Timer2IntClr; // 0x002C : interrupt clear
uint32_t Timer2RIS; // 0x0030 : raw interrupt status
uint32_t Timer2MIS; // 0x0034 : masked interrupt status
uint32_t Timer2BGLoad; // 0x0038 : background load
RSVD( 1, 0x003C, 0x0EFF ); // 0x003C...0x0EFF : reserved
uint32_t TimerITCR; // 0x0F00 : integration test
uint32_t TimerITOP; // 0x0F04 : integration test
RSVD( 2, 0x0F08, 0x0FDF ); // 0x0F08...0x0FDF : reserved
uint32_t PeriphID0; // 0x0FE0 : peripheral ID
uint32_t PeriphID1; // 0x0FE4 : peripheral ID
uint32_t PeriphID2; // 0x0FE8 : peripheral ID
uint32_t PeriphID3; // 0x0FEC : peripheral ID
uint32_t PCellID0; // 0x0FF0 : PrimeCell ID
uint32_t PCellID1; // 0x0FF4 : PrimeCell ID
uint32_t PCellID2; // 0x0FF8 : PrimeCell ID
uint32_t PCellID3; // 0x0FFC : PrimeCell ID
} SP804_t;
/* Per Table 4.2 (for example: the information is in several places) of
*
* http://infocenter.arm.com/help/topic/com.arm.doc.dui0417d/index.html
*
* we know the registers are mapped to fixed addresses in memory, so we
* can just define a (structured) pointer to each one to support access.
*/
extern volatile SP804_t* TIMER0; // timer module 0 -> timers #0 and #1
extern volatile SP804_t* TIMER1; // timer module 1 -> timers #2 and #3
extern volatile SP804_t* TIMER2; // timer module 2 -> timers #4 and #5
extern volatile SP804_t* TIMER3; // timer module 3 -> timers #6 and #7
#endif

6
device/SYS.c Normal file
View File

@ -0,0 +1,6 @@
#include "SYS.h"
volatile SYSCONF_t* SYSCONF = ( volatile SYSCONF_t* )( 0x10000000 );
volatile uint32_t* SYSCTRL0 = ( volatile uint32_t* )( 0x10001000 );
volatile uint32_t* SYSCTRL1 = ( volatile uint32_t* )( 0x1001A000 );

103
device/SYS.h Normal file
View File

@ -0,0 +1,103 @@
#ifndef __SYS_H
#define __SYS_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#define RSVD(x,y,z) uint8_t reserved##x[ z - y + 1 ];
/* As outlined in Section 4.3 and 4.4, especially Table 4.5, of
*
* http://infocenter.arm.com/help/topic/com.arm.doc.dui0417d/index.html
*
* the platform houses various configuration and control registers: these
* allow a) control over and b) inspection of the state maintained by the
* resources and devices it houses; some are rendered moot when placed in
* an emulated context of course.
*/
typedef volatile struct {
uint32_t ID; // 0x0000 : system identifier
uint32_t USERSW; // 0x0004 : user switch
uint32_t LED; // 0x0008 : user LED
uint32_t OSC0; // 0x000C : oscillator configuration
uint32_t OSC1; // 0x0010 : oscillator configuration
uint32_t OSC2; // 0x0014 : oscillator configuration
uint32_t OSC3; // 0x0018 : oscillator configuration
uint32_t OSC4; // 0x001C : oscillator configuration
uint32_t LOCK; // 0x0020 : lock control
uint32_t COUNTER_100HZ; // 0x0024 : 100Hz counter
RSVD( 0, 0x0028, 0x002C ); // 0x0028...0x002C : reserved
union { uint32_t FLAGS; // 0x0030 : general-purpose flags
uint32_t FLAGSSET; }; // 0x0030 | general-purpose flags set
uint32_t FLAGSCLR; // 0x0034 : general-purpose flags clear
union { uint32_t NVFLAGS; // 0x0038 : general-purpose non-volatile flags
uint32_t NVFLAGSSET; }; // 0x0038 | general-purpose non-volatile flags set
uint32_t NVFLAGSCLR; // 0x003C : general-purpose non-volatile flags clear
uint32_t RESETCTL; // 0x0040 : software reset level
RSVD( 1, 0x0044, 0x0047 ); // 0x0044...0x0047 : reserved
uint32_t MCI; // 0x0048 : MCI status
uint32_t FLASH; // 0x004C : flash write protection
uint32_t CLCD; // 0x0050 : colour LCD power and multiplexing
RSVD( 2, 0x0054, 0x0057 ); // 0x0054...0x0057 : reserved
uint32_t CFGSW; // 0x0058 : user switch configuration
uint32_t COUNTER_24MHZ; // 0x005C : 24MHz counter
uint32_t MISC; // 0x0060 : miscellaneous
uint32_t DMAPSR; // 0x0064 : DMA mapping
uint32_t PEX_STAT; // 0x0068 : PCI Express status
uint32_t PCI_STAT; // 0x006C : PCI status
RSVD( 3, 0x0070, 0x0073 ); // 0x0070...0x0073 : reserved
uint32_t PLD_CTRL1; // 0x0074 : PLD configuration
uint32_t PLD_CTRL2; // 0x0078 : PLD configuration
uint32_t PLL_INIT; // 0x007C : PLL configuration
RSVD( 4, 0x0080, 0x0083 ); // 0x0080...0x0083 : reserved
uint32_t PROCID0; // 0x0084 : processor ID
uint32_t PROCID1; // 0x0088 : processor ID
uint32_t OSCRESET0; // 0x008C : oscillator reset value
uint32_t OSCRESET1; // 0x0090 : oscillator reset value
uint32_t OSCRESET2; // 0x0094 : oscillator reset value
uint32_t OSCRESET3; // 0x0098 : oscillator reset value
uint32_t OSCRESET4; // 0x009A : oscillator reset value
uint32_t VOLTAGE_CTL0; // 0x00A0 : voltate control/monitoring
uint32_t VOLTAGE_CTL1; // 0x00A4 : voltate control/monitoring
uint32_t VOLTAGE_CTL2; // 0x00A8 : voltate control/monitoring
uint32_t VOLTAGE_CTL3; // 0x00AA : voltate control/monitoring
uint32_t VOLTAGE_CTL4; // 0x00AC : voltate control/monitoring
uint32_t VOLTAGE_CTL5; // 0x00B0 : voltate control/monitoring
uint32_t VOLTAGE_CTL6; // 0x00B4 : voltate control/monitoring
uint32_t VOLTAGE_CTL7; // 0x00B8 : voltate control/monitoring
uint32_t VOLTAGE_CTL8; // 0x00BC : voltate control/monitoring
uint32_t TEST_OSC0; // 0x00C0 : oscillator driven counter
uint32_t TEST_OSC1; // 0x00C4 : oscillator driven counter
uint32_t TEST_OSC2; // 0x00C8 : oscillator driven counter
uint32_t TEST_OSC3; // 0x00CC : oscillator driven counter
uint32_t TEST_OSC4; // 0x00D0 : oscillator driven counter
uint32_t OSC5; // 0x00D4 : oscillator configuration
uint32_t OSC6; // 0x00D8 : oscillator configuration
uint32_t OSCRESET5; // 0x00DC : oscillator reset value
uint32_t OSCRESET6; // 0x00E0 : oscillator reset value
uint32_t TEST_OSC5; // 0x00E4 : oscillator driven counter
uint32_t TEST_OSC6; // 0x00E8 : oscillator driven counter
uint32_t OSC7; // 0x00EC : oscillator configuration
uint32_t OSCRESET7; // 0x00F0 : oscillator reset value
uint32_t TEST_OSC7; // 0x00F4 : oscillator driven counter
uint32_t DEBUG; // 0x00F8 : debug unit configuration
uint32_t TESTMODE; // 0x00FC : test mode configuration
uint32_t PLL_RESET; // 0x0100 : PLL reset value
} SYSCONF_t;
/* Per Table 4.2 (for example: the information is in several places) of
*
* http://infocenter.arm.com/help/topic/com.arm.doc.dui0417d/index.html
*
* we know the registers are mapped to fixed addresses in memory, so we
* can just define a (structured) pointer to each one to support access.
*/
extern volatile SYSCONF_t* SYSCONF;
extern volatile uint32_t* SYSCTRL0;
extern volatile uint32_t* SYSCTRL1;
#endif

114
device/disk.c Normal file
View File

@ -0,0 +1,114 @@
#include "disk.h"
void addr_puth( PL011_t* d, uint32_t x, bool f ) {
PL011_puth( d, ( x >> 0 ) & 0xFF, f );
PL011_puth( d, ( x >> 8 ) & 0xFF, f );
PL011_puth( d, ( x >> 16 ) & 0xFF, f );
PL011_puth( d, ( x >> 24 ) & 0xFF, f );
}
void data_puth( PL011_t* d, const uint8_t* x, int n, bool f ) {
for( int i = 0; i < n; i++ ) {
PL011_puth( d, x[ i ], f );
}
}
void data_geth( PL011_t* d, uint8_t* x, int n, bool f ) {
for( int i = 0; i < n; i++ ) {
x[ i ] = PL011_geth( d, f );
}
}
int disk_get_block_num() {
int n = 2 * sizeof( uint32_t ); uint8_t x[ n ];
for( int i = 0; i < DISK_RETRY; i++ ) {
PL011_puth( UART2, 0x00, true ); // write command
PL011_putc( UART2, '\n', true ); // write EOL
if( PL011_geth( UART2, true ) == 0x00 ) { // read command
PL011_getc( UART2, true ); // read separator
data_geth( UART2, x, n, true ); // read data
PL011_getc( UART2, true ); // read EOL
return ( ( uint32_t )( x[ 0 ] ) << 0 ) |
( ( uint32_t )( x[ 1 ] ) << 8 ) |
( ( uint32_t )( x[ 2 ] ) << 16 ) |
( ( uint32_t )( x[ 3 ] ) << 24 ) ;
}
else {
PL011_getc( UART2, true ); // read EOL
}
}
return DISK_FAILURE;
}
int disk_get_block_len() {
int n = 2 * sizeof( uint32_t ); uint8_t x[ n ];
for( int i = 0; i < DISK_RETRY; i++ ) {
PL011_puth( UART2, 0x00, true ); // write command
PL011_putc( UART2, '\n', true ); // write EOL
if( PL011_geth( UART2, true ) == 0x00 ) { // read command
PL011_getc( UART2, true ); // read separator
data_geth( UART2, x, n, true ); // read data
PL011_getc( UART2, true ); // read EOL
return ( ( uint32_t )( x[ 4 ] ) << 0 ) |
( ( uint32_t )( x[ 5 ] ) << 8 ) |
( ( uint32_t )( x[ 6 ] ) << 16 ) |
( ( uint32_t )( x[ 7 ] ) << 24 ) ;
}
else {
PL011_getc( UART2, true ); // read EOL
}
}
return DISK_FAILURE;
}
int disk_wr( uint32_t a, const uint8_t* x, int n ) {
for( int i = 0; i < DISK_RETRY; i++ ) {
PL011_puth( UART2, 0x01, true ); // write command
PL011_putc( UART2, ' ', true ); // write separator
addr_puth( UART2, a, true ); // write address
PL011_putc( UART2, ' ', true ); // write separator
data_puth( UART2, x, n, true ); // write data
PL011_putc( UART2, '\n', true ); // write EOL
if( PL011_geth( UART2, true ) == 0x00 ) { // read command
PL011_getc( UART2, true ); // read EOL
return DISK_SUCCESS;
}
else {
PL011_getc( UART2, true ); // read EOL
}
}
return DISK_FAILURE;
}
int disk_rd( uint32_t a, uint8_t* x, int n ) {
for( int i = 0; i < DISK_RETRY; i++ ) {
PL011_puth( UART2, 0x02, true ); // write command
PL011_putc( UART2, ' ', true ); // write separator
addr_puth( UART2, a, true ); // write address
PL011_putc( UART2, '\n', true ); // write EOL
if( PL011_geth( UART2, true ) == 0x00 ) { // read command
PL011_getc( UART2, true ); // read separator
data_geth( UART2, x, n, true ); // read data
PL011_getc( UART2, true ); // read EOL
return DISK_SUCCESS;
}
else {
PL011_getc( UART2, true ); // read EOL
}
}
return DISK_FAILURE;
}

36
device/disk.h Normal file
View File

@ -0,0 +1,36 @@
#ifndef __DISK_H
#define __DISK_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "PL011.h"
/* Each of the following functions adopts the same approach to
* reporting success vs. failure, as indicated by the response
* produced by the disk: they return an r st.
*
* r < 0 means failure
* r >= 0 means success
*
* Rather than give up immediately if a given request fails, it
* will (automatically) retry for some fixed number of times.
*/
#define DISK_RETRY ( 3 )
#define DISK_SUCCESS ( 0 )
#define DISK_FAILURE ( -1 )
// query the disk block count
extern int disk_get_block_num();
// query the disk block length
extern int disk_get_block_len();
// write an n-byte block of data x to the disk at block address a
extern int disk_wr( uint32_t a, const uint8_t* x, int n );
// read an n-byte block of data x from the disk at block address a
extern int disk_rd( uint32_t a, uint8_t* x, int n );
#endif

138
device/disk.py Normal file
View File

@ -0,0 +1,138 @@
import argparse, binascii, logging, os, socket, struct, sys
REQ_CONF = '00'
REQ_WR = '01'
REQ_RD = '02'
ACK_OKAY = '00'
ACK_FAIL = '01'
# 00 command means a query operation: we pack the block size
# and count into a single datum, then return it.
def conf( fd, req ) :
data = struct.pack( '<l', args.block_num )
data += struct.pack( '<l', args.block_len )
return [ ACK_OKAY, data ]
# 01 command means a write operation:
# - if the address provided is invalid the request fails,
# - if the data provided is invalid the request fails,
# - else write the block to the disk, then flush the data.
def wr( fd, req ) :
address = struct.unpack( '<l', binascii.unhexlify( req[ 1 ] ) )[ 0 ]
data = binascii.unhexlify( req[ 2 ] )
if( address >= args.block_num ) :
return [ ACK_FAIL ]
if( len( data ) != args.block_len ) :
return [ ACK_FAIL ]
os.lseek( fd, address * args.block_len, os.SEEK_SET )
n = os.write( fd, data )
if( len( data ) != n ) :
return [ ACK_FAIL ]
os.fsync( fd )
logging.info( 'wr %d bytes -> address %X_{(16)} = %d_{(10)}' % ( len( data ), address, address ) )
logging.debug( 'wr data = %s' % ( ''.join( [ '%02X' % ( ord( x ) ) for x in data ] ) ) )
return [ ACK_OKAY ]
# 02 command means a read operation:
# - if the address provided is invalid the request fails,
# - else read the block from the disk, then return the data.
def rd( fd, req ) :
address = struct.unpack( '<l', binascii.unhexlify( req[ 1 ] ) )[ 0 ]
if( address >= args.block_num ) :
return [ ACK_FAIL ]
os.lseek( fd, address * args.block_len, os.SEEK_SET )
data = os.read( fd, args.block_len )
if( len( data ) != args.block_len ) :
return [ ACK_FAIL ]
os.fsync( fd )
logging.info( 'rd %d bytes <- address %X_{(16)} = %d_{(10)}' % ( len( data ), address, address ) )
logging.debug( 'rd data = %s' % ( ''.join( [ '%02X' % ( ord( x ) ) for x in data ] ) ) )
return [ ACK_OKAY, data ]
# The command line interface basically just parses the arguments
# which configure the disk etc. then enters an infinite loop: it
# reads requests and writes acknowledgements one at a time until
# terminated.
if ( __name__ == '__main__' ) :
# parse command line arguments
parser = argparse.ArgumentParser()
parser.add_argument( '--host', type = str, action = 'store' )
parser.add_argument( '--port', type = int, action = 'store' )
parser.add_argument( '--file', type = str, action = 'store' )
parser.add_argument( '--block-num', type = int, action = 'store' )
parser.add_argument( '--block-len', type = int, action = 'store' )
parser.add_argument( '--debug', action = 'store_true' )
args = parser.parse_args()
if ( args.debug ) :
l = logging.DEBUG
else :
l = logging.INFO
logging.basicConfig( stream = sys.stdout, level = l, format = '%(filename)s : %(asctime)s : %(message)s', datefmt = '%d/%m/%y @ %H:%M:%S' )
# open disk image
fd = os.open( args.file, os.O_RDWR )
# open network connection
s = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
s.connect( ( args.host, args.port ) ) ; sd = s.makefile()
# read request, process it and write acknowledgement
while ( True ) :
req = sd.readline().strip().split( ' ' )
logging.debug( 'req = ' + str( req ) )
if ( req[ 0 ] == REQ_CONF ) :
ack = conf( fd, req )
elif ( req[ 0 ] == REQ_WR ) :
ack = wr( fd, req )
elif ( req[ 0 ] == REQ_RD ) :
ack = rd( fd, req )
else :
ack = [ ACK_FAIL ]
logging.debug( 'ack = ' + str( ack ) )
if ( len( ack ) > 1 ) :
ack = ack[ 0 ] + ' ' + ' '.join( [ binascii.hexlify( x ) for x in ack[ 1 : ] ] )
else :
ack = ack[ 0 ]
sd.write( ack + '\n' ) ; sd.flush()
# close network connection
sd.close()
# close disk image
os.close( fd )

18
image.ld Normal file
View File

@ -0,0 +1,18 @@
SECTIONS {
/* assign load address (per QEMU) */
. = 0x70010000;
/* place text segment(s) */
.text : { kernel/lolevel.o(.text) *(.text .rodata) }
/* place data segment(s) */
.data : { *(.data ) }
/* place bss segment(s) */
.bss : { *(.bss ) }
/* align address (per AAPCS) */
. = ALIGN( 8 );
/* allocate stack for irq mode */
. = . + 0x00001000;
tos_irq = .;
/* allocate stack for svc mode */
. = . + 0x00001000;
tos_svc = .;
}

13
kernel/hilevel.c Normal file
View File

@ -0,0 +1,13 @@
#include "hilevel.h"
void hilevel_handler_rst() {
return;
}
void hilevel_handler_irq() {
return;
}
void hilevel_handler_svc() {
return;
}

15
kernel/hilevel.h Normal file
View File

@ -0,0 +1,15 @@
#ifndef __HILEVEL_H
#define __HILEVEL_H
// Include functionality relating to newlib (the standard C library).
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
// Include functionality relating to the kernel.
#include "lolevel.h"
#include "int.h"
#endif

20
kernel/int.h Normal file
View File

@ -0,0 +1,20 @@
#ifndef __INT_H
#define __INT_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
// initialise interrupt vector table
extern void int_init();
// enable IRQ interrupts
extern void int_enable_irq();
// disable IRQ interrupts
extern void int_unable_irq();
// enable FIQ interrupts
extern void int_enable_fiq();
// disable FIQ interrupts
extern void int_unable_fiq();
#endif

69
kernel/int.s Normal file
View File

@ -0,0 +1,69 @@
/* The following captures the interrupt vector table, plus a function
* to copy it into place (which is called on reset): note that
*
* - for interrupts we don't handle an infinite loop is realised (to
* to approximate halting the processor), and
* - we copy the table itself, *plus* the associated addresses stored
* as static data: this preserves the relative offset between each
* ldr instruction and wherever it loads from.
*/
int_data: ldr pc, int_addr_rst @ reset vector -> SVC mode
b . @ undefined instruction vector -> UND mode
ldr pc, int_addr_svc @ supervisor call vector -> SVC mode
b . @ pre-fetch abort vector -> ABT mode
b . @ data abort vector -> ABT mode
b . @ reserved
ldr pc, int_addr_irq @ IRQ vector -> IRQ mode
b . @ FIQ vector -> FIQ mode
int_addr_rst: .word lolevel_handler_rst
int_addr_svc: .word lolevel_handler_svc
int_addr_irq: .word lolevel_handler_irq
.global int_init
int_init: mov r0, #0 @ set destination address
ldr r1, =int_data @ set source address = start of data
ldr r2, =int_init @ set source limit = start of function
l0: ldr r3, [ r1 ], #4 @ load word, inc. source address
str r3, [ r0 ], #4 @ store word, inc. destination address
cmp r1, r2
bne l0 @ loop if address != limit
mov pc, lr @ return
/* These function enable and disable IRQ and FIQ interrupts, toggling
* either the 6-th or 7-th bit of CPSR to 0 or 1 respectively.
*/
.global int_enable_irq
.global int_unable_irq
.global int_enable_fiq
.global int_unable_fiq
int_enable_irq: mrs r0, cpsr @ get USR mode CPSR
bic r0, r0, #0x80 @ enable IRQ interrupts
msr cpsr_c, r0 @ set USR mode CPSR
mov pc, lr @ return
int_unable_irq: mrs r0, cpsr @ get USR mode CPSR
orr r0, r0, #0x80 @ disable IRQ interrupts
msr cpsr_c, r0 @ set USR mode CPSR
mov pc, lr @ return
int_enable_fiq: mrs r0, cpsr @ get USR mode CPSR
bic r0, r0, #0x40 @ enable FIQ interrupts
msr cpsr_c, r0 @ set USR mode CPSR
mov pc, lr @ return
int_unable_fiq: mrs r0, cpsr @ get USR mode CPSR
orr r0, r0, #0x40 @ disable FIQ interrupts
msr cpsr_c, r0 @ set USR mode CPSR
mov pc, lr @ return

4
kernel/lolevel.h Normal file
View File

@ -0,0 +1,4 @@
#ifndef __LOLEVEL_H
#define __LOLEVEL_H
#endif

34
kernel/lolevel.s Normal file
View File

@ -0,0 +1,34 @@
/* Each of the following is a low-level interrupt handler: each one is
* tasked with handling a different interrupt type, and acts as a sort
* of wrapper around a high-level, C-based handler.
*/
.global lolevel_handler_rst
.global lolevel_handler_irq
.global lolevel_handler_svc
lolevel_handler_rst: bl int_init @ initialise interrupt vector table
msr cpsr, #0xD2 @ enter IRQ mode with IRQ and FIQ interrupts disabled
ldr sp, =tos_irq @ initialise IRQ mode stack
msr cpsr, #0xD3 @ enter SVC mode with IRQ and FIQ interrupts disabled
ldr sp, =tos_svc @ initialise SVC mode stack
bl hilevel_handler_rst @ invoke high-level C function
b . @ halt
lolevel_handler_irq: sub lr, lr, #4 @ correct return address
stmfd sp!, { r0-r3, ip, lr } @ save caller-save registers
bl hilevel_handler_irq @ invoke high-level C function
ldmfd sp!, { r0-r3, ip, lr } @ restore caller-save registers
movs pc, lr @ return from interrupt
lolevel_handler_svc: sub lr, lr, #0 @ correct return address
stmfd sp!, { r0-r3, ip, lr } @ save caller-save registers
bl hilevel_handler_svc @ invoke high-level C function
ldmfd sp!, { r0-r3, ip, lr } @ restore caller-save registers
movs pc, lr @ return from interrupt

BIN
question.pdf Normal file

Binary file not shown.

30
user/P3.c Normal file
View File

@ -0,0 +1,30 @@
#include "P3.h"
int is_prime( uint32_t x ) {
if ( !( x & 1 ) || ( x < 2 ) ) {
return ( x == 2 );
}
for( uint32_t d = 3; ( d * d ) <= x ; d += 2 ) {
if( !( x % d ) ) {
return 0;
}
}
return 1;
}
void main_P3() {
for( int i = 0; i < 50; i++ ) {
write( STDOUT_FILENO, "P3", 2 );
uint32_t lo = 1 << 8;
uint32_t hi = 1 << 16;
for( uint32_t x = lo; x < hi; x++ ) {
int r = is_prime( x );
}
}
exit( EXIT_SUCCESS );
}

10
user/P3.h Normal file
View File

@ -0,0 +1,10 @@
#ifndef __P3_H
#define __P3_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "libc.h"
#endif

30
user/P4.c Normal file
View File

@ -0,0 +1,30 @@
#include "P4.h"
uint32_t gcd( uint32_t x, uint32_t y ) {
if ( x == y ) {
return x;
}
else if( x > y ) {
return gcd( x - y, y );
}
else if( x < y ) {
return gcd( x, y - x );
}
}
void main_P4() {
while( 1 ) {
write( STDOUT_FILENO, "P4", 2 );
uint32_t lo = 1 << 4;
uint32_t hi = 1 << 8;
for( uint32_t x = lo; x < hi; x++ ) {
for( uint32_t y = lo; y < hi; y++ ) {
uint32_t r = gcd( x, y );
}
}
}
exit( EXIT_SUCCESS );
}

10
user/P4.h Normal file
View File

@ -0,0 +1,10 @@
#ifndef __P4_H
#define __P4_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "libc.h"
#endif

26
user/P5.c Normal file
View File

@ -0,0 +1,26 @@
#include "P5.h"
uint32_t weight( uint32_t x ) {
x = ( x & 0x55555555 ) + ( ( x >> 1 ) & 0x55555555 );
x = ( x & 0x33333333 ) + ( ( x >> 2 ) & 0x33333333 );
x = ( x & 0x0F0F0F0F ) + ( ( x >> 4 ) & 0x0F0F0F0F );
x = ( x & 0x00FF00FF ) + ( ( x >> 8 ) & 0x00FF00FF );
x = ( x & 0x0000FFFF ) + ( ( x >> 16 ) & 0x0000FFFF );
return x;
}
void main_P5() {
while( 1 ) {
write( STDOUT_FILENO, "P5", 2 );
uint32_t lo = 1 << 8;
uint32_t hi = 1 << 24;
for( uint32_t x = lo; x < hi; x++ ) {
uint32_t r = weight( x );
}
}
exit( EXIT_SUCCESS );
}

10
user/P5.h Normal file
View File

@ -0,0 +1,10 @@
#ifndef __P5_H
#define __P5_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "libc.h"
#endif

83
user/console.c Normal file
View File

@ -0,0 +1,83 @@
#include "console.h"
/* The following functions are special-case versions of a) writing,
* and b) reading a string from the UART (the latter case returning
* once a carriage return character has been read, or an overall
* limit reached).
*/
void puts( char* x, int n ) {
for( int i = 0; i < n; i++ ) {
PL011_putc( UART1, x[ i ], true );
}
}
void gets( char* x, int n ) {
for( int i = 0; i < n; i++ ) {
x[ i ] = PL011_getc( UART1, true );
if( x[ i ] == '\x0A' ) {
x[ i ] = '\x00'; break;
}
}
}
/* Since we lack a *real* loader (as a result of lacking a storage
* medium to store program images), the following approximates one:
* given a program name, from the set of programs statically linked
* into the kernel image, it returns a pointer to the entry point.
*/
extern void main_P3();
extern void main_P4();
extern void main_P5();
void* load( char* x ) {
if ( 0 == strcmp( x, "P3" ) ) {
return &main_P3;
}
else if( 0 == strcmp( x, "P4" ) ) {
return &main_P4;
}
else if( 0 == strcmp( x, "P5" ) ) {
return &main_P5;
}
return NULL;
}
/* The behaviour of the console process can be summarised as an
* (infinite) loop over three main steps, namely
*
* 1. write a command prompt then read a command,
* 2. split the command into space-separated tokens using strtok,
* 3. execute whatever steps the command dictates.
*/
void main_console() {
char* p, x[ 1024 ];
while( 1 ) {
puts( "shell$ ", 7 ); gets( x, 1024 ); p = strtok( x, " " );
if ( 0 == strcmp( p, "fork" ) ) {
pid_t pid = fork();
if( 0 == pid ) {
void* addr = load( strtok( NULL, " " ) );
exec( addr );
}
}
else if( 0 == strcmp( p, "kill" ) ) {
pid_t pid = atoi( strtok( NULL, " " ) );
int s = atoi( strtok( NULL, " " ) );
kill( pid, s );
}
else {
puts( "unknown command\n", 16 );
}
}
exit( EXIT_SUCCESS );
}

14
user/console.h Normal file
View File

@ -0,0 +1,14 @@
#ifndef __CONSOLE_H
#define __CONSOLE_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include "PL011.h"
#include "libc.h"
#endif

131
user/libc.c Normal file
View File

@ -0,0 +1,131 @@
#include "libc.h"
int atoi( char* x ) {
char* p = x; bool s = false; int r = 0;
if ( *p == '-' ) {
s = true; p++;
}
else if( *p == '+' ) {
s = false; p++;
}
for( int i = 0; *p != '\x00'; i++, p++ ) {
r = s ? ( r * 10 ) - ( *p - '0' ) :
( r * 10 ) + ( *p - '0' ) ;
}
return r;
}
void itoa( char* r, int x ) {
char* p = r; int t, n;
if( x < 0 ) {
p++; t = -x; n = 1;
}
else {
t = +x; n = 1;
}
while( t >= n ) {
p++; n *= 10;
}
*p-- = '\x00';
do {
*p-- = '0' + ( t % 10 ); t /= 10;
} while( t );
if( x < 0 ) {
*p-- = '-';
}
return;
}
void yield() {
asm volatile( "svc %0 \n" // make system call SYS_YIELD
:
: "I" (SYS_YIELD)
: );
return;
}
int write( int fd, const void* x, size_t n ) {
int r;
asm volatile( "mov r0, %2 \n" // assign r0 = fd
"mov r1, %3 \n" // assign r1 = x
"mov r2, %4 \n" // assign r2 = n
"svc %1 \n" // make system call SYS_WRITE
"mov %0, r0 \n" // assign r = r0
: "=r" (r)
: "I" (SYS_WRITE), "r" (fd), "r" (x), "r" (n)
: "r0", "r1", "r2" );
return r;
}
int read( int fd, void* x, size_t n ) {
int r;
asm volatile( "mov r0, %2 \n" // assign r0 = fd
"mov r1, %3 \n" // assign r1 = x
"mov r2, %4 \n" // assign r2 = n
"svc %1 \n" // make system call SYS_READ
"mov %0, r0 \n" // assign r = r0
: "=r" (r)
: "I" (SYS_READ), "r" (fd), "r" (x), "r" (n)
: "r0", "r1", "r2" );
return r;
}
int fork() {
int r;
asm volatile( "svc %1 \n" // make system call SYS_FORK
"mov %0, r0 \n" // assign r = r0
: "=r" (r)
: "I" (SYS_FORK)
: "r0" );
return r;
}
void exit( int x ) {
asm volatile( "mov r0, %1 \n" // assign r0 = x
"svc %0 \n" // make system call SYS_EXIT
:
: "I" (SYS_EXIT), "r" (x)
: "r0" );
return;
}
void exec( const void* x ) {
asm volatile( "mov r0, %1 \n" // assign r0 = x
"svc %0 \n" // make system call SYS_EXEC
:
: "I" (SYS_EXEC), "r" (x)
: "r0" );
return;
}
int kill( int pid, int x ) {
int r;
asm volatile( "mov r0, %2 \n" // assign r0 = pid
"mov r1, %3 \n" // assign r1 = x
"svc %1 \n" // make system call SYS_KILL
"mov %0, r0 \n" // assign r0 = r
: "=r" (r)
: "I" (SYS_KILL), "r" (pid), "r" (x)
: "r0", "r1" );
return r;
}

67
user/libc.h Normal file
View File

@ -0,0 +1,67 @@
#ifndef __LIBC_H
#define __LIBC_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
// Define a type that that captures a Process IDentifier (PID).
typedef int pid_t;
/* The definitions below capture symbolic constants within these classes:
*
* 1. system call identifiers (i.e., the constant used by a system call
* to specify which action the kernel should take),
* 2. signal identifiers (as used by the kill system call),
* 3. status codes for exit,
* 4. standard file descriptors (e.g., for read and write system calls),
* 5. platform-specific constants, which may need calibration (wrt. the
* underlying hardware QEMU is executed on).
*
* They don't *precisely* match the standard C library, but are intended
* to act as a limited model of similar concepts.
*/
#define SYS_YIELD ( 0x00 )
#define SYS_WRITE ( 0x01 )
#define SYS_READ ( 0x02 )
#define SYS_FORK ( 0x03 )
#define SYS_EXIT ( 0x04 )
#define SYS_EXEC ( 0x05 )
#define SYS_KILL ( 0x06 )
#define SIG_TERM ( 0x00 )
#define SIG_QUIT ( 0x01 )
#define EXIT_SUCCESS ( 0 )
#define EXIT_FAILURE ( 1 )
#define STDIN_FILENO ( 0 )
#define STDOUT_FILENO ( 1 )
#define STDERR_FILENO ( 2 )
// convert ASCII string x into integer r
extern int atoi( char* x );
// convert integer x into ASCII string r
extern void itoa( char* r, int x );
// cooperatively yield control of processor, i.e., invoke the scheduler
extern void yield();
// write n bytes from x to the file descriptor fd; return bytes written
extern int write( int fd, const void* x, size_t n );
// read n bytes into x from the file descriptor fd; return bytes read
extern int read( int fd, void* x, size_t n );
// perform fork, returning 0 iff. child or > 0 iff. parent process
extern int fork();
// perform exit, i.e., terminate process with status x
extern void exit( int x );
// perform exec, i.e., start executing program at address x
extern void exec( const void* x );
// signal process identified by pid with signal x
extern int kill( pid_t pid, int x );
#endif