Initial Commit
commit
bb84f09914
|
@ -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
|
|
@ -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
|
|
@ -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}
|
|
@ -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}
|
|
@ -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 );
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -0,0 +1,3 @@
|
|||
#include "PL111.h"
|
||||
|
||||
volatile PL111_t* LCD = ( volatile PL111_t* )( 0x10020000 );
|
|
@ -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
|
|
@ -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 );
|
|
@ -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
|
|
@ -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 );
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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 )
|
|
@ -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 = .;
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
#include "hilevel.h"
|
||||
|
||||
void hilevel_handler_rst() {
|
||||
return;
|
||||
}
|
||||
|
||||
void hilevel_handler_irq() {
|
||||
return;
|
||||
}
|
||||
|
||||
void hilevel_handler_svc() {
|
||||
return;
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1,4 @@
|
|||
#ifndef __LOLEVEL_H
|
||||
#define __LOLEVEL_H
|
||||
|
||||
#endif
|
|
@ -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
|
Binary file not shown.
|
@ -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 );
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
#ifndef __P3_H
|
||||
#define __P3_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "libc.h"
|
||||
|
||||
#endif
|
|
@ -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 );
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
#ifndef __P4_H
|
||||
#define __P4_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "libc.h"
|
||||
|
||||
#endif
|
|
@ -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 );
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
#ifndef __P5_H
|
||||
#define __P5_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "libc.h"
|
||||
|
||||
#endif
|
|
@ -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 );
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
Loading…
Reference in New Issue