commit bb84f09914ecd459d96ed688f39329853fe91bbc Author: rubenwardy Date: Mon Mar 13 16:22:39 2017 +0000 Initial Commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..82f4c25 --- /dev/null +++ b/.gitignore @@ -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 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..7c1c1f4 --- /dev/null +++ b/Makefile @@ -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 diff --git a/Makefile.console b/Makefile.console new file mode 100644 index 0000000..0564c08 --- /dev/null +++ b/Makefile.console @@ -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} diff --git a/Makefile.disk b/Makefile.disk new file mode 100644 index 0000000..2fbfe73 --- /dev/null +++ b/Makefile.disk @@ -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} diff --git a/device/GIC.c b/device/GIC.c new file mode 100644 index 0000000..2ea5aab --- /dev/null +++ b/device/GIC.c @@ -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 ); diff --git a/device/GIC.h b/device/GIC.h new file mode 100644 index 0000000..169afe9 --- /dev/null +++ b/device/GIC.h @@ -0,0 +1,114 @@ +#ifndef __GIC_H +#define __GIC_H + +#include +#include +#include + +#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 diff --git a/device/MMU.h b/device/MMU.h new file mode 100644 index 0000000..b39d85c --- /dev/null +++ b/device/MMU.h @@ -0,0 +1,24 @@ +#ifndef __MMU_H +#define __MMU_H + +#include +#include +#include + +// 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 diff --git a/device/MMU.s b/device/MMU.s new file mode 100644 index 0000000..c67e10f --- /dev/null +++ b/device/MMU.s @@ -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 + diff --git a/device/PL011.c b/device/PL011.c new file mode 100644 index 0000000..e9744c8 --- /dev/null +++ b/device/PL011.c @@ -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; +} diff --git a/device/PL011.h b/device/PL011.h new file mode 100644 index 0000000..b0dd6f9 --- /dev/null +++ b/device/PL011.h @@ -0,0 +1,89 @@ +#ifndef __PL011_H +#define __PL011_H + +#include +#include +#include + +#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 diff --git a/device/PL050.c b/device/PL050.c new file mode 100644 index 0000000..a03333f --- /dev/null +++ b/device/PL050.c @@ -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; +} diff --git a/device/PL050.h b/device/PL050.h new file mode 100644 index 0000000..5304224 --- /dev/null +++ b/device/PL050.h @@ -0,0 +1,55 @@ +#ifndef __PL050_H +#define __PL050_H + +#include +#include +#include + +#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 diff --git a/device/PL111.c b/device/PL111.c new file mode 100644 index 0000000..8aeb64d --- /dev/null +++ b/device/PL111.c @@ -0,0 +1,3 @@ +#include "PL111.h" + +volatile PL111_t* LCD = ( volatile PL111_t* )( 0x10020000 ); diff --git a/device/PL111.h b/device/PL111.h new file mode 100644 index 0000000..4523c8b --- /dev/null +++ b/device/PL111.h @@ -0,0 +1,80 @@ +#ifndef __PL111_H +#define __PL111_H + +#include +#include +#include + +#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 diff --git a/device/SP804.c b/device/SP804.c new file mode 100644 index 0000000..aafb3fb --- /dev/null +++ b/device/SP804.c @@ -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 ); diff --git a/device/SP804.h b/device/SP804.h new file mode 100644 index 0000000..c1d438e --- /dev/null +++ b/device/SP804.h @@ -0,0 +1,71 @@ +#ifndef __SP804_H +#define __SP804_H + +#include +#include +#include + +#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 diff --git a/device/SYS.c b/device/SYS.c new file mode 100644 index 0000000..79af14e --- /dev/null +++ b/device/SYS.c @@ -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 ); diff --git a/device/SYS.h b/device/SYS.h new file mode 100644 index 0000000..ae997d9 --- /dev/null +++ b/device/SYS.h @@ -0,0 +1,103 @@ +#ifndef __SYS_H +#define __SYS_H + +#include +#include +#include + +#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 diff --git a/device/disk.c b/device/disk.c new file mode 100644 index 0000000..f064230 --- /dev/null +++ b/device/disk.c @@ -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; +} diff --git a/device/disk.h b/device/disk.h new file mode 100644 index 0000000..0f0bb46 --- /dev/null +++ b/device/disk.h @@ -0,0 +1,36 @@ +#ifndef __DISK_H +#define __DISK_H + +#include +#include +#include + +#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 diff --git a/device/disk.py b/device/disk.py new file mode 100644 index 0000000..5451a8b --- /dev/null +++ b/device/disk.py @@ -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( '= 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( '= 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 ) diff --git a/image.ld b/image.ld new file mode 100644 index 0000000..081483b --- /dev/null +++ b/image.ld @@ -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 = .; +} diff --git a/kernel/hilevel.c b/kernel/hilevel.c new file mode 100644 index 0000000..224ee21 --- /dev/null +++ b/kernel/hilevel.c @@ -0,0 +1,13 @@ +#include "hilevel.h" + +void hilevel_handler_rst() { + return; +} + +void hilevel_handler_irq() { + return; +} + +void hilevel_handler_svc() { + return; +} diff --git a/kernel/hilevel.h b/kernel/hilevel.h new file mode 100644 index 0000000..7408be9 --- /dev/null +++ b/kernel/hilevel.h @@ -0,0 +1,15 @@ +#ifndef __HILEVEL_H +#define __HILEVEL_H + +// Include functionality relating to newlib (the standard C library). + +#include +#include +#include + +// Include functionality relating to the kernel. + +#include "lolevel.h" +#include "int.h" + +#endif diff --git a/kernel/int.h b/kernel/int.h new file mode 100644 index 0000000..b9a2fa5 --- /dev/null +++ b/kernel/int.h @@ -0,0 +1,20 @@ +#ifndef __INT_H +#define __INT_H + +#include +#include +#include + +// 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 diff --git a/kernel/int.s b/kernel/int.s new file mode 100644 index 0000000..081c0db --- /dev/null +++ b/kernel/int.s @@ -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 diff --git a/kernel/lolevel.h b/kernel/lolevel.h new file mode 100644 index 0000000..ac31e92 --- /dev/null +++ b/kernel/lolevel.h @@ -0,0 +1,4 @@ +#ifndef __LOLEVEL_H +#define __LOLEVEL_H + +#endif diff --git a/kernel/lolevel.s b/kernel/lolevel.s new file mode 100644 index 0000000..51beda3 --- /dev/null +++ b/kernel/lolevel.s @@ -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 diff --git a/question.pdf b/question.pdf new file mode 100644 index 0000000..46b14ea Binary files /dev/null and b/question.pdf differ diff --git a/user/P3.c b/user/P3.c new file mode 100644 index 0000000..3c29c79 --- /dev/null +++ b/user/P3.c @@ -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 ); +} diff --git a/user/P3.h b/user/P3.h new file mode 100644 index 0000000..966dec4 --- /dev/null +++ b/user/P3.h @@ -0,0 +1,10 @@ +#ifndef __P3_H +#define __P3_H + +#include +#include +#include + +#include "libc.h" + +#endif diff --git a/user/P4.c b/user/P4.c new file mode 100644 index 0000000..312e558 --- /dev/null +++ b/user/P4.c @@ -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 ); +} diff --git a/user/P4.h b/user/P4.h new file mode 100644 index 0000000..8ec205b --- /dev/null +++ b/user/P4.h @@ -0,0 +1,10 @@ +#ifndef __P4_H +#define __P4_H + +#include +#include +#include + +#include "libc.h" + +#endif diff --git a/user/P5.c b/user/P5.c new file mode 100644 index 0000000..53523c5 --- /dev/null +++ b/user/P5.c @@ -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 ); +} diff --git a/user/P5.h b/user/P5.h new file mode 100644 index 0000000..e6f31c2 --- /dev/null +++ b/user/P5.h @@ -0,0 +1,10 @@ +#ifndef __P5_H +#define __P5_H + +#include +#include +#include + +#include "libc.h" + +#endif diff --git a/user/console.c b/user/console.c new file mode 100644 index 0000000..a59ebb4 --- /dev/null +++ b/user/console.c @@ -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 ); +} diff --git a/user/console.h b/user/console.h new file mode 100644 index 0000000..39867fd --- /dev/null +++ b/user/console.h @@ -0,0 +1,14 @@ +#ifndef __CONSOLE_H +#define __CONSOLE_H + +#include +#include +#include + +#include + +#include "PL011.h" + +#include "libc.h" + +#endif diff --git a/user/libc.c b/user/libc.c new file mode 100644 index 0000000..f376f15 --- /dev/null +++ b/user/libc.c @@ -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; +} diff --git a/user/libc.h b/user/libc.h new file mode 100644 index 0000000..02af5dc --- /dev/null +++ b/user/libc.h @@ -0,0 +1,67 @@ +#ifndef __LIBC_H +#define __LIBC_H + +#include +#include +#include + +// 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