diff --git a/rpi3/00_simple/.gitignore b/rpi3/00_simple/.gitignore new file mode 100644 index 0000000..8bb9984 --- /dev/null +++ b/rpi3/00_simple/.gitignore @@ -0,0 +1,3 @@ +kernel8.* +.gdbinit +*.o diff --git a/rpi3/00_simple/Makefile b/rpi3/00_simple/Makefile new file mode 100644 index 0000000..0dae26e --- /dev/null +++ b/rpi3/00_simple/Makefile @@ -0,0 +1,15 @@ +# +# Simplest bootloader example (prints hello world on serial port, UART0, then echos what you type at it) +# + +all: clean kernel8.elf + +# note the target is rpi3_bl in this directory +kernel8.elf: simple.go rpi3_bl.o + tinygo build --target=rpi3_bl -target 'rpi3_bl.json' -ldflags 'rpi3_bl.o' -o kernel8.elf . + +rpi3_bl.o: rpi3_bl.S + clang --target=aarch64-elf -c rpi3_bl.S + +clean: + rm kernel8.elf kernel8.img *.o >/dev/null 2>/dev/null || true diff --git a/rpi3/00_simple/go.mod b/rpi3/00_simple/go.mod new file mode 100644 index 0000000..ddbc791 --- /dev/null +++ b/rpi3/00_simple/go.mod @@ -0,0 +1,5 @@ +module interrupts + +go 1.11 + +require github.com/tinygo-org/tinygo v0.7.1 // indirect diff --git a/rpi3/00_simple/go.sum b/rpi3/00_simple/go.sum new file mode 100644 index 0000000..c181705 --- /dev/null +++ b/rpi3/00_simple/go.sum @@ -0,0 +1,16 @@ +github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2 h1:oMCHnXa6CCCafdPDbMh/lWRhRByN0VFLvv+g+ayx1SI= +github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/marcinbor85/gohex v0.0.0-20180128172054-7a43cd876e46 h1:wXG2bA8fO7Vv7lLk2PihFMTqmbT173Tje39oKzQ50Mo= +github.com/marcinbor85/gohex v0.0.0-20180128172054-7a43cd876e46/go.mod h1:Pb6XcsXyropB9LNHhnqaknG/vEwYztLkQzVCHv8sQ3M= +github.com/tinygo-org/tinygo v0.7.1 h1:DbKW4LglJLr9L3zykwFkUR/974mVwZEtnSeOzUsslRQ= +github.com/tinygo-org/tinygo v0.7.1/go.mod h1:lw4bWjN/+Nm/9ypTo6BgAlYNLS9GrpP7Rw5g0bWAwew= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20190227180812-8dcc6e70cdef h1:ymc9FeDom3RIEA3coKokSllBB1hRcMT0tZ1W3Jf9Ids= +golang.org/x/tools v0.0.0-20190227180812-8dcc6e70cdef/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +tinygo.org/x/go-llvm v0.0.0-20190224120431-7707ae5d1261 h1:rJS2Hga39YAnm7DE4qrPm6Dr/67EOojL0XPzvbEeBiw= +tinygo.org/x/go-llvm v0.0.0-20190224120431-7707ae5d1261/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE= diff --git a/rpi3/00_simple/rpi3_bl.S b/rpi3/00_simple/rpi3_bl.S new file mode 100644 index 0000000..7d0ad49 --- /dev/null +++ b/rpi3/00_simple/rpi3_bl.S @@ -0,0 +1,69 @@ +.section ".text.boot_bl" + +// +// The bootloader changes execution state for bootloaded code to EL2 if that is +// needed. This is after execution switches to the bootloaded code, and it drops +// to EL1 once the stack is setup ok. +// + +// startup parameters are: +// x0 - start address (also in PC) +// x1 - stack ptr +// x2 - heap ptr +// x3 - unix time +.global _start + //set the sp for el1, but we have to have the fp space so subtract 16 (two 64bit ptr) + sub x1,x1,#16 + msr sp_el1, x1 + + + // enable CNTP for EL1 + mrs x27, cnthctl_el2 + orr x27, x27, #3 + msr cnthctl_el2, x27 + msr cntvoff_el2, xzr + + // enable AArch64 in EL1 + mov x27, #(1 << 31) // AArch64 + orr x27, x27, #(1 << 1) // SWIO hardwired on Pi3 + msr hcr_el2, x27 + mrs x27, hcr_el2 + + // system control register + mov x4, #0x0800 + movk x4, #0x30d0, lsl #16 + msr sctlr_el1, x4 + + //insert default vectors for core 0 + ldr x27, =vectors + msr vbar_el1, x27 + + // change execution level to EL1 + mov x27, #0x3c5 //5! not 4! we have our own stack + msr spsr_el2, x27 + adr x27, 1f + msr elr_el2, x27 + eret + +## getting the stack and fp in order, we subtracted 16 from x1 earlier in bootloader +## note that we store 0 for the link and ret values because we are at the +## bottom of the stack and want stack to correctly show in GDB and similar +1: + # fp to bottom of frame + add x27, x1, #16 + mov x29, x27 + + #shove values in based on top of stack + str xzr, [sp, #8] + str xzr, [sp, #16] + + //sp points to top of stack (for Bootloaded code 0x30000 - 0x20) + //fp points to bottom (x29) (for Bootloaded code 0x30000 - 0x10) + + //tell hardware that EL0 has own stack + //msr SPSel, #1 + + // jump to go code + bl mainFromBootloader + // for failsafe, halt this core too + wfe diff --git a/rpi3/00_simple/rpi3_bl.json b/rpi3/00_simple/rpi3_bl.json new file mode 100644 index 0000000..c1d5daf --- /dev/null +++ b/rpi3/00_simple/rpi3_bl.json @@ -0,0 +1,4 @@ +{ + "inherits": ["rpi3"], + "linkerscript": "rpi3_bl.ld" +} diff --git a/rpi3/00_simple/rpi3_bl.ld b/rpi3/00_simple/rpi3_bl.ld new file mode 100644 index 0000000..b0a15c0 --- /dev/null +++ b/rpi3/00_simple/rpi3_bl.ld @@ -0,0 +1,19 @@ +SECTIONS +{ +. = 0x10000; +.text : { KEEP(*(.text.boot_bl)) *(.text .text.* .gnu.linkonce.t*) } +.rodata : { *(.rodata .rodata.* .gnu.linkonce.r*) } +PROVIDE(_data = .); +.data : { *(.data .data.* .gnu.linkonce.d*) } +.bss (NOLOAD) : { + . = ALIGN(16); + __bss_start = .; + *(.bss .bss.*) + *(COMMON) + __bss_end = .; +} +_end = .; + +/DISCARD/ : { *(.comment) *(.gnu*) *(.note*) *(.eh_frame*) *(.text.boot)} +} +__bss_size = (__bss_end - __bss_start)>>3; diff --git a/rpi3/00_simple/simple.go b/rpi3/00_simple/simple.go new file mode 100644 index 0000000..718c845 --- /dev/null +++ b/rpi3/00_simple/simple.go @@ -0,0 +1,13 @@ +package main + +import ( + dev "device/rpi3" +) + +func main() { + print("hello world!\nechoing what you type\n") + for { + c := dev.UART0Getc() + dev.UART0Send(c) + } +} diff --git a/rpi3/01_blinker/.gitignore b/rpi3/01_blinker/.gitignore new file mode 100644 index 0000000..8be6272 --- /dev/null +++ b/rpi3/01_blinker/.gitignore @@ -0,0 +1,2 @@ +kernel8.* +*.o diff --git a/rpi3/01_blinker/Makefile b/rpi3/01_blinker/Makefile new file mode 100644 index 0000000..73656c4 --- /dev/null +++ b/rpi3/01_blinker/Makefile @@ -0,0 +1,15 @@ +# +# Use interrupts to blink the light on the RPI3. +# + +all: clean kernel8.elf + +# note the target is rpi3_bl in this directory +kernel8.elf: clean rpi3_bl.o + tinygo build --target=rpi3_bl.json -target 'rpi3_bl.json' -ldflags 'rpi3_bl.o' -o kernel8.elf . + +rpi3_bl.o: rpi3_bl.S + clang --target=aarch64-elf -c rpi3_bl.S + +clean: + rm kernel8.elf kernel8.img *.o >/dev/null 2>/dev/null || true diff --git a/rpi3/01_blinker/blinker.go b/rpi3/01_blinker/blinker.go new file mode 100644 index 0000000..b03e6b9 --- /dev/null +++ b/rpi3/01_blinker/blinker.go @@ -0,0 +1,38 @@ +package main + +import ( + dev "device/rpi3" +) + +var on = false +var interval uint32 + +// it's more than a little unclear that the values passed to this +// interrupt handler are ever going to be useful +func blinker(_ uint32, _ uint64) uint32 { + dev.LEDSet(on) + on = !on + print("set LED to ", on, "\n") + return interval +} + +func main() { + hi, lo := dev.GetRPIID() + if hi == 0 && lo == 0 { + print("you are running on QEMU so you aren't going to see any lights blinking...\n") + } + + freq := dev.CounterFreq() //getting my freq on + print("frequency per second is:") + dev.UART0Hex(freq) //this number is number of ticks/sec + + //sets the timer for N secs, where N is multiplier + interval = 1 * freq + dev.SetCounterTargetInterval(interval, blinker) + dev.Core0CounterToCore0Irq() + dev.EnableCounter() + dev.EnableTimerIRQ() + for { + dev.WaitForInterrupt() + } +} diff --git a/rpi3/01_blinker/rpi3_bl.S b/rpi3/01_blinker/rpi3_bl.S new file mode 100644 index 0000000..7d0ad49 --- /dev/null +++ b/rpi3/01_blinker/rpi3_bl.S @@ -0,0 +1,69 @@ +.section ".text.boot_bl" + +// +// The bootloader changes execution state for bootloaded code to EL2 if that is +// needed. This is after execution switches to the bootloaded code, and it drops +// to EL1 once the stack is setup ok. +// + +// startup parameters are: +// x0 - start address (also in PC) +// x1 - stack ptr +// x2 - heap ptr +// x3 - unix time +.global _start + //set the sp for el1, but we have to have the fp space so subtract 16 (two 64bit ptr) + sub x1,x1,#16 + msr sp_el1, x1 + + + // enable CNTP for EL1 + mrs x27, cnthctl_el2 + orr x27, x27, #3 + msr cnthctl_el2, x27 + msr cntvoff_el2, xzr + + // enable AArch64 in EL1 + mov x27, #(1 << 31) // AArch64 + orr x27, x27, #(1 << 1) // SWIO hardwired on Pi3 + msr hcr_el2, x27 + mrs x27, hcr_el2 + + // system control register + mov x4, #0x0800 + movk x4, #0x30d0, lsl #16 + msr sctlr_el1, x4 + + //insert default vectors for core 0 + ldr x27, =vectors + msr vbar_el1, x27 + + // change execution level to EL1 + mov x27, #0x3c5 //5! not 4! we have our own stack + msr spsr_el2, x27 + adr x27, 1f + msr elr_el2, x27 + eret + +## getting the stack and fp in order, we subtracted 16 from x1 earlier in bootloader +## note that we store 0 for the link and ret values because we are at the +## bottom of the stack and want stack to correctly show in GDB and similar +1: + # fp to bottom of frame + add x27, x1, #16 + mov x29, x27 + + #shove values in based on top of stack + str xzr, [sp, #8] + str xzr, [sp, #16] + + //sp points to top of stack (for Bootloaded code 0x30000 - 0x20) + //fp points to bottom (x29) (for Bootloaded code 0x30000 - 0x10) + + //tell hardware that EL0 has own stack + //msr SPSel, #1 + + // jump to go code + bl mainFromBootloader + // for failsafe, halt this core too + wfe diff --git a/rpi3/01_blinker/rpi3_bl.json b/rpi3/01_blinker/rpi3_bl.json new file mode 100644 index 0000000..c1d5daf --- /dev/null +++ b/rpi3/01_blinker/rpi3_bl.json @@ -0,0 +1,4 @@ +{ + "inherits": ["rpi3"], + "linkerscript": "rpi3_bl.ld" +} diff --git a/rpi3/01_blinker/rpi3_bl.ld b/rpi3/01_blinker/rpi3_bl.ld new file mode 100644 index 0000000..b0a15c0 --- /dev/null +++ b/rpi3/01_blinker/rpi3_bl.ld @@ -0,0 +1,19 @@ +SECTIONS +{ +. = 0x10000; +.text : { KEEP(*(.text.boot_bl)) *(.text .text.* .gnu.linkonce.t*) } +.rodata : { *(.rodata .rodata.* .gnu.linkonce.r*) } +PROVIDE(_data = .); +.data : { *(.data .data.* .gnu.linkonce.d*) } +.bss (NOLOAD) : { + . = ALIGN(16); + __bss_start = .; + *(.bss .bss.*) + *(COMMON) + __bss_end = .; +} +_end = .; + +/DISCARD/ : { *(.comment) *(.gnu*) *(.note*) *(.eh_frame*) *(.text.boot)} +} +__bss_size = (__bss_end - __bss_start)>>3; diff --git a/rpi3/02_delays/.gitignore b/rpi3/02_delays/.gitignore new file mode 100644 index 0000000..8be6272 --- /dev/null +++ b/rpi3/02_delays/.gitignore @@ -0,0 +1,2 @@ +kernel8.* +*.o diff --git a/rpi3/02_delays/Makefile b/rpi3/02_delays/Makefile new file mode 100644 index 0000000..606c15a --- /dev/null +++ b/rpi3/02_delays/Makefile @@ -0,0 +1,15 @@ +# +# Use the bootloader to test our delays. +# + +all: clean kernel8.elf + +# note the target is rpi3_bl in this directory +kernel8.elf: rpi3_bl.o + tinygo build -target 'rpi3_bl.json' -ldflags 'rpi3_bl.o' -no-debug -o kernel8.elf . + +rpi3_bl.o: rpi3_bl.S + clang --target=aarch64-elf -c rpi3_bl.S + +clean: + rm kernel8.elf kernel8.img *.o >/dev/null 2>/dev/null || true diff --git a/rpi3/02_delays/delays.go b/rpi3/02_delays/delays.go new file mode 100644 index 0000000..1798c4f --- /dev/null +++ b/rpi3/02_delays/delays.go @@ -0,0 +1,51 @@ +package main + +import ( + dev "device/rpi3" +) + +var interval uint32 + +// not sure how useful the current value is, but the virtual timer should +// have increased by exactly interval (or very close to it) +// return the amount of time to wait for the next call to this callback +// return 0 if you don't want it anymore +func myHandler(current uint32, virtualTimer uint64) uint32 { + print("my handler, current: ") + dev.UART0Hex(current) + print("my handler, virtual timer: ") + dev.UART0Hex64(virtualTimer) + return interval +} + +func main() { + dev.UART0TimeDateString(dev.Now()) + print("\n") + testDelays() + // dev.BComTimerInit(interval) + // dev.EnableInterruptController() + // dev.EnableTimerIRQ() + // + // for { + // dev.WaitForInterrupt() + // } + dev.Abort() +} + +func testDelays() { + print("Waiting 3000000 CPU cycles (ARM CPU): ") + dev.WaitCycles(3000000) + print("OK\n") + + print("Waiting 3000000 microsec (ARM CPU): ") + dev.WaitMuSec(3000000) + print("OK\n") + + print("Waiting 3000000 microsec (BCM System Timer): ") + if dev.SysTimer() == 0 { + print("Not available\n") + } else { + dev.WaitMuSecST(3000000) + print("OK\n") + } +} diff --git a/rpi3/02_delays/rpi3_bl.S b/rpi3/02_delays/rpi3_bl.S new file mode 100644 index 0000000..7d0ad49 --- /dev/null +++ b/rpi3/02_delays/rpi3_bl.S @@ -0,0 +1,69 @@ +.section ".text.boot_bl" + +// +// The bootloader changes execution state for bootloaded code to EL2 if that is +// needed. This is after execution switches to the bootloaded code, and it drops +// to EL1 once the stack is setup ok. +// + +// startup parameters are: +// x0 - start address (also in PC) +// x1 - stack ptr +// x2 - heap ptr +// x3 - unix time +.global _start + //set the sp for el1, but we have to have the fp space so subtract 16 (two 64bit ptr) + sub x1,x1,#16 + msr sp_el1, x1 + + + // enable CNTP for EL1 + mrs x27, cnthctl_el2 + orr x27, x27, #3 + msr cnthctl_el2, x27 + msr cntvoff_el2, xzr + + // enable AArch64 in EL1 + mov x27, #(1 << 31) // AArch64 + orr x27, x27, #(1 << 1) // SWIO hardwired on Pi3 + msr hcr_el2, x27 + mrs x27, hcr_el2 + + // system control register + mov x4, #0x0800 + movk x4, #0x30d0, lsl #16 + msr sctlr_el1, x4 + + //insert default vectors for core 0 + ldr x27, =vectors + msr vbar_el1, x27 + + // change execution level to EL1 + mov x27, #0x3c5 //5! not 4! we have our own stack + msr spsr_el2, x27 + adr x27, 1f + msr elr_el2, x27 + eret + +## getting the stack and fp in order, we subtracted 16 from x1 earlier in bootloader +## note that we store 0 for the link and ret values because we are at the +## bottom of the stack and want stack to correctly show in GDB and similar +1: + # fp to bottom of frame + add x27, x1, #16 + mov x29, x27 + + #shove values in based on top of stack + str xzr, [sp, #8] + str xzr, [sp, #16] + + //sp points to top of stack (for Bootloaded code 0x30000 - 0x20) + //fp points to bottom (x29) (for Bootloaded code 0x30000 - 0x10) + + //tell hardware that EL0 has own stack + //msr SPSel, #1 + + // jump to go code + bl mainFromBootloader + // for failsafe, halt this core too + wfe diff --git a/rpi3/02_delays/rpi3_bl.json b/rpi3/02_delays/rpi3_bl.json new file mode 100644 index 0000000..c1d5daf --- /dev/null +++ b/rpi3/02_delays/rpi3_bl.json @@ -0,0 +1,4 @@ +{ + "inherits": ["rpi3"], + "linkerscript": "rpi3_bl.ld" +} diff --git a/rpi3/02_delays/rpi3_bl.ld b/rpi3/02_delays/rpi3_bl.ld new file mode 100644 index 0000000..b0a15c0 --- /dev/null +++ b/rpi3/02_delays/rpi3_bl.ld @@ -0,0 +1,19 @@ +SECTIONS +{ +. = 0x10000; +.text : { KEEP(*(.text.boot_bl)) *(.text .text.* .gnu.linkonce.t*) } +.rodata : { *(.rodata .rodata.* .gnu.linkonce.r*) } +PROVIDE(_data = .); +.data : { *(.data .data.* .gnu.linkonce.d*) } +.bss (NOLOAD) : { + . = ALIGN(16); + __bss_start = .; + *(.bss .bss.*) + *(COMMON) + __bss_end = .; +} +_end = .; + +/DISCARD/ : { *(.comment) *(.gnu*) *(.note*) *(.eh_frame*) *(.text.boot)} +} +__bss_size = (__bss_end - __bss_start)>>3; diff --git a/rpi3/03_framebuffer/.gitignore b/rpi3/03_framebuffer/.gitignore new file mode 100644 index 0000000..8be6272 --- /dev/null +++ b/rpi3/03_framebuffer/.gitignore @@ -0,0 +1,2 @@ +kernel8.* +*.o diff --git a/rpi3/03_framebuffer/Makefile b/rpi3/03_framebuffer/Makefile new file mode 100644 index 0000000..c237da9 --- /dev/null +++ b/rpi3/03_framebuffer/Makefile @@ -0,0 +1,18 @@ +# +# Simplest bootloader example. +# + +all: clean kernel8.elf + +# note the target is rpi3_bl +kernel8.elf: clean font.o rpi3_bl.o + tinygo build -target rpi3_bl.json -ldflags 'font.o rpi3_bl.o' -no-debug -o kernel8.elf . + +font.o: font.psf + ld.lld -m aarch64elf -r -b binary -o font.o font.psf + +rpi3_bl.o: rpi3_bl.S + clang --target=aarch64-elf -c rpi3_bl.S + +clean: + rm kernel8.elf kernel8.img *.o >/dev/null 2>/dev/null || true diff --git a/rpi3/03_framebuffer/README.md b/rpi3/03_framebuffer/README.md new file mode 100644 index 0000000..4f94597 --- /dev/null +++ b/rpi3/03_framebuffer/README.md @@ -0,0 +1,33 @@ +This program demonstrates three things: + +* Putting a font into your binary so you can use it with bare metal. +* Rendering the font on screen on the pi +* Scrolling so you have something that works like a console + +The scrolling is GPU assisted. If you don't have the maximum height of your +framebuffer correctly set, this will fail-- probably about 15 lines after +the line marked with '<-------' or you will get an exception. + +You need to add this line to your config.txt file on your SD card (this is really +setting a BIOS option) + +``` +max_framebuffer_height=1536 +``` + +Here is my complete config.txt if you want to duplicate it exactly: + +``` +# Force the monitor to HDMI mode so that sound will be sent over HDMI cable +hdmi_drive=2 +# Set monitor mode to DMT +hdmi_group=2 +# Set monitor resolution to 1024x768 XGA 60Hz (HDMI_DMT_XGA_60) +hdmi_mode=16 +# don't show rainbow screen +disable_splash=1 +# more GPU space, on 1GB machines, use 128MB for GPU +gpu_mem_1024=128 +# set FB height for our fancy scrolling +max_framebuffer_height=1536 +``` diff --git a/rpi3/03_framebuffer/fb.go b/rpi3/03_framebuffer/fb.go new file mode 100644 index 0000000..e9e131f --- /dev/null +++ b/rpi3/03_framebuffer/fb.go @@ -0,0 +1,94 @@ +package main + +import ( + dev "device/rpi3" + "unsafe" +) + +const width = 1024 +const height = 768 + +var twoDigits = []byte{99, 99} + +func main() { + dev.UART0TimeDateString(dev.Now()) + print("\n") + + if !dev.InitFramebuffer() { + print("unable to init framebuffer!") + dev.Abort() + } + + font := dev.NewPSFFontViaLinker(unsafe.Pointer(&_binary_font_psf_start), &dev.FrameBufferInfo) + for { + display48(font) + } + + print("finished ok") +} + +func display48(font *dev.PSFFont) { + font.ConsolePrint("0 Lorem ipsum dolor sit amet, consectetur adipiscing elit.") + font.ConsolePrint("1 Sed vehicula lacinia malesuada.") + font.ConsolePrint("2 Phasellus sagittis nisl nisl, nec placerat lectus rutrum nec.") + font.ConsolePrint("3") + font.ConsolePrint("4") + font.ConsolePrint("5") + font.ConsolePrint("6 Donec sed nibh ut tortor finibus ultricies et non tellus.") + font.ConsolePrint("7 Vivamus eget suscipit nibh.") + font.ConsolePrint("8 Duis egestas, velit non hendrerit eleifend, libero neque bibendum nibh, bibendum cursus odio metus ac sapien.") + font.ConsolePrint("9") + font.ConsolePrint("0") + font.ConsolePrint("1 Nullam enim turpis, egestas vitae mi vel, scelerisque interdum dolor.") + font.ConsolePrint("2 Aenean vestibulum tortor vel congue pulvinar.") + font.ConsolePrint("3 Suspendisse lobortis varius convallis.") + font.ConsolePrint("4") + font.ConsolePrint("5") + font.ConsolePrint("6 Mauris quis consequat dui.") + font.ConsolePrint("7 In sagittis elit at felis cursus, eget aliquam dui aliquam.") + font.ConsolePrint("8 Curabitur augue ante, ullamcorper hendrerit nulla sit amet, sollicitudin euismod ante.") + font.ConsolePrint("9") + font.ConsolePrint("0") + font.ConsolePrint("1") + font.ConsolePrint("2 Nam varius ultricies condimentum.") + font.ConsolePrint("3 Interdum et malesuada fames ac ante ipsum primis in faucibus.") + font.ConsolePrint("4 Vivamus rhoncus laoreet molestie.") + font.ConsolePrint("5") + font.ConsolePrint("6") + font.ConsolePrint("7 Nunc mattis nec elit at varius.") + font.ConsolePrint("8 Aenean faucibus aliquam augue ac gravida.") + font.ConsolePrint("9 Ut non tellus luctus, laoreet nulla eget, congue dolor.") + font.ConsolePrint("0") + font.ConsolePrint("1") + font.ConsolePrint("2 Pellentesque fringilla tincidunt rutrum.") + font.ConsolePrint("3 Duis ultricies auctor fringilla. Nunc lacus arcu, scelerisque ac arcu a, finibus viverra turpis.") + font.ConsolePrint("4 Fusce auctor eleifend erat, sit amet iaculis augue.") + font.ConsolePrint("5") + font.ConsolePrint("6 Mauris orci quam, ornare eget orci ut, ullamcorper sollicitudin nunc.") + font.ConsolePrint("7 Morbi eu nibh urna.") + font.ConsolePrint("8 Sed vel aliquam nisl.") + font.ConsolePrint("9 ") + font.ConsolePrint("0 Mauris ornare tellus eu metus blandit congue.") + font.ConsolePrint("1 Nunc metus lacus, laoreet finibus tempus quis, gravida non lectus.") + font.ConsolePrint("2 Sed sed rutrum neque, sed venenatis libero.") + font.ConsolePrint("3") + font.ConsolePrint("4 Interdum et malesuada fames ac ante ipsum primis in faucibus.") + font.ConsolePrint("5 Nam pretium tristique lectus. Vestibulum venenatis tellus ut euismod hendrerit.") + font.ConsolePrint("6 Suspendisse quis nibh blandit, tincidunt mauris eget, eleifend lorem.") + font.ConsolePrint("7 <------") //48th line +} + +//go:extern _binary_font_psf_start +var _binary_font_psf_start *uint8 + +//go:export sync_el1h_handler +func interruptHandler(n int, esr uint64, address uint64) { + //if you see this, probably you are hitting the GPU's mailbox with bad params + //see README.md + print("unexpected interrupt: synchronous el1h: esr:", esr, " address 0x") + dev.UART0Hex64(address) + sp := dev.ReadRegister("sp") + print("sp is ") + dev.UART0Hex64(uint64(sp)) + dev.Abort() +} diff --git a/rpi3/03_framebuffer/font.psf b/rpi3/03_framebuffer/font.psf new file mode 100644 index 0000000..3e67693 Binary files /dev/null and b/rpi3/03_framebuffer/font.psf differ diff --git a/rpi3/03_framebuffer/rpi3_bl.S b/rpi3/03_framebuffer/rpi3_bl.S new file mode 100644 index 0000000..7d0ad49 --- /dev/null +++ b/rpi3/03_framebuffer/rpi3_bl.S @@ -0,0 +1,69 @@ +.section ".text.boot_bl" + +// +// The bootloader changes execution state for bootloaded code to EL2 if that is +// needed. This is after execution switches to the bootloaded code, and it drops +// to EL1 once the stack is setup ok. +// + +// startup parameters are: +// x0 - start address (also in PC) +// x1 - stack ptr +// x2 - heap ptr +// x3 - unix time +.global _start + //set the sp for el1, but we have to have the fp space so subtract 16 (two 64bit ptr) + sub x1,x1,#16 + msr sp_el1, x1 + + + // enable CNTP for EL1 + mrs x27, cnthctl_el2 + orr x27, x27, #3 + msr cnthctl_el2, x27 + msr cntvoff_el2, xzr + + // enable AArch64 in EL1 + mov x27, #(1 << 31) // AArch64 + orr x27, x27, #(1 << 1) // SWIO hardwired on Pi3 + msr hcr_el2, x27 + mrs x27, hcr_el2 + + // system control register + mov x4, #0x0800 + movk x4, #0x30d0, lsl #16 + msr sctlr_el1, x4 + + //insert default vectors for core 0 + ldr x27, =vectors + msr vbar_el1, x27 + + // change execution level to EL1 + mov x27, #0x3c5 //5! not 4! we have our own stack + msr spsr_el2, x27 + adr x27, 1f + msr elr_el2, x27 + eret + +## getting the stack and fp in order, we subtracted 16 from x1 earlier in bootloader +## note that we store 0 for the link and ret values because we are at the +## bottom of the stack and want stack to correctly show in GDB and similar +1: + # fp to bottom of frame + add x27, x1, #16 + mov x29, x27 + + #shove values in based on top of stack + str xzr, [sp, #8] + str xzr, [sp, #16] + + //sp points to top of stack (for Bootloaded code 0x30000 - 0x20) + //fp points to bottom (x29) (for Bootloaded code 0x30000 - 0x10) + + //tell hardware that EL0 has own stack + //msr SPSel, #1 + + // jump to go code + bl mainFromBootloader + // for failsafe, halt this core too + wfe diff --git a/rpi3/03_framebuffer/rpi3_bl.json b/rpi3/03_framebuffer/rpi3_bl.json new file mode 100644 index 0000000..c1d5daf --- /dev/null +++ b/rpi3/03_framebuffer/rpi3_bl.json @@ -0,0 +1,4 @@ +{ + "inherits": ["rpi3"], + "linkerscript": "rpi3_bl.ld" +} diff --git a/rpi3/03_framebuffer/rpi3_bl.ld b/rpi3/03_framebuffer/rpi3_bl.ld new file mode 100644 index 0000000..b0a15c0 --- /dev/null +++ b/rpi3/03_framebuffer/rpi3_bl.ld @@ -0,0 +1,19 @@ +SECTIONS +{ +. = 0x10000; +.text : { KEEP(*(.text.boot_bl)) *(.text .text.* .gnu.linkonce.t*) } +.rodata : { *(.rodata .rodata.* .gnu.linkonce.r*) } +PROVIDE(_data = .); +.data : { *(.data .data.* .gnu.linkonce.d*) } +.bss (NOLOAD) : { + . = ALIGN(16); + __bss_start = .; + *(.bss .bss.*) + *(COMMON) + __bss_end = .; +} +_end = .; + +/DISCARD/ : { *(.comment) *(.gnu*) *(.note*) *(.eh_frame*) *(.text.boot)} +} +__bss_size = (__bss_end - __bss_start)>>3; diff --git a/rpi3/CREDITS.md b/rpi3/CREDITS.md new file mode 100644 index 0000000..43cc0ce --- /dev/null +++ b/rpi3/CREDITS.md @@ -0,0 +1,31 @@ +Almost all of the information about how to control the Raspberry PI 3 +in "bare metal" mode came from the internet. The primary sources are listed +below, but there are probably others that I have forgotten and for that +I apologize. + +These primary sources are absolutely critical if you want to know how the actual +hardware of the RPI works. In most cases I simply translate the wonderful +examples they provided from C to Go. The authors did all the hard work. + +* bzt -- Zoltan Baldaszti +https://github.com/bztsrc/raspi3-tutorial/ + +* dwelch -- David Welch +https://github.com/dwelch67/raspberrypi + +* ldb -- Leon de Boer +https://github.com/LdB-ECM + +* s-matyukevich -- Sergey Matyukevich +https://github.com/s-matyukevich/raspberry-pi-os + +* eggman -- +https://github.com/eggman/raspberrypi + +* Raspberry PI Bare Metal Forum -- lots of excellent posts and helpful folks. +https://www.raspberrypi.org/forums/viewforum.php?f=72&sid=a669482c89f6f7e9b2f7647ac200fa7c + + + +* krom -- Peter Lemon +https://github.com/PeterLemon/RaspberryPi diff --git a/rpi3/README.md b/rpi3/README.md new file mode 100644 index 0000000..2fc8bf2 --- /dev/null +++ b/rpi3/README.md @@ -0,0 +1,76 @@ +# Raspberry PI Samples + +## Requirements +You have to have three things in your PATH. A copy of tinygo (that includes +the rpi3 devices, version 0.9+), a "normal" copy of go of at least version 1.11, +and `llvm-copy`. + +The "normal" go will be used (only) to compile/run a program that runs on the host +computer and talks to the RPI3 over serial. + +`llvm-objcopy` is used to extract a bootable image from an elf file created by +tinygo, which is using llvm under the covers. If you built llvm as part +of installing tinygo, then you can probably find the binary you need in +`llvm-build/bin/llvm-objcopy` within your tinygo source tree. + +## Hardware vs. Emulation + +It may be advantageous to have a recent copy of QEMU (4.10+) so you can run +in emulation mode without needing an actual piece of hardware. + +If you are using a real hardware version of the RPI3, it must be connected to the +host computer over a serial port. See this tutorial for how to install a +serial cable connection to the host and RPI3: +https://learn.adafruit.com/adafruits-raspberry-pi-lesson-5-using-a-console-cable/overview + +## How To Use + +Run the "device" part of the bootloader `anticipationbl` either on your RPI3 (connected via +serial to the host) or on QEMU (see `make runqemu`). Then you run the host side, +passing the appropriate device (see `make runqemu` in `anticipation` for an example +with QEMU). You need to make sure you use the right device to talk to the device +side, either serial port like `/dev/tty.SLAB_USBtoUART` or the tty device created +by QEMU when you started the device side. + +Once you have started the host side, it will transfer the elf file you supplied +on the command line. Once that is completed, the device-side bootloader jumps +to the downloaded file's entry point and the downloaded file starts executing. + +## What's Here + +* `anticipation` and `anticipationbl` which together allow bootloading over +the serial port on either hardware or on qemu. + +* `00_simple` Simplest possible bootloaded program. It prints out one line +to the terminal, then echos back whatever you type at it. Works on QEMU. + +* `01_simple` The inevitable blinking light example. This one uses the timers +in QEMU or on the hardware (they aren't the same) and handles the timer +interrupt to blink the light. + +* `02_delay` Demonstrates how to use the delays on the system and how to get +the system time. Note that RPI3 does not have a battery-powered clock, so +the time is copied from the host to the running program via the bootloader. + +* `03_framebuffer` Initializes the framebuffer, and then uses a font to write +to the "console" on the screen. It does this in a loop so it scrolls a lot. + +## To bootload or not to bootload + +The bootloader provided, anticipation, has two primary advantages over running +your own code on bare metal by creating a `kernel8.img` and then booting from that. + +1) Less hassle when doing development. Constantly changing SD cards from the +RPI3 to the host and back to update code is irritating. Further, this increases +the mechanical wear and tear on the RPI3's card slot--which is less than robust. + +2) The bootloader can get your system into a known, useful state without you +having to worry about initialization. Primarily this means that devices are +initialized, your code is running with a sensible stack and heap pointers, the +time is set, and so on. + +### If you don't want to bootload + +Copy the makefile from `anticipationbl` and go for it! You can see there that +you use tinygo to create a self contained kernel image in the same way that +the bootloader builds. diff --git a/rpi3/anticipation/.gitignore b/rpi3/anticipation/.gitignore new file mode 100644 index 0000000..2ebf9c5 --- /dev/null +++ b/rpi3/anticipation/.gitignore @@ -0,0 +1 @@ +anticipation diff --git a/rpi3/anticipation/Makefile b/rpi3/anticipation/Makefile new file mode 100644 index 0000000..e773a46 --- /dev/null +++ b/rpi3/anticipation/Makefile @@ -0,0 +1,21 @@ +all: + go build -o anticipation ./cmd/anticipation + +## QEMU info +## 1) you have to use the right TTY that is created by anticipationbl when +## it starts (via QEMU). You should run anticipationbl first so you can see the output +## of the device (tty) to use to connect to it. +## 2) You need to choose what you want the bootloader to load. In the +## example below, we are loading the kernel (must be an ELF file) created +## by building in the 00_simple directory. +runqemu: + ./anticipation -d /dev/ttys007 ../00_simple/kernel8.elf + +## RPI3 (hardware) info +## 1) you need to know where your Serial-To-USB device is. I have +## listed the device below as it exists on my Mac. +## 2) You need to choose what you want the bootloader to load. In the +## example below, we are loading the kernel (must be an ELF file) created +## by building in the 00_simple directory. +runrpi3: + ./anticipation -d /dev/tty.SLAB_USBtoUART ../00_simple/kernel8.elf diff --git a/rpi3/anticipation/README.md b/rpi3/anticipation/README.md new file mode 100644 index 0000000..8b6d880 --- /dev/null +++ b/rpi3/anticipation/README.md @@ -0,0 +1,30 @@ +### Notice +This program runs on the host system (linux, darwin, etc) and requires +a "normal" copy of go. The go compiler should be at least version 1.11. + +See the `Makefile` for how to run this program with QEMU as a simulator. +See the `Makefile` for how to run this against real hardware. + + +### What you will see +You will see a sequence of log messages (lines preceded by timestamps like +2019/09/09 20:14:33) as the bootloading process goes on. Once the program +is loaded and started, you'll see a log message that says +"starting terminal loop...." After that, the terminal is connected to +your program and you'll notice the text sent from the device is not preceded +by a timestamp. + +# Running with QEMU (copied from Makefile) +1. you have to use the right TTY that is created by anticipationbl when +it starts (via QEMU). You should run anticipationbl first so you can see the output +of the device (tty) to use to connect to it. +2. You need to choose what you want the bootloader to load. In the +example below, we are loading the kernel (must be an ELF file) created +by building in the 00_simple directory. + +# Running on Hardware (copied from Makefile) +1. you need to know where your Serial-To-USB device is. I have +listed the device below as it exists on my Mac. +2. You need to choose what you want the bootloader to load. In the +example below, we are loading the kernel (must be an ELF file) created +by building in the 00_simple directory. diff --git a/rpi3/anticipation/go.mod b/rpi3/anticipation/go.mod new file mode 100644 index 0000000..caedc50 --- /dev/null +++ b/rpi3/anticipation/go.mod @@ -0,0 +1,5 @@ +module anticipation + +go 1.11 + +require github.com/pkg/term v0.0.0-20190109203006-aa71e9d9e942 diff --git a/rpi3/anticipation/go.sum b/rpi3/anticipation/go.sum new file mode 100644 index 0000000..ba844f5 --- /dev/null +++ b/rpi3/anticipation/go.sum @@ -0,0 +1,2 @@ +github.com/pkg/term v0.0.0-20190109203006-aa71e9d9e942 h1:A7GG7zcGjl3jqAqGPmcNjd/D9hzL95SuoOQAaFNdLU0= +github.com/pkg/term v0.0.0-20190109203006-aa71e9d9e942/go.mod h1:eCbImbZ95eXtAUIbLAuAVnBnwf83mjf6QIVH8SHYwqQ= diff --git a/rpi3/anticipation/main.go b/rpi3/anticipation/main.go new file mode 100644 index 0000000..746a6c9 --- /dev/null +++ b/rpi3/anticipation/main.go @@ -0,0 +1,698 @@ +package anticipation + +import ( + "bytes" + "debug/elf" + "encoding/hex" + "fmt" + "io" + "log" + "os" + "os/signal" + "strings" + "sync" + "time" + + "github.com/pkg/term" +) + +const normalSize = 32 +const maxDataLineFails = 2 //this is multiplied by maxFailsOneLine for # fails on a single data line +const maxFailsOneLine = 5 +const maxCommandFails = 5 // usually the other side is in a bad state when this happens +const maxPings = 25 +const sanityBlockSize = 0x20 +const notificationInterval = 0x800 +const pageSize = 0x100000 + +var debugRcvd = false +var debugRcvdChars = false +var debugSent = false + +var cleanupNeeded = []*term.Term{} + +var signalChan = make(chan os.Signal, 1) + +type antcState int + +const ( + waiting antcState = iota + sectionSend + done +) + +const ready = "?" +const sectionTransmit = "section" +const fetchTransmit = "fetch" +const inflateTransmit = "inflate" +const launchTransmit = "launch" +const pingTransmit = "ping" + +var state = waiting + +type sectionInfo struct { + offset uint64 + physAddr uint64 + size uint64 +} + +// Main is where the action is. +func Main(device string, kpath string, byteLimit int) { + + log.SetOutput(os.Stdout) + var terminal io.ReadWriter + + if strings.HasPrefix(device, "/dev/") { + terminal = setupTTY(device, false) + } else { + file, err := os.Open(device) + if err != nil { + log.Fatalf("unable to open %s: %v", device, err) + } + terminal = file + } + + pingLoop(terminal) + + // + // Open the kernel file for our transmissions + // + in, err := os.Open(kpath) + if err != nil { + fatalf(terminal, "unable to open %s: %v", kpath, err) + } + + // end of the program is the end of what we sent to the bootloader (highest address used) + endOfProgram := uint64(0) + // + // these are the sections that require us to copy bytes to target in hex format + // executable, rwdata, rodata + // + var execInfo, rwInfo, roInfo *sectionInfo + names := []string{"executable", "rw data", "readonly data"} + execInfo, err = getExecutable(kpath) + if err != nil { + fatalf(terminal, "loading executable: %v", err) + } + if execInfo.physAddr+execInfo.size > endOfProgram { + endOfProgram = execInfo.physAddr + execInfo.size + } + entryPoint := execInfo.physAddr + rwInfo, err = getRWData(kpath) + if err != nil { + log.Printf("WARNING: no RW section found in the elf file %v", kpath) + //fatalf(terminal, "loading rw: %v", err) + } else { + if rwInfo.physAddr+rwInfo.size > endOfProgram { + endOfProgram = rwInfo.physAddr + rwInfo.size + } + } + roInfo, err = getSectionByName(kpath, ".rodata") + if err != nil { + fatalf(terminal, "loading ro: %v", err) + } + if roInfo.physAddr+roInfo.size > endOfProgram { + endOfProgram = roInfo.physAddr + roInfo.size + } + for i, info := range []*sectionInfo{execInfo, rwInfo, roInfo} { + if info == nil { + continue + } + log.Printf("sending %s: 0x%04x bytes total, starts at offset 0x%04x in elf file\n", names[i], info.size, info.offset) + ok, badState := sendSection(terminal, info, in) + if !ok { + if badState { + fatalf(terminal, "synchronization lost with device, aborting") + } + fatalf(terminal, "giving up trying to send %s", names[i]) + } + sanityCheck(terminal, info, in, names[i]) + } + + // + // Inflate the BSS data and zero it + // + info, err := getSectionByName(kpath, ".bss") + if err != nil { + fatalf(terminal, "unable to load the .bss section: %v", err) + } + if !sendInflate(terminal, info.physAddr, info.size) { + fatalf(terminal, "unable to inflate bss section, giving up") + } + if info.physAddr+info.size > endOfProgram { + endOfProgram = info.physAddr + info.size + } + + mask := ^int64(pageSize - 1) + heap := (endOfProgram + pageSize) & uint64(mask) //upward + stack := heap + pageSize + log.Printf("highest address used by loaded program: 0x%04x... launching at %04x with SP %04x and heap at %04x", endOfProgram, entryPoint, stack, heap) + if !sendLaunch(terminal, entryPoint, stack, heap) { + fatalf(terminal, "failed to launch, giving up") + } + simpleTerminal(terminal, device, byteLimit) +} + +const sanityCheckSize = 0x100 + +func sanityCheck(terminal io.ReadWriter, prog *sectionInfo, in *os.File, name string) { + // do a sanity check on the first few bytes.... seek back to origin + _, err := in.Seek(int64(prog.offset), 0) + if err != nil { + fatalf(terminal, "failed to seek to do sanity check: %v", err) + } + minSize := uint64(sanityCheckSize) + if prog.size < minSize { + minSize = prog.size + } + + // run some records through fetch and compare to disk version + for p := prog.physAddr; p < prog.physAddr+minSize; p += sanityBlockSize { + ok, line := sendFetch(terminal, p) + if !ok { + fatalf(terminal, "fetch failed, giving up") + } + rcvd, err := hex.DecodeString(line) + if err != nil { + fatalf(terminal, "unable to decode hex from bl: %v", err) + } + onDisk := make([]byte, len(rcvd)) + n, err := in.Read(onDisk) + if err != nil || n != len(rcvd) { + fatalf(terminal, "unable to read the disk to do comparison: %v", err) + } + for i := 0; i < len(rcvd) && uint64(i) < minSize && p+uint64(i) < prog.physAddr+minSize; i++ { + if rcvd[i] != onDisk[i] { + log.Printf("byte mismatch found at 0x%04x: 0x%x expected but got 0x%x (iteration %d, minsize %d)", + int(p)+i, onDisk[i], rcvd[i], i, minSize) + } + } + } + log.Printf("sanity check completed on '%s' (checked first 0x%04x bytes)", name, minSize) +} + +func sendSection(terminal io.ReadWriter, info *sectionInfo, fp *os.File) (bool, bool) { + switch state { + case waiting: + line, ok := readLine(terminal) + if !ok { + return false, false + } + if line == ready { + ok, badState := sendSectionCommand(terminal) + if ok { + state = sectionSend + } else { + return false, badState + } + } + fallthrough + case sectionSend: + if err := transmitSection(terminal, info, fp); err == false { + return false, false + } + state = waiting + return true, false + default: + fatalf(terminal, "unknown anticipation state: %d", int(state)) + } + return true, false //should never happen +} + +func cleanupAndExit() { + for _, terminal := range cleanupNeeded { + terminal.Write([]byte{0x10, 0x21, 0x10, 0x04}) + terminal.Restore() + } + os.Exit(0) +} + +func readOneByte(t io.ReadWriter) (byte, bool) { + c := make([]byte, 1) + n, err := t.Read(c) + if err != nil { + if err == io.EOF { + return 0, false + } + fatalf(t, "error reading character from terminal: %v", err) + } + if n == 0 && err == io.EOF { + cleanupAndExit() + } + if debugRcvdChars { + if c[0] == 10 { + log.Printf("<<<<< debugChar: LF") + } else { + log.Printf("<<<< debugChar: %c", c[0]) + } + } + return c[0], true +} + +func readLine(t io.ReadWriter) (string, bool) { + var buffer bytes.Buffer + for { + c, ok := readOneByte(t) + if !ok { + return "", false + } + if c == 10 { + break + } + if err := buffer.WriteByte(c); err != nil { + fatalf(t, "error writing to buffer: %v", err) + } + } + l := buffer.String() + if len(l) == 0 { + if debugRcvd { + log.Printf("<----------- EMPTY LINE received!") + } + return l, true + } + if debugRcvd { + log.Printf("<----------- %s", l) + } + return l, true +} + +func fatalf(t io.ReadWriter, s string, args ...interface{}) { + log.Printf(s, args...) + cleanupAndExit() +} + +func transmitSection(t io.ReadWriter, info *sectionInfo, fp *os.File) bool { + //setup file pointer + ret, err := fp.Seek(int64(info.offset), 0) + if err != nil || ret != int64(info.offset) { + fatalf(t, "bad seek: %v (ret was %d, size is %d)", err, ret, info.offset) + } + + return transmitFile(t, info.physAddr, info.size, fp) +} + +func transmitFile(t io.ReadWriter, paddr uint64, filesz uint64, fp *os.File) bool { + if !sendHexESA(t, paddr) { + return false + } + + current := uint64(0) + buffer := make([]byte, normalSize) + + //we will try data lines up to 5 times + fails := 0 + + for current < filesz && fails < maxDataLineFails { + size := 32 //normal case + if int(filesz-current) < 32 { + size = int(filesz - current) + } + n, err := fp.Read(buffer[:size]) + if n != int(size) || err != nil { + fatalf(t, "bad read from data file: %v (with size=%d and bytes read=%d)", err, size, n) + } + if !sendHexData(t, size, current, buffer) { + //move fp back to previous position + _, err := fp.Seek(int64(-size), 1) + if err != nil { + fatalf(t, "bad seek for rewind: %v (n was %d, size is %d)", err, n, size) + } + fails++ + } else { + fails = 0 + if current%notificationInterval == 0 { //we iter in 0x20 increments + log.Printf("sent block @ 0x%08x!\n", current+paddr) + } + current += uint64(size) + } + } + if fails == maxDataLineFails { + return false + } + if !sendHexEOF(t) { + return false + } + log.Printf("completed sending file: 0x%04x bytes", filesz) + return true +} + +// returns the line sent in either case +func confirm(t io.ReadWriter) (bool, string) { + l, ok := readLine(t) + if !ok { + return false, "" + } + if strings.HasPrefix(l, ".") { + return true, l + } + return false, l +} + +func writeWithChecksum(t io.ReadWriter, payload string) { + decoded, err := hex.DecodeString(payload) + if err != nil { + fatalf(t, "bad hex string: %v", err) + } + //figure out checksum + sum := uint64(0) + for i := 0; i < len(decoded); i++ { + sum += uint64(decoded[i]) + } + complement := ((^sum) + 1) + checksum := uint8(complement) & 0xff + + line := fmt.Sprintf(":%s%02x\n", payload, checksum) + b := []byte(line) + + current := 0 + if debugSent { + log.Printf("------------> %+v\n", b) + } + for current < len(b) { + n, err := t.Write(b[current:]) + if err != nil { + fatalf(t, "failed writing line to bl: %v", err) + } + current += n + } + +} + +func sendHexEOF(t io.ReadWriter) bool { + payload := fmt.Sprintf("00000001") + ok, _ := sendSingleCommand(t, payload, "EOF", maxFailsOneLine, false) + return ok +} + +func sendHexESA(t io.ReadWriter, paddr uint64) bool { + executableLocation := paddr >> 4 + if executableLocation > 0xffff { + fatalf(t, "unable to use hex record type 02 (ESA) because executable physical address too large: %x", paddr) + } + loc16 := uint16(executableLocation) //checked above + payload := fmt.Sprintf("02000002%04x", loc16) + ok, _ := sendSingleCommand(t, payload, "ESA", maxDataLineFails, false) + return ok +} + +func sendHexData(t io.ReadWriter, size int, current uint64, buffer []byte) bool { + payload := fmt.Sprintf("%02x%04x00", size, current) + for i := 0; i < size; i++ { + payload += fmt.Sprintf("%02x", buffer[i]) + } + // prev := debugSent + // if prev { + // log.Printf("------------> DATA @ 0x%04x00", current) + // debugSent = false + // } + ok, _ := sendSingleCommand(t, payload, "DATA", maxFailsOneLine, false) + // if prev { + // debugSent = true + // } + return ok +} + +// bool is success/failure and the string is the response in the failure case +func sendSingleCommand(t io.ReadWriter, payload string, name string, maxFails int, quiet bool) (bool, string) { + tries := 0 + confirmLine := "" + var ok bool + for tries < maxFails { + if isCommand(payload) { + p := fmt.Sprintf(":%s\n", payload) + b := []byte(p) + if debugSent { + log.Printf("------------> %+v\n", b) + } + n, err := t.Write(b) + if err != nil || n != len(p) { + fatalf(t, "unable to send command %s (%d bytes sent): %v", payload, n, err) + } + } else { + writeWithChecksum(t, payload) + } + ok, confirmLine = confirm(t) + if ok { + break + } + if !quiet { + log.Printf("attempt %d of %s command failed, response: %s", tries, name, confirmLine) + //special case because we lost sync + if (sectionTransmit == payload || name == "ESA") && confirmLine == "?" { + return false, "?" + } + } + tries++ + } + if tries == maxFails { + return false, confirmLine + } + return true, confirmLine +} + +func setupTTY(device string, cbreak bool) io.ReadWriter { + // tty shenanigans + tty, err := term.Open(device) + if err != nil { + log.Fatalf("unable to open %s: %v", device, err) + } + + if err := tty.SetFlowControl(term.NONE); err != nil { + log.Fatalf("unable to set flow control none:%v", err) + } + if err := tty.SetSpeed(115200); err != nil { + log.Fatalf("unable to set speed:%v", err) + } + + if cbreak { + if err := tty.SetCbreak(); err != nil { + log.Fatalf("unable to set cbreak on %s: %v", device, err) + } + } else { + if err := tty.SetRaw(); err != nil { + log.Fatalf("unable to set raw on %s: %v", device, err) + } + err := tty.SetReadTimeout(time.Duration(5 * time.Second)) + if err != nil { + log.Fatalf("unable to set read timeout on tty: %v", err) + } + } + cleanupNeeded = append(cleanupNeeded, tty) + + signal.Notify(signalChan, os.Interrupt) + go func(t *term.Term) { + <-signalChan + cleanupAndExit() + os.Exit(0) + }(tty) + return tty +} + +func sendSectionCommand(t io.ReadWriter) (bool, bool) { + ok, line := sendSingleCommand(t, sectionTransmit, "SECTION", maxCommandFails, false) + if !ok && line == "?" { + return false, true + } + return ok, false +} + +func isCommand(payload string) bool { + switch { + case sectionTransmit == payload: + return true + case pingTransmit == payload: + return true + case strings.HasPrefix(payload, fetchTransmit): + return true + case strings.HasPrefix(payload, inflateTransmit): + return true + case strings.HasPrefix(payload, launchTransmit): + return true + default: + return false + } +} + +func sendFetch(t io.ReadWriter, addr uint64) (bool, string) { + cmd := fmt.Sprintf("%s %08x", fetchTransmit, addr) + ok, response := sendSingleCommand(t, cmd, "FETCH ", maxCommandFails, false) + if !ok { + return false, response + } + return true, response[len(".ok "):] +} + +func sendLaunch(t io.ReadWriter, addr uint64, stack uint64, heap uint64) bool { + cmd := fmt.Sprintf("%s %08x %08x %08x %08x", launchTransmit, addr, stack, heap, time.Now().Unix()) + ok, _ := sendSingleCommand(t, cmd, "LAUNCH ", maxCommandFails, false) + if !ok { + return false + } + return true +} + +func sendInflate(t io.ReadWriter, addr uint64, size uint64) bool { + cmd := fmt.Sprintf("%s %08x %08x", inflateTransmit, addr, size) + ok, _ := sendSingleCommand(t, cmd, "INFLATE ", maxCommandFails, false) + if !ok { + return false + } + return true +} + +func getRWData(kpath string) (*sectionInfo, error) { + return getProgramSectionByHeader(kpath, false, true, true) +} + +func getExecutable(kpath string) (*sectionInfo, error) { + //return getSectionByName(kpath, ".text") + return getProgramSectionByHeader(kpath, true, false, true) +} + +func getSectionByName(kpath string, sectionName string) (*sectionInfo, error) { + elfFile, err := elf.Open(kpath) + if err != nil { + log.Fatalf("whoa!?!? can't read elf file but checked it before: %v", err) + } + defer elfFile.Close() + s := elfFile.Section(sectionName) + if s == nil { + return nil, fmt.Errorf("unable to find section %s", sectionName) + } + return §ionInfo{ + offset: s.Offset, + physAddr: s.Addr, + size: s.Size, + }, nil +} + +func getProgramSectionByHeader(kpath string, targExec bool, targWrite bool, targRead bool) (*sectionInfo, error) { + elfFile, err := elf.Open(kpath) + if err != nil { + log.Fatalf("whoa!?!? can't read elf file but checked it before: %v", err) + } + defer elfFile.Close() + for _, prog := range elfFile.Progs { + if prog.Type == elf.PT_LOAD { + isExecutable := false + isReadable := false + isWritable := false + if prog.Flags&elf.PF_X == elf.PF_X { + isExecutable = true + } + if prog.Flags&elf.PF_W == elf.PF_W { + isWritable = true + } + if prog.Flags&elf.PF_R == elf.PF_R { + isReadable = true + } + if targExec == isExecutable && targRead == isReadable && targWrite == isWritable { + info := §ionInfo{ + physAddr: prog.Paddr, + offset: prog.Off, + size: prog.Filesz, + } + return info, nil + } + } + } + return nil, fmt.Errorf("no executable program found in %s with attributes x=%v,w=%v,r==%v", + kpath, targExec, targWrite, targRead) +} + +func simpleTerminal(terminal io.ReadWriter, device string, byteLimit int) { + t, ok := terminal.(*term.Term) + if !ok { + fatalf(terminal, "unable to run simple terminal when terminal is a file!") + } + t.SetReadTimeout(0) + byteCount := 0 + log.Printf("starting terminal loop....\n") + // log.Printf("hackery: %s",readLine(terminal)) + + if device != "/dev/tty" { + k := setupTTY("/dev/tty", true) + kbd := k.(*term.Term) + + var wg sync.WaitGroup + wg.Add(2) + + go func(wg *sync.WaitGroup) { + defer wg.Done() + + for { + one := make([]byte, 1) + _, err := kbd.Read(one) + if err != nil { + fatalf(terminal, "unable to read from /dev/tty: %v", err) + } + _, err = terminal.Write(one) + if err != nil { + fatalf(terminal, "unable to write to device: %v", err) + } + if one[0] == 0x04 { + cleanupAndExit() + } + } + }(&wg) + go func(wg *sync.WaitGroup) { + defer wg.Done() + + one := make([]byte, 1) + for { + n, err := t.Read(one) + if err != nil { + fatalf(t, "unable to read from device: %v", err) + return + } + if n == 0 { + fmt.Printf("read failed (no error, but no data read)\n") + continue + } + if one[0] == 0 { + fmt.Printf("nul ") + continue + } + if one[0] < 32 && one[0] != '\n' { + fmt.Printf("[%02x]", one[0]) + continue + } + _, err = kbd.Write(one) + if err != nil { + fatalf(terminal, "unable to write to kbd terminal: %v", err) + } + byteCount++ + if byteLimit > 0 && byteCount >= byteLimit { + log.Printf("byte limit of %d characters reached", byteLimit) + cleanupAndExit() + } + } + }(&wg) + wg.Wait() //currently this will wait forever because there is no exit protocol + cleanupAndExit() + } +} + +func pingLoop(t io.ReadWriter) { + // ping is useful for being sure our connection is ok and giving us a chance + // to futz with the set hardware flow control + attempts := 0 + for attempts < maxPings { + log.Printf("sending ping %d\n", attempts) + ok, _ := sendSingleCommand(t, pingTransmit, "PING", 3, false) + if ok { + break + } + attempts++ + terminal, ok := t.(*term.Term) // just in case it ends up being a file or something + if ok { + if err := terminal.SetFlowControl(term.NONE); err != nil { + log.Fatalf("unable to set flow control none:%v", err) + } + } + } + if attempts == maxPings { + fatalf(t, "giving, cannot reach the device with ping...(%d attempts)", attempts) + } + log.Printf("connection established") +} diff --git a/rpi3/anticipationbl/.gitignore b/rpi3/anticipationbl/.gitignore new file mode 100644 index 0000000..0bb33b0 --- /dev/null +++ b/rpi3/anticipationbl/.gitignore @@ -0,0 +1,2 @@ +kernel8.* +.gdbinit diff --git a/rpi3/anticipationbl/Makefile b/rpi3/anticipationbl/Makefile new file mode 100644 index 0000000..2f79bcc --- /dev/null +++ b/rpi3/anticipationbl/Makefile @@ -0,0 +1,24 @@ +all: kernel8.img + +# note that we are building kernel8.img as well as kernel8.elf. +# on raspberry pi (which uses bcm2837) kernel8.img is one that can be copied +# onto an SD card and booted. The name of the kernel to load is kernel8.img on a RPI3 but +# if you have changed options in config.txt on the SD card, it might different +# for you. +# +# kernel8.elf is used if you want to run and use QEMU as a simulator (-M raspi3) +# + +## NOTE: this uses the bare metal version of RPI (--target rpi3) but all the things +## NOTE: loaded WITH this bootloader should supply the --target rpi3_bl +kernel8.img: clean + tinygo build --target=rpi3 -o kernel8.elf . + llvm-objcopy -O binary kernel8.elf kernel8.img + +clean: + rm kernel8.elf kernel8.img *.o >/dev/null 2>/dev/null || true + +## for running the simulator, use this target BEFORE starting the host-side +## in ../anticipation +runqemu: + qemu-system-aarch64 --chardev 'pty,id=char0' -M raspi3 -kernel kernel8.img -serial chardev:char0 diff --git a/rpi3/anticipationbl/TODO.md b/rpi3/anticipationbl/TODO.md new file mode 100644 index 0000000..6d1c261 --- /dev/null +++ b/rpi3/anticipationbl/TODO.md @@ -0,0 +1,10 @@ +### Bootloader TODO + +1. Figure out why the blinker example has bad bytes in its read-write data. Once +this is fixed, return "byte mismatch" to being a fatal error. + +2. Figure out how to clear out the buffer (buffers?) at startup so there are +not "left over" lines in the buffers when the host side starts up again. + +3. Determine what state the rpi3 gets into when it stops responding to DATA +requests and we are forced to just do a reset. diff --git a/rpi3/anticipationbl/main.go b/rpi3/anticipationbl/main.go new file mode 100644 index 0000000..a582e5b --- /dev/null +++ b/rpi3/anticipationbl/main.go @@ -0,0 +1,488 @@ +package main + +import ( + dev "device/rpi3" + "unsafe" +) + +//first character of line sent: +//? in command mode +//# in hex mode +//. ok +//! error + +const ready = "?\n" +const readyHex = "#\n" +const sectionCommand = ":section" +const fetchCommand = ":fetch" +const inflateCommand = ":inflate" +const launchCommand = ":launch" +const pingCommand = ":ping" +const confirm = ".ok\n" +const fetchSize = 0x20 + +var buffer [1025]byte // : is first so to get 512 bytes, we add 1 +var converted [512]uint8 +var hexArgs [4]uint64 +var fetchBuffer [0x40]byte + +type blstate int + +const ( + waiting blstate = iota + section +) + +type hexLineType int + +const ( + dataLine hexLineType = iota + endOfFile + extendedSegmentAddress + badBufferType +) + +var tests = 0 + +func main() { + state := waiting + for { + switch state { + case waiting: + length, ok := readLine() + if !ok { + print(ready) + continue + } + if checkLine(sectionCommand, length) { + print(confirm) + state = section + continue + } + if checkLine(pingCommand, length) { + print(confirm) + continue + } + + if checkLine(fetchCommand, 0) { + performFetch(length) + continue + } + if checkLine(inflateCommand, 0) { + performInflate(length) + continue + } + if checkLine(launchCommand, 0) { + performLaunch(length) + continue + } + //s := string(buffer[:length]) + //print("! bad line:", s, "\n") + case section: + readHex() //succeed or fail, we go back to ready for more sections + state = waiting + } + } + //dev.QEMUTryExit() +} + +func checkLine(s string, l int) bool { + if l != 0 && len(s) != l { + return false + } + for i, b := range []byte(s) { + if buffer[i] != b { + return false + } + } + return true +} + +// returns second value true if this timed out +// otherwise, first return is length of line in buffer, with L/F removed +func readLine() (int, bool) { + for i := 0; i < len(buffer); i++ { + buffer[i] = 0 + } + current := 0 + miss := 0 + for miss < 5000000 { + if dev.UART0DataAvailable() { + miss = 0 + b := dev.UART0Getc() + if b == 10 { + if current == 0 { + print("!empty line! ") + dev.Abort() + } + return current, true + } + buffer[current] = b + current++ + continue + } + miss++ + } + return -181711, false +} + +const failLimitOnHexMode = 10 + +// read a file using Intel Hex format https://en.wikipedia.org/wiki/Intel_HEX +// only uses record types 00, 01, and 02 +//go:export readHex +func readHex() bool { + fails := 0 + baseAddr := uint64(0) + + //this loop reads a purported line of hex protocol and either complains or executes + //the appropriate action + for fails < failLimitOnHexMode { + l, ok := readLine() + if !ok { + print(readyHex) + fails++ + continue + } + if buffer[0] != ':' { + print("!fail line not started with colon but was ", buffer[0], ",", buffer[1], ",", buffer[2], " and ", l, "\n") + fails++ + continue + } + if !convertBuffer(l) { + fails++ + continue + } + if !checkBufferLength(l) { + fails++ + continue + } + if !checkChecksum(l) { + fails++ + continue + } + t := lineType() + if t == badBufferType { + fails++ + continue + } + err, done := processLine(t, &baseAddr) + if err { + fails++ + continue + } + //line was ok if we get here + fails = 0 + print(confirm) + //bail out because we got EOF? + if done { + return true + } + } + //too many fails + return false + +} + +// deal with a received hex line and return (error?,done?) +func processLine(t hexLineType, baseAddr *uint64) (bool, bool) { + switch t { + case dataLine: + l := converted[0] + offset := (uint64(converted[1]) * 256) + (uint64(converted[2])) + offset += *baseAddr + var addr *uint8 + var val uint8 + for i := uint8(0); i < l; i++ { + addr = (*uint8)(unsafe.Pointer(uintptr(offset) + uintptr(i))) + val = converted[4+i] + *addr = val + } + return false, false + case endOfFile: + return false, true + case extendedSegmentAddress: + len := converted[0] + if len != 2 { + print("!ESA value has too many bytes:", len, "\n") + return true, false + } + esaAddr := uint64(converted[4])*256 + uint64(converted[5]) + esaAddr = esaAddr << 4 //it's assumed to be a multiple of 16 + if esaAddr == 0x80000 { + print("!ESA value ,", esaAddr, "would load code over the bootloader\n") + return true, false + } + *baseAddr = esaAddr + return false, false + } + print("!internal error, unexpected line type", t, "\n") + return true, false +} + +// received a line, check that it has a hope of being syntactically correct +func checkBufferLength(l int) bool { + total := uint8(11) //size of just framing in characters (colon, 2 len chars, 4 addr chars, 2 type chars, 2 checksum chars) + if uint8(l) < total { + print("!bad buffer length, can't be smaller than", total, ":", l, "\n") + return false + } + total += converted[0] * 2 + if uint8(l) != total { + print("!bad buffer length, expected", total, "but got", l, " based on ", converted[0], "\n") + return false + } + return true +} + +// verify line's checksum +func checkChecksum(l int) bool { + sum := uint64(0) + limit := (l - 1) / 2 + for i := 0; i < limit; i++ { + sum += uint64(converted[i]) + } + complement := ^sum + complement++ + checksum := uint8(complement & 0xff) + if checksum != 0 { + print("!bad checksum, expected ", checksum, "but got ", converted[limit-1], "\n") + return false + } + return true +} + +// extract the line type, 00 (data), 01 (eof), or 02 (esa) +func lineType() hexLineType { + switch converted[3] { + case 0: + return dataLine + case 1: + return endOfFile + case 2: + return extendedSegmentAddress + default: + print("!bad buffer type:", converted[3], "\n") + return badBufferType + } +} + +// change buffer of ascii->converted bytes by taking the ascii values (2 per byte) and making them proper bytes +func convertBuffer(l int) bool { + //l-1 because the : is skipped so the remaining number of characters must be even + if (l-1)%2 == 1 { + print("!bad payload, expected even number of hex bytes (length read minus LF is=", l, ")") + for i := 0; i < l; i++ { + print(i, "=", buffer[i], " ") + } + print("\n") + return false + } + //skip first colon + for i := 1; i < l; i += 2 { + v, ok := bufferValue(i) + if !ok { + return false // they already sent the error to the other side + } + converted[(i-1)/2] = v + } + return true +} + +func performInflate(length int) { + start := len(inflateCommand + " ") + if !splitHexArguments(2, start, length, "inflate") { + return + } + addr := hexArgs[0] + size := hexArgs[1] + for i := int(addr); i < int(addr)+int(size); i++ { + ptr := (*uint8)((unsafe.Pointer)((uintptr)(i))) + *ptr = 0 + } + print(confirm) +} + +func performLaunch(length int) { + start := len(launchCommand + " ") + if !splitHexArguments(4, start, length, "launch") { + return + } + print(confirm) + addr := hexArgs[0] + stackPtr := hexArgs[1] - 0x10 //writes are from lower to higher, but we have be 16 byte aligned + heapPtr := hexArgs[2] + now := hexArgs[3] + //print("xxx about to jump:", addr, " ", stackPtr, " ", heapPtr, " ", now, "\n") + dev.AsmFull(`mov x1,{stackPtr} + mov x2,{heapPtr} + mov x3,{now} + mov x0,{addr} + br x0`, map[string]interface{}{"addr": addr, "stackPtr": stackPtr, "heapPtr": heapPtr, "now": now}) + + print("!bad launch addr=", addr, " sp=", stackPtr, " heap=", heapPtr, " time=", now, "\n") +} + +func performFetch(length int) { + start := len(fetchCommand + " ") + if !splitHexArguments(1, start, length, "fetch") { + return + } + for i := int(hexArgs[0]); i < int(hexArgs[0])+fetchSize; i++ { + index := i - int(hexArgs[0]) + ptr := (*uint8)((unsafe.Pointer)((uintptr)(i))) + thisByte := *ptr + hi := thisByte >> 4 + lo := thisByte & 0xf + for j, v := range []uint8{hi, lo} { + switch v { + case 0, 1, 2, 3, 4, 5, 6, 7, 8, 9: + fetchBuffer[(index*2)+j] = byte(48 + v) + case 10, 11, 12, 13, 14, 15: + fetchBuffer[(index*2)+j] = byte(65 + (v - 10)) + default: + print("!bad value in hex?!? ", v, "\n") + } + } + } + print(".ok ", string(fetchBuffer[:]), "\n") +} + +// this hits buffer[i] and buffer[i+1] to convert an ascii byte +// returns false to mean you had a bad character in the input +func bufferValue(i int) (uint8, bool) { + total := uint8(0) + switch buffer[i] { + case '0': + case '1': + total += 16 * 1 + case '2': + total += 16 * 2 + case '3': + total += 16 * 3 + case '4': + total += 16 * 4 + case '5': + total += 16 * 5 + case '6': + total += 16 * 6 + case '7': + total += 16 * 7 + case '8': + total += 16 * 8 + case '9': + total += 16 * 9 + case 'a', 'A': + total += 16 * 10 + case 'b', 'B': + total += 16 * 11 + case 'c', 'C': + total += 16 * 12 + case 'd', 'D': + total += 16 * 13 + case 'e', 'E': + total += 16 * 14 + case 'f', 'F': + total += 16 * 15 + default: + print("!bad character in payload hi byte(number #", i, "):", buffer[i], "\n") + return 0xff, false + } + switch buffer[i+1] { + case '0': + case '1': + total++ + case '2': + total += 2 + case '3': + total += 3 + case '4': + total += 4 + case '5': + total += 5 + case '6': + total += 6 + case '7': + total += 7 + case '8': + total += 8 + case '9': + total += 9 + case 'a', 'A': + total += 10 + case 'b', 'B': + total += 11 + case 'c', 'C': + total += 12 + case 'd', 'D': + total += 13 + case 'e', 'E': + total += 14 + case 'f', 'F': + total += 15 + default: + print("!bad character in payload low byte (number #", i+1, "):", buffer[i+1], "\n") + return 0xff, false + } + return total, true +} + +func bufferASCIIToUint(start int, end int) (uint64, bool) { + var ok bool + total := uint64(0) + var thisByte uint8 + placeValue := uint64(24) + for i := start; i < end; i += 2 { + thisByte, ok = bufferValue(i) + if !ok { + return 0, false + } + total += uint64(thisByte) * (1 << placeValue) + placeValue -= 8 + } + return total, true +} + +func splitHexArguments(expected int, start int, length int, name string) bool { + for argNum := 0; argNum < expected; argNum++ { + if argNum == expected-1 { //last arg + end := length + if (end-start)%2 != 0 { + print("!", name, ":argument ", argNum, " [last] has odd number of hex digits, distance=", (end - start), "(length of total command was ", length, " and start was ", start, ") \n") + return false + } + total, ok := bufferASCIIToUint(start, end) + if !ok { + return false + } + hexArgs[argNum] = total + return true + } + //we are not at last argument, look for next space + found := false + end := -1 + for i := start; i < length; i++ { + if buffer[i] == 0x20 { + end = i + found = true + break + } + } + if !found { + print("!unable to find end of argument number ", argNum, "\n") + return false + } + if (end-start)%2 != 0 { + print("!argument ", argNum, "has odd number of hex digits:", (end - start), "(length of total command was ", length, ", start was ", start, " and end was ", end, ") \n") + return false + } + + total, ok := bufferASCIIToUint(start, end) + if !ok { + return false + } + hexArgs[argNum] = total + start = end + 1 + } + return true +}