From a6519030d0810204b55e158c781635a6901931cd Mon Sep 17 00:00:00 2001 From: Ian Smith Date: Tue, 10 Sep 2019 13:09:14 -0400 Subject: [PATCH 01/11] much improved version of 00 and 01; added credits; add support for -l option on host side --- rpi3/00_simple/.gitignore | 2 + rpi3/00_simple/Makefile | 12 + rpi3/00_simple/go.mod | 5 + rpi3/00_simple/go.sum | 16 + rpi3/00_simple/simple.go | 13 + rpi3/01_blinker/.gitignore | 1 + rpi3/01_blinker/Makefile | 12 + rpi3/01_blinker/blinker.go | 40 ++ rpi3/02_delays/delays.go | 45 +++ rpi3/CREDITS.md | 26 ++ rpi3/README.md | 61 ++++ rpi3/anticipation/.gitignore | 1 + rpi3/anticipation/Makefile | 21 ++ rpi3/anticipation/README.md | 30 ++ rpi3/anticipation/go.mod | 5 + rpi3/anticipation/go.sum | 2 + rpi3/anticipation/main.go | 643 +++++++++++++++++++++++++++++++++ rpi3/anticipationbl/.gitignore | 2 + rpi3/anticipationbl/Makefile | 21 ++ rpi3/anticipationbl/main.go | 486 +++++++++++++++++++++++++ 20 files changed, 1444 insertions(+) create mode 100644 rpi3/00_simple/.gitignore create mode 100644 rpi3/00_simple/Makefile create mode 100644 rpi3/00_simple/go.mod create mode 100644 rpi3/00_simple/go.sum create mode 100644 rpi3/00_simple/simple.go create mode 100644 rpi3/01_blinker/.gitignore create mode 100644 rpi3/01_blinker/Makefile create mode 100644 rpi3/01_blinker/blinker.go create mode 100644 rpi3/02_delays/delays.go create mode 100644 rpi3/CREDITS.md create mode 100644 rpi3/README.md create mode 100644 rpi3/anticipation/.gitignore create mode 100644 rpi3/anticipation/Makefile create mode 100644 rpi3/anticipation/README.md create mode 100644 rpi3/anticipation/go.mod create mode 100644 rpi3/anticipation/go.sum create mode 100644 rpi3/anticipation/main.go create mode 100644 rpi3/anticipationbl/.gitignore create mode 100644 rpi3/anticipationbl/Makefile create mode 100644 rpi3/anticipationbl/main.go diff --git a/rpi3/00_simple/.gitignore b/rpi3/00_simple/.gitignore new file mode 100644 index 0000000..0bb33b0 --- /dev/null +++ b/rpi3/00_simple/.gitignore @@ -0,0 +1,2 @@ +kernel8.* +.gdbinit diff --git a/rpi3/00_simple/Makefile b/rpi3/00_simple/Makefile new file mode 100644 index 0000000..7f740c0 --- /dev/null +++ b/rpi3/00_simple/Makefile @@ -0,0 +1,12 @@ +# +# Simplest bootloader example. +# + +all: clean kernel8.img + +# note the target is rpi3_bl +kernel8.img: simple.go + tinygo build --target=rpi3_bl -o kernel8.elf . + +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/simple.go b/rpi3/00_simple/simple.go new file mode 100644 index 0000000..d1829d7 --- /dev/null +++ b/rpi3/00_simple/simple.go @@ -0,0 +1,13 @@ +package main + +import ( + dev "device/rpi3" +) + +func main() { + print("echoing 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..d6b69a0 --- /dev/null +++ b/rpi3/01_blinker/.gitignore @@ -0,0 +1 @@ +kernel8.* diff --git a/rpi3/01_blinker/Makefile b/rpi3/01_blinker/Makefile new file mode 100644 index 0000000..d07000f --- /dev/null +++ b/rpi3/01_blinker/Makefile @@ -0,0 +1,12 @@ +# +# Simplest bootloader example. +# + +all: clean kernel8.img + +# note the target is rpi3_bl +kernel8.img: blinker.go + tinygo build --target=rpi3_bl -o kernel8.elf . + +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..d7ccff5 --- /dev/null +++ b/rpi3/01_blinker/blinker.go @@ -0,0 +1,40 @@ +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(foo uint32, bar uint64) uint32 { + dev.LEDSet(on) + on = !on + return interval +} + +// +// MAIN_QEMU hits the QEMU version of the counters directly. It won't work +// on real hardware. +// +func main() { + + dev.GetRPIID() + blinkerSetup() + + freq := dev.QEMUCounterFreq() //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.QEMUSetCounterTargetInterval(interval, blinker) + dev.QEMUCore0CounterToCore0Irq() + dev.QEMUEnableCounter() + dev.EnableTimerIRQ() + for { + dev.WaitForInterrupt() + } +} diff --git a/rpi3/02_delays/delays.go b/rpi3/02_delays/delays.go new file mode 100644 index 0000000..dfd12ff --- /dev/null +++ b/rpi3/02_delays/delays.go @@ -0,0 +1,45 @@ +package main + +import ( + dev "device/rpi3" + "runtime/volatile" +) + + +// 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 mainForRPI3OnHardware() { + //dev.IRQVectorInitEL1(HandleIRQ) + dev.TimerInit(interval) + dev.EnableInterruptController() + dev.EnableTimerIRQ() +} + +func testDelays() { + print("hello from main\n") + print("Waiting 1000000 CPU cycles (ARM CPU): ") + dev.WaitCycles(1000000) + print("OK\n") + + print("Waiting 1000000 microsec (ARM CPU): ") + dev.WaitMuSec(1000000) + print("OK\n") + + print("Waiting 1000000 microsec (BCM System Timer): ") + if dev.SysTimer() == 0 { + print("Not available\n") + } else { + dev.WaitMuSecST(1000000) + print("OK\n") + } +} diff --git a/rpi3/CREDITS.md b/rpi3/CREDITS.md new file mode 100644 index 0000000..970007c --- /dev/null +++ b/rpi3/CREDITS.md @@ -0,0 +1,26 @@ +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 diff --git a/rpi3/README.md b/rpi3/README.md new file mode 100644 index 0000000..645e553 --- /dev/null +++ b/rpi3/README.md @@ -0,0 +1,61 @@ +# Raspberry PI Samples + +## Requirements +You have to have three things in your PATH. A copy of tinygo (that includes +the rpi3 and rpi3_bl devices), 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 + + +## 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. + +## 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..ef3bf1c --- /dev/null +++ b/rpi3/anticipation/main.go @@ -0,0 +1,643 @@ +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 = 3 // usually the other side is in a bad state when this happens +const maxPings = 25 +const sanityBlockSize = 0x20 +const notificationInterval = 0x800 +const pageSize = 0x10000 + +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 { + fatalf(terminal, "loading rw: %v", err) + } + 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} { + log.Printf("sending %s: 0x%04x bytes total, starts at offset 0x%04x in elf file\n", names[i], info.size, info.offset) + if !sendSection(terminal, info, in) { + 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) && p+uint64(i) < minSize; i++ { + if rcvd[i] != onDisk[i] { + fatalf(terminal, "byte mismatch found at %04x: %x expected but got %x", int(p)+i, onDisk[i], rcvd[i]) + } + } + } + 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 { + for state != done { + switch state { + case waiting: + line := readLine(terminal) + if line == ready { + if sendSectionCommand(terminal) { + state = sectionSend + continue + } + //failed to send the command, user will see us trying a few times + return false + } + log.Printf("expecting ready signal (?) but got '%s'", line) + return false + case sectionSend: + if err := transmitSection(terminal, info, fp); err == false { + return false + } + state = done + continue + default: + fatalf(terminal, "unknown anticipation state: %d", int(state)) + } + } + return true +} + +func cleanupAndExit() { + for _, terminal := range cleanupNeeded { + terminal.Write([]byte{0x10, 0x21, 0x10, 0x04}) + terminal.Restore() + } + os.Exit(0) +} + +func readOneByte(t io.ReadWriter) byte { + c := make([]byte, 1) + n, err := t.Read(c) + if err != nil { + 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] +} + +func readLine(t io.ReadWriter) string { + var buffer bytes.Buffer + for { + c := readOneByte(t) + 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 + } + if debugRcvd { + log.Printf("<----------- %s", l) + } + return l +} + +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... %04x bytes", filesz) + return true +} + +// returns the line sent in either case +func confirm(t io.ReadWriter) (bool, string) { + l := readLine(t) + 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]) + } + ok, _ := sendSingleCommand(t, payload, "DATA", maxFailsOneLine, false) + 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) + } + 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) + } + } + 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 { + ok, _ := sendSingleCommand(t, sectionTransmit, "SECTION", maxCommandFails, false) + return ok +} + +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 getProgramSection(kpath, false, true, true) +} + +func getExecutable(kpath string) (*sectionInfo, error) { + return getProgramSection(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 getProgramSection(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!") + } + byteCount := 0 + log.Printf("starting terminal loop....") + 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 = t.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() + + for { + one := make([]byte, 1) + _, err := t.Read(one) + if err != nil { + fatalf(t, "unable to read from device: %v", err) + return + } + _, 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 { + 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..65c5b1d --- /dev/null +++ b/rpi3/anticipationbl/Makefile @@ -0,0 +1,21 @@ +all: kernel8.img + +# note that we are building kernel8.img as well as kernel8.elf. +# kernel8.img is one that can be loaded onto an SD card and placed into your +# rasberry pi. 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. +# +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/main.go b/rpi3/anticipationbl/main.go new file mode 100644 index 0000000..81350f4 --- /dev/null +++ b/rpi3/anticipationbl/main.go @@ -0,0 +1,486 @@ +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 +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] + heapPtr := hexArgs[2] + now := hexArgs[3] + 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 +} From 8b8c4f0d39f49726b0568635d393d982ed10e0d5 Mon Sep 17 00:00:00 2001 From: Ian Smith Date: Tue, 10 Sep 2019 17:16:22 -0400 Subject: [PATCH 02/11] improved some naming --- rpi3/01_blinker/blinker.go | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/rpi3/01_blinker/blinker.go b/rpi3/01_blinker/blinker.go index d7ccff5..b03e6b9 100644 --- a/rpi3/01_blinker/blinker.go +++ b/rpi3/01_blinker/blinker.go @@ -9,30 +9,28 @@ 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(foo uint32, bar uint64) uint32 { +func blinker(_ uint32, _ uint64) uint32 { dev.LEDSet(on) on = !on + print("set LED to ", on, "\n") return interval } -// -// MAIN_QEMU hits the QEMU version of the counters directly. It won't work -// on real hardware. -// 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") + } - dev.GetRPIID() - blinkerSetup() - - freq := dev.QEMUCounterFreq() //getting my freq on + 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.QEMUSetCounterTargetInterval(interval, blinker) - dev.QEMUCore0CounterToCore0Irq() - dev.QEMUEnableCounter() + dev.SetCounterTargetInterval(interval, blinker) + dev.Core0CounterToCore0Irq() + dev.EnableCounter() dev.EnableTimerIRQ() for { dev.WaitForInterrupt() From e815d8398e93dc032682766efd34dcd999a8ec51 Mon Sep 17 00:00:00 2001 From: Ian Smith Date: Thu, 19 Sep 2019 20:14:37 -0400 Subject: [PATCH 03/11] got delays working on qemu --- rpi3/01_blinker/Makefile | 2 +- rpi3/02_delays/.gitignore | 1 + rpi3/02_delays/Makefile | 12 +++++ rpi3/02_delays/delays.go | 32 ++++++++------ rpi3/anticipation/main.go | 88 ++++++++++++++++++++++--------------- rpi3/anticipationbl/main.go | 7 ++- 6 files changed, 91 insertions(+), 51 deletions(-) create mode 100644 rpi3/02_delays/.gitignore create mode 100644 rpi3/02_delays/Makefile diff --git a/rpi3/01_blinker/Makefile b/rpi3/01_blinker/Makefile index d07000f..0b7564c 100644 --- a/rpi3/01_blinker/Makefile +++ b/rpi3/01_blinker/Makefile @@ -5,7 +5,7 @@ all: clean kernel8.img # note the target is rpi3_bl -kernel8.img: blinker.go +kernel8.img: clean tinygo build --target=rpi3_bl -o kernel8.elf . clean: diff --git a/rpi3/02_delays/.gitignore b/rpi3/02_delays/.gitignore new file mode 100644 index 0000000..d6b69a0 --- /dev/null +++ b/rpi3/02_delays/.gitignore @@ -0,0 +1 @@ +kernel8.* diff --git a/rpi3/02_delays/Makefile b/rpi3/02_delays/Makefile new file mode 100644 index 0000000..0b7564c --- /dev/null +++ b/rpi3/02_delays/Makefile @@ -0,0 +1,12 @@ +# +# Simplest bootloader example. +# + +all: clean kernel8.img + +# note the target is rpi3_bl +kernel8.img: clean + tinygo build --target=rpi3_bl -o kernel8.elf . + +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 index dfd12ff..1798c4f 100644 --- a/rpi3/02_delays/delays.go +++ b/rpi3/02_delays/delays.go @@ -2,9 +2,9 @@ package main import ( dev "device/rpi3" - "runtime/volatile" ) +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) @@ -18,28 +18,34 @@ func myHandler(current uint32, virtualTimer uint64) uint32 { return interval } -func mainForRPI3OnHardware() { - //dev.IRQVectorInitEL1(HandleIRQ) - dev.TimerInit(interval) - dev.EnableInterruptController() - dev.EnableTimerIRQ() +func main() { + dev.UART0TimeDateString(dev.Now()) + print("\n") + testDelays() + // dev.BComTimerInit(interval) + // dev.EnableInterruptController() + // dev.EnableTimerIRQ() + // + // for { + // dev.WaitForInterrupt() + // } + dev.Abort() } func testDelays() { - print("hello from main\n") - print("Waiting 1000000 CPU cycles (ARM CPU): ") - dev.WaitCycles(1000000) + print("Waiting 3000000 CPU cycles (ARM CPU): ") + dev.WaitCycles(3000000) print("OK\n") - print("Waiting 1000000 microsec (ARM CPU): ") - dev.WaitMuSec(1000000) + print("Waiting 3000000 microsec (ARM CPU): ") + dev.WaitMuSec(3000000) print("OK\n") - print("Waiting 1000000 microsec (BCM System Timer): ") + print("Waiting 3000000 microsec (BCM System Timer): ") if dev.SysTimer() == 0 { print("Not available\n") } else { - dev.WaitMuSecST(1000000) + dev.WaitMuSecST(3000000) print("OK\n") } } diff --git a/rpi3/anticipation/main.go b/rpi3/anticipation/main.go index ef3bf1c..5474ce3 100644 --- a/rpi3/anticipation/main.go +++ b/rpi3/anticipation/main.go @@ -84,7 +84,6 @@ func Main(device string, kpath string, byteLimit int) { // 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 @@ -101,10 +100,12 @@ func Main(device string, kpath string, byteLimit int) { entryPoint := execInfo.physAddr rwInfo, err = getRWData(kpath) if err != nil { - fatalf(terminal, "loading rw: %v", err) - } - if rwInfo.physAddr+rwInfo.size > endOfProgram { - endOfProgram = rwInfo.physAddr + rwInfo.size + 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 { @@ -114,6 +115,9 @@ func Main(device string, kpath string, byteLimit int) { 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) if !sendSection(terminal, info, in) { fatalf(terminal, "giving up trying to send %s", names[i]) @@ -173,9 +177,9 @@ func sanityCheck(terminal io.ReadWriter, prog *sectionInfo, in *os.File, name st if err != nil || n != len(rcvd) { fatalf(terminal, "unable to read the disk to do comparison: %v", err) } - for i := 0; i < len(rcvd) && p+uint64(i) < minSize; i++ { + for i := 0; i < len(rcvd) && uint64(i) < minSize && p+uint64(i) Date: Sun, 22 Sep 2019 10:40:50 -0400 Subject: [PATCH 04/11] added start of framebuffer --- rpi3/03_framebuffer/.gitignore | 1 + rpi3/03_framebuffer/Makefile | 12 ++++++++++++ rpi3/03_framebuffer/fb.go | 16 ++++++++++++++++ rpi3/anticipation/main.go | 31 ++++++++++++++++++++++--------- rpi3/anticipationbl/main.go | 5 ++--- 5 files changed, 53 insertions(+), 12 deletions(-) create mode 100644 rpi3/03_framebuffer/.gitignore create mode 100644 rpi3/03_framebuffer/Makefile create mode 100644 rpi3/03_framebuffer/fb.go diff --git a/rpi3/03_framebuffer/.gitignore b/rpi3/03_framebuffer/.gitignore new file mode 100644 index 0000000..d6b69a0 --- /dev/null +++ b/rpi3/03_framebuffer/.gitignore @@ -0,0 +1 @@ +kernel8.* diff --git a/rpi3/03_framebuffer/Makefile b/rpi3/03_framebuffer/Makefile new file mode 100644 index 0000000..0b7564c --- /dev/null +++ b/rpi3/03_framebuffer/Makefile @@ -0,0 +1,12 @@ +# +# Simplest bootloader example. +# + +all: clean kernel8.img + +# note the target is rpi3_bl +kernel8.img: clean + tinygo build --target=rpi3_bl -o kernel8.elf . + +clean: + rm kernel8.elf kernel8.img *.o >/dev/null 2>/dev/null || true diff --git a/rpi3/03_framebuffer/fb.go b/rpi3/03_framebuffer/fb.go new file mode 100644 index 0000000..5cb0865 --- /dev/null +++ b/rpi3/03_framebuffer/fb.go @@ -0,0 +1,16 @@ +package main + +import ( + dev "device/rpi3" +) + +func main() { + print("starting at\n") + dev.UART0TimeDateString(dev.Now()) + print("\n") + + if !dev.InitFramebuffer() { + print("unable to init framebuffer!") + } + print("done\n") +} diff --git a/rpi3/anticipation/main.go b/rpi3/anticipation/main.go index 5474ce3..f7ca1cd 100644 --- a/rpi3/anticipation/main.go +++ b/rpi3/anticipation/main.go @@ -19,7 +19,7 @@ import ( const normalSize = 32 const maxDataLineFails = 2 //this is multiplied by maxFailsOneLine for # fails on a single data line const maxFailsOneLine = 5 -const maxCommandFails = 3 // usually the other side is in a bad state when this happens +const maxCommandFails = 5 // usually the other side is in a bad state when this happens const maxPings = 25 const sanityBlockSize = 0x20 const notificationInterval = 0x800 @@ -177,9 +177,9 @@ func sanityCheck(terminal io.ReadWriter, prog *sectionInfo, in *os.File, name st 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) DATA @ 0x%04x00", current) + debugSent = false + } ok, _ := sendSingleCommand(t, payload, "DATA", maxFailsOneLine, false) + if prev { + debugSent = true + } return ok } @@ -443,6 +451,11 @@ func setupTTY(device string, cbreak bool) io.ReadWriter { if err := tty.SetRaw(); err != nil { log.Fatalf("unable to set raw on %s: %v", device, err) } + a, err := tty.Available() + if err != nil { + log.Fatalf("unable to check Available on %s: %v", device, err) + } + log.Printf("available? %d\n", a) } cleanupNeeded = append(cleanupNeeded, tty) @@ -571,7 +584,7 @@ func simpleTerminal(terminal io.ReadWriter, device string, byteLimit int) { } byteCount := 0 log.Printf("starting terminal loop....\n") -// log.Printf("hackery: %s",readLine(terminal)) + // log.Printf("hackery: %s",readLine(terminal)) if device != "/dev/tty" { k := setupTTY("/dev/tty", true) @@ -602,22 +615,22 @@ func simpleTerminal(terminal io.ReadWriter, device string, byteLimit int) { defer wg.Done() one := make([]byte, 1) - for { + for { n, err := t.Read(one) if err != nil { fatalf(t, "unable to read from device: %v", err) return } - if n==0 { + if n == 0 { fmt.Printf("read failed (no error, but no data read)\n") continue } - if one[0]==0{ + if one[0] == 0 { fmt.Printf("nul ") continue } - if one[0]<32 && one[0]!='\n'{ - fmt.Printf("[%02x]",one[0]) + if one[0] < 32 && one[0] != '\n' { + fmt.Printf("[%02x]", one[0]) continue } _, err = kbd.Write(one) diff --git a/rpi3/anticipationbl/main.go b/rpi3/anticipationbl/main.go index f01a68a..a582e5b 100644 --- a/rpi3/anticipationbl/main.go +++ b/rpi3/anticipationbl/main.go @@ -309,12 +309,11 @@ func performLaunch(length int) { return } print(confirm) -// print("xxx perform launch\n") addr := hexArgs[0] - stackPtr := hexArgs[1] - 8 //writes are from lower to higher + 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") + //print("xxx about to jump:", addr, " ", stackPtr, " ", heapPtr, " ", now, "\n") dev.AsmFull(`mov x1,{stackPtr} mov x2,{heapPtr} mov x3,{now} From 3b18413348cfb4e6413a45b01d0dc87434164f85 Mon Sep 17 00:00:00 2001 From: Ian Smith Date: Sun, 22 Sep 2019 11:38:12 -0400 Subject: [PATCH 05/11] added support for moving font into the binary in 03 --- rpi3/03_framebuffer/.gitignore | 1 + rpi3/03_framebuffer/Makefile | 11 ++++++++--- rpi3/03_framebuffer/fb.go | 18 ++++++++++++++++++ rpi3/03_framebuffer/font.psf | Bin 0 -> 2080 bytes 4 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 rpi3/03_framebuffer/font.psf diff --git a/rpi3/03_framebuffer/.gitignore b/rpi3/03_framebuffer/.gitignore index d6b69a0..af5dcde 100644 --- a/rpi3/03_framebuffer/.gitignore +++ b/rpi3/03_framebuffer/.gitignore @@ -1 +1,2 @@ kernel8.* +font.o diff --git a/rpi3/03_framebuffer/Makefile b/rpi3/03_framebuffer/Makefile index 0b7564c..317fba1 100644 --- a/rpi3/03_framebuffer/Makefile +++ b/rpi3/03_framebuffer/Makefile @@ -2,11 +2,16 @@ # Simplest bootloader example. # -all: clean kernel8.img +all: clean kernel8.elf # note the target is rpi3_bl -kernel8.img: clean - tinygo build --target=rpi3_bl -o kernel8.elf . +kernel8.elf: clean font.o + tinygo build -ldflags "-m aarch64elf font.o" --target=rpi3_bl -o kernel8.elf . + +font.o: font.psf + ld.lld -m aarch64elf -r -b binary -o font.o font.psf + +#ld.lld -m aarch64elf -nostdlib start.o font.o $(OBJS) -T link.ld -o kernel8.elf clean: rm kernel8.elf kernel8.img *.o >/dev/null 2>/dev/null || true diff --git a/rpi3/03_framebuffer/fb.go b/rpi3/03_framebuffer/fb.go index 5cb0865..af3e65c 100644 --- a/rpi3/03_framebuffer/fb.go +++ b/rpi3/03_framebuffer/fb.go @@ -2,6 +2,7 @@ package main import ( dev "device/rpi3" + "unsafe" ) func main() { @@ -12,5 +13,22 @@ func main() { if !dev.InitFramebuffer() { print("unable to init framebuffer!") } + font := (*psf)(unsafe.Pointer(&_binary_font_psf_start)) + print("magic=", font.magic, " headersize=", font.headersize, " height=", font.height, " width=", font.width, "bytesperglyph=", font.bytesperglyph, " \n") print("done\n") } + +//go:extern _binary_font_psf_start +var _binary_font_psf_start *uint8 + +type psf struct { + magic uint32 + version uint32 + headersize uint32 + flags uint32 + numglyph uint32 + bytesperglyph uint32 + height uint32 + width uint32 + glyphs *uint8 +} diff --git a/rpi3/03_framebuffer/font.psf b/rpi3/03_framebuffer/font.psf new file mode 100644 index 0000000000000000000000000000000000000000..3e67693f59d769c6453826ed9012a09818a9b9af GIT binary patch literal 2080 zcmZuyJ!>0D7#>_KR}4bsa4{l9ij>P897`AyNSU9IYDvN-#T4cWA-F-duJC{G54fL@ zbcRKSsZ2^0kr1w|CInT73$wcC{TS^A?#0^Ocb@nAWBljmHB10uHuk&-tTrh=e)ws&!OD& zIEIK5^uV;cAqPZ!>YLZE8&T)Oo2%>Vs~g29qhEiU-GXLf!8ai9;6V6SzrQL$XZt(h z&)=WF2YuY#w{qaz+VA{Y-GAtCq&H0R97LTP}vkkfS*({VNnjTr}Y#bM;bqUfqI81m+h>gY@@8Zx*QlcZQH7K zK7Ag#@Vv*zRTX1)k7G11R(<<=LH8K`*G8P>6EwU3UbcBQLcDrdc zzA+V~qku&I5fcz;;va?g%@P^5OI=X&5D09V@E~~Cy!$l!RKIJqUdq??exScisd*th zd180!kA{!Q3qIj#==c2|u4Xehy%&66Gadb4RA`6UDj_MD1UD@KOKcBz-DPA(or1q5ydEtm(tybNSpz)=EZHy?o@Kj5#f#%<( zOKRRS&!3ldDNDr-Zm_fJ^mt!+Ss!Z|r*4Yf|9RrJEz9A>qB1iZkNaxw>YA#JAvjuy z2?LPpcM4Ad;=11?6cd&p<5l=jbC0yXz^5dB8!cbqupg#mU+as}tcLwB3NykwfKV4G zLtX|z Date: Tue, 24 Sep 2019 11:06:33 -0400 Subject: [PATCH 06/11] updated framebuffer example --- rpi3/03_framebuffer/Makefile | 2 +- rpi3/03_framebuffer/README.md | 33 +++++++++++++ rpi3/03_framebuffer/fb.go | 88 +++++++++++++++++++++++++++++------ rpi3/CREDITS.md | 5 ++ rpi3/anticipation/main.go | 69 ++++++++++++++++++--------- 5 files changed, 159 insertions(+), 38 deletions(-) create mode 100644 rpi3/03_framebuffer/README.md diff --git a/rpi3/03_framebuffer/Makefile b/rpi3/03_framebuffer/Makefile index 317fba1..3ce8bd4 100644 --- a/rpi3/03_framebuffer/Makefile +++ b/rpi3/03_framebuffer/Makefile @@ -6,7 +6,7 @@ all: clean kernel8.elf # note the target is rpi3_bl kernel8.elf: clean font.o - tinygo build -ldflags "-m aarch64elf font.o" --target=rpi3_bl -o kernel8.elf . + tinygo build -ldflags font.o -no-debug --target=rpi3_bl -o kernel8.elf . font.o: font.psf ld.lld -m aarch64elf -r -b binary -o font.o font.psf 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 index af3e65c..e9e131f 100644 --- a/rpi3/03_framebuffer/fb.go +++ b/rpi3/03_framebuffer/fb.go @@ -5,30 +5,90 @@ import ( "unsafe" ) +const width = 1024 +const height = 768 + +var twoDigits = []byte{99, 99} + func main() { - print("starting at\n") 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) } - font := (*psf)(unsafe.Pointer(&_binary_font_psf_start)) - print("magic=", font.magic, " headersize=", font.headersize, " height=", font.height, " width=", font.width, "bytesperglyph=", font.bytesperglyph, " \n") - print("done\n") + + 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 -type psf struct { - magic uint32 - version uint32 - headersize uint32 - flags uint32 - numglyph uint32 - bytesperglyph uint32 - height uint32 - width uint32 - glyphs *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/CREDITS.md b/rpi3/CREDITS.md index 970007c..43cc0ce 100644 --- a/rpi3/CREDITS.md +++ b/rpi3/CREDITS.md @@ -24,3 +24,8 @@ 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/anticipation/main.go b/rpi3/anticipation/main.go index f7ca1cd..d8775e4 100644 --- a/rpi3/anticipation/main.go +++ b/rpi3/anticipation/main.go @@ -23,7 +23,7 @@ const maxCommandFails = 5 // usually the other side is in a bad state when this const maxPings = 25 const sanityBlockSize = 0x20 const notificationInterval = 0x800 -const pageSize = 0x10000 +const pageSize = 0x100000 var debugRcvd = false var debugRcvdChars = false @@ -119,7 +119,11 @@ func Main(device string, kpath string, byteLimit int) { continue } log.Printf("sending %s: 0x%04x bytes total, starts at offset 0x%04x in elf file\n", names[i], info.size, info.offset) - if !sendSection(terminal, info, in) { + 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]) @@ -186,29 +190,32 @@ func sanityCheck(terminal io.ReadWriter, prog *sectionInfo, in *os.File, name st 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 { +func sendSection(terminal io.ReadWriter, info *sectionInfo, fp *os.File) (bool, bool) { switch state { case waiting: - line := readLine(terminal) + line, ok := readLine(terminal) + if !ok { + return false, false + } if line == ready { - if sendSectionCommand(terminal) { + ok, badState := sendSectionCommand(terminal) + if ok { state = sectionSend } else { - //failed to send the command, user will see us trying a few times - return false + return false, badState } } fallthrough case sectionSend: if err := transmitSection(terminal, info, fp); err == false { - return false + return false, false } state = waiting - return true + return true, false default: fatalf(terminal, "unknown anticipation state: %d", int(state)) } - return true //should never happen + return true, false //should never happen } func cleanupAndExit() { @@ -219,10 +226,13 @@ func cleanupAndExit() { os.Exit(0) } -func readOneByte(t io.ReadWriter) byte { +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 { @@ -235,13 +245,16 @@ func readOneByte(t io.ReadWriter) byte { log.Printf("<<<< debugChar: %c", c[0]) } } - return c[0] + return c[0], true } -func readLine(t io.ReadWriter) string { +func readLine(t io.ReadWriter) (string, bool) { var buffer bytes.Buffer for { - c := readOneByte(t) + c, ok := readOneByte(t) + if !ok { + return "", false + } if c == 10 { break } @@ -254,12 +267,12 @@ func readLine(t io.ReadWriter) string { if debugRcvd { log.Printf("<----------- EMPTY LINE received!") } - return l + return l, true } if debugRcvd { log.Printf("<----------- %s", l) } - return l + return l, true } func fatalf(t io.ReadWriter, s string, args ...interface{}) { @@ -324,7 +337,10 @@ func transmitFile(t io.ReadWriter, paddr uint64, filesz uint64, fp *os.File) boo // returns the line sent in either case func confirm(t io.ReadWriter) (bool, string) { - l := readLine(t) + l, ok := readLine(t) + if !ok { + return false, "" + } if strings.HasPrefix(l, ".") { return true, l } @@ -420,6 +436,10 @@ func sendSingleCommand(t io.ReadWriter, payload string, name string, maxFails in } 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++ } @@ -451,11 +471,10 @@ func setupTTY(device string, cbreak bool) io.ReadWriter { if err := tty.SetRaw(); err != nil { log.Fatalf("unable to set raw on %s: %v", device, err) } - a, err := tty.Available() + err := tty.SetReadTimeout(time.Duration(5 * time.Second)) if err != nil { - log.Fatalf("unable to check Available on %s: %v", device, err) + log.Fatalf("unable to set read timeout on tty: %v", err) } - log.Printf("available? %d\n", a) } cleanupNeeded = append(cleanupNeeded, tty) @@ -468,9 +487,12 @@ func setupTTY(device string, cbreak bool) io.ReadWriter { return tty } -func sendSectionCommand(t io.ReadWriter) bool { - ok, _ := sendSingleCommand(t, sectionTransmit, "SECTION", maxCommandFails, false) - return ok +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 { @@ -582,6 +604,7 @@ func simpleTerminal(terminal io.ReadWriter, device string, byteLimit int) { 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)) From 8d0e285390515f67d6e9e4b6e8921b5e8b7485ef Mon Sep 17 00:00:00 2001 From: Ian Smith Date: Tue, 24 Sep 2019 12:50:01 -0400 Subject: [PATCH 07/11] updated to work with the new layout in tinygo --- rpi3/03_framebuffer/.gitignore | 2 +- rpi3/03_framebuffer/Makefile | 7 +++- rpi3/03_framebuffer/rpi3_bl.S | 69 ++++++++++++++++++++++++++++++++ rpi3/03_framebuffer/rpi3_bl.json | 4 ++ rpi3/03_framebuffer/rpi3_bl.ld | 19 +++++++++ rpi3/anticipationbl/Makefile | 8 ++-- 6 files changed, 102 insertions(+), 7 deletions(-) create mode 100644 rpi3/03_framebuffer/rpi3_bl.S create mode 100644 rpi3/03_framebuffer/rpi3_bl.json create mode 100644 rpi3/03_framebuffer/rpi3_bl.ld diff --git a/rpi3/03_framebuffer/.gitignore b/rpi3/03_framebuffer/.gitignore index af5dcde..8be6272 100644 --- a/rpi3/03_framebuffer/.gitignore +++ b/rpi3/03_framebuffer/.gitignore @@ -1,2 +1,2 @@ kernel8.* -font.o +*.o diff --git a/rpi3/03_framebuffer/Makefile b/rpi3/03_framebuffer/Makefile index 3ce8bd4..6122ac6 100644 --- a/rpi3/03_framebuffer/Makefile +++ b/rpi3/03_framebuffer/Makefile @@ -5,12 +5,15 @@ all: clean kernel8.elf # note the target is rpi3_bl -kernel8.elf: clean font.o - tinygo build -ldflags font.o -no-debug --target=rpi3_bl -o kernel8.elf . +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 + #ld.lld -m aarch64elf -nostdlib start.o font.o $(OBJS) -T link.ld -o kernel8.elf clean: 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/anticipationbl/Makefile b/rpi3/anticipationbl/Makefile index 65c5b1d..9158ab5 100644 --- a/rpi3/anticipationbl/Makefile +++ b/rpi3/anticipationbl/Makefile @@ -1,15 +1,15 @@ all: kernel8.img # note that we are building kernel8.img as well as kernel8.elf. -# kernel8.img is one that can be loaded onto an SD card and placed into your -# rasberry pi. The name of the kernel to load is kernel8.img on a RPI3 but +# 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. +# kernel8.elf is used if you want to run and use QEMU as a simulator (-M raspi3) # kernel8.img: clean - tinygo build --target=rpi3 -o kernel8.elf . + tinygo build --target=bcm2837 -o kernel8.elf . llvm-objcopy -O binary kernel8.elf kernel8.img clean: From ca2d4d9f55e63924b7999176a9d7d19247edeed3 Mon Sep 17 00:00:00 2001 From: Ian Smith Date: Tue, 24 Sep 2019 14:26:04 -0400 Subject: [PATCH 08/11] updated to new tinygo organization --- rpi3/00_simple/.gitignore | 1 + rpi3/00_simple/Makefile | 13 ++++--- rpi3/00_simple/rpi3_bl.S | 69 ++++++++++++++++++++++++++++++++++++ rpi3/00_simple/rpi3_bl.json | 4 +++ rpi3/00_simple/rpi3_bl.ld | 19 ++++++++++ rpi3/00_simple/simple.go | 4 +-- rpi3/01_blinker/.gitignore | 1 + rpi3/01_blinker/Makefile | 13 ++++--- rpi3/01_blinker/rpi3_bl.S | 69 ++++++++++++++++++++++++++++++++++++ rpi3/01_blinker/rpi3_bl.json | 4 +++ rpi3/01_blinker/rpi3_bl.ld | 19 ++++++++++ rpi3/02_delays/.gitignore | 1 + rpi3/02_delays/Makefile | 13 ++++--- rpi3/02_delays/rpi3_bl.S | 69 ++++++++++++++++++++++++++++++++++++ rpi3/02_delays/rpi3_bl.json | 4 +++ rpi3/02_delays/rpi3_bl.ld | 19 ++++++++++ rpi3/03_framebuffer/Makefile | 2 -- rpi3/anticipation/main.go | 21 +++++------ 18 files changed, 316 insertions(+), 29 deletions(-) create mode 100644 rpi3/00_simple/rpi3_bl.S create mode 100644 rpi3/00_simple/rpi3_bl.json create mode 100644 rpi3/00_simple/rpi3_bl.ld create mode 100644 rpi3/01_blinker/rpi3_bl.S create mode 100644 rpi3/01_blinker/rpi3_bl.json create mode 100644 rpi3/01_blinker/rpi3_bl.ld create mode 100644 rpi3/02_delays/rpi3_bl.S create mode 100644 rpi3/02_delays/rpi3_bl.json create mode 100644 rpi3/02_delays/rpi3_bl.ld diff --git a/rpi3/00_simple/.gitignore b/rpi3/00_simple/.gitignore index 0bb33b0..8bb9984 100644 --- a/rpi3/00_simple/.gitignore +++ b/rpi3/00_simple/.gitignore @@ -1,2 +1,3 @@ kernel8.* .gdbinit +*.o diff --git a/rpi3/00_simple/Makefile b/rpi3/00_simple/Makefile index 7f740c0..0dae26e 100644 --- a/rpi3/00_simple/Makefile +++ b/rpi3/00_simple/Makefile @@ -1,12 +1,15 @@ # -# Simplest bootloader example. +# Simplest bootloader example (prints hello world on serial port, UART0, then echos what you type at it) # -all: clean kernel8.img +all: clean kernel8.elf -# note the target is rpi3_bl -kernel8.img: simple.go - tinygo build --target=rpi3_bl -o 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/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 index d1829d7..718c845 100644 --- a/rpi3/00_simple/simple.go +++ b/rpi3/00_simple/simple.go @@ -4,8 +4,8 @@ import ( dev "device/rpi3" ) -func main() { - print("echoing what you type\n") +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 index d6b69a0..8be6272 100644 --- a/rpi3/01_blinker/.gitignore +++ b/rpi3/01_blinker/.gitignore @@ -1 +1,2 @@ kernel8.* +*.o diff --git a/rpi3/01_blinker/Makefile b/rpi3/01_blinker/Makefile index 0b7564c..73656c4 100644 --- a/rpi3/01_blinker/Makefile +++ b/rpi3/01_blinker/Makefile @@ -1,12 +1,15 @@ # -# Simplest bootloader example. +# Use interrupts to blink the light on the RPI3. # -all: clean kernel8.img +all: clean kernel8.elf -# note the target is rpi3_bl -kernel8.img: clean - tinygo build --target=rpi3_bl -o 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/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 index d6b69a0..8be6272 100644 --- a/rpi3/02_delays/.gitignore +++ b/rpi3/02_delays/.gitignore @@ -1 +1,2 @@ kernel8.* +*.o diff --git a/rpi3/02_delays/Makefile b/rpi3/02_delays/Makefile index 0b7564c..606c15a 100644 --- a/rpi3/02_delays/Makefile +++ b/rpi3/02_delays/Makefile @@ -1,12 +1,15 @@ # -# Simplest bootloader example. +# Use the bootloader to test our delays. # -all: clean kernel8.img +all: clean kernel8.elf -# note the target is rpi3_bl -kernel8.img: clean - tinygo build --target=rpi3_bl -o 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/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/Makefile b/rpi3/03_framebuffer/Makefile index 6122ac6..5473352 100644 --- a/rpi3/03_framebuffer/Makefile +++ b/rpi3/03_framebuffer/Makefile @@ -14,7 +14,5 @@ font.o: font.psf rpi3_bl.o: rpi3_bl.S clang --target=aarch64-elf -c rpi3_bl.S -#ld.lld -m aarch64elf -nostdlib start.o font.o $(OBJS) -T link.ld -o kernel8.elf - clean: rm kernel8.elf kernel8.img *.o >/dev/null 2>/dev/null || true diff --git a/rpi3/anticipation/main.go b/rpi3/anticipation/main.go index d8775e4..746a6c9 100644 --- a/rpi3/anticipation/main.go +++ b/rpi3/anticipation/main.go @@ -183,7 +183,8 @@ func sanityCheck(terminal io.ReadWriter, prog *sectionInfo, in *os.File, name st } for i := 0; i < len(rcvd) && uint64(i) < minSize && p+uint64(i) < prog.physAddr+minSize; i++ { if rcvd[i] != onDisk[i] { - fatalf(terminal, "byte mismatch found at %04x: %x expected but got %x (iteration %d, minsize %d, %d vs %d)", int(p)+i, onDisk[i], rcvd[i], i, minSize, len(onDisk), len(rcvd)) + 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) } } } @@ -331,7 +332,7 @@ func transmitFile(t io.ReadWriter, paddr uint64, filesz uint64, fp *os.File) boo if !sendHexEOF(t) { return false } - log.Printf("completed sending file... %04x bytes", filesz) + log.Printf("completed sending file: 0x%04x bytes", filesz) return true } @@ -399,15 +400,15 @@ func sendHexData(t io.ReadWriter, size int, current uint64, buffer []byte) bool for i := 0; i < size; i++ { payload += fmt.Sprintf("%02x", buffer[i]) } - prev := debugSent - if prev { - log.Printf("------------> DATA @ 0x%04x00", current) - debugSent = false - } + // prev := debugSent + // if prev { + // log.Printf("------------> DATA @ 0x%04x00", current) + // debugSent = false + // } ok, _ := sendSingleCommand(t, payload, "DATA", maxFailsOneLine, false) - if prev { - debugSent = true - } + // if prev { + // debugSent = true + // } return ok } From f4771ff61ba743422964989b149476a7d5756874 Mon Sep 17 00:00:00 2001 From: Ian Smith Date: Tue, 24 Sep 2019 14:32:52 -0400 Subject: [PATCH 09/11] updated to reflect new organization --- rpi3/README.md | 5 ++++- rpi3/anticipationbl/Makefile | 5 ++++- rpi3/anticipationbl/TODO.md | 10 ++++++++++ 3 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 rpi3/anticipationbl/TODO.md diff --git a/rpi3/README.md b/rpi3/README.md index 645e553..e85666e 100644 --- a/rpi3/README.md +++ b/rpi3/README.md @@ -2,7 +2,7 @@ ## Requirements You have to have three things in your PATH. A copy of tinygo (that includes -the rpi3 and rpi3_bl devices), a "normal" copy of go of at least version 1.11, +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 @@ -40,6 +40,9 @@ interrupt to blink the light. 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 diff --git a/rpi3/anticipationbl/Makefile b/rpi3/anticipationbl/Makefile index 9158ab5..2f79bcc 100644 --- a/rpi3/anticipationbl/Makefile +++ b/rpi3/anticipationbl/Makefile @@ -8,8 +8,11 @@ all: kernel8.img # # 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=bcm2837 -o kernel8.elf . + tinygo build --target=rpi3 -o kernel8.elf . llvm-objcopy -O binary kernel8.elf kernel8.img clean: 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. From 9511a935027f0e375129df7ac459b18039c570a6 Mon Sep 17 00:00:00 2001 From: Ian Smith Date: Tue, 24 Sep 2019 14:38:34 -0400 Subject: [PATCH 10/11] updated README --- rpi3/README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/rpi3/README.md b/rpi3/README.md index e85666e..2fc8bf2 100644 --- a/rpi3/README.md +++ b/rpi3/README.md @@ -23,6 +23,18 @@ 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 From d72474955be9b4035b42962dc450d7e056d9ea27 Mon Sep 17 00:00:00 2001 From: Ian Smith Date: Tue, 24 Sep 2019 17:24:29 -0400 Subject: [PATCH 11/11] removed unneeded quote --- rpi3/03_framebuffer/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rpi3/03_framebuffer/Makefile b/rpi3/03_framebuffer/Makefile index 5473352..c237da9 100644 --- a/rpi3/03_framebuffer/Makefile +++ b/rpi3/03_framebuffer/Makefile @@ -6,7 +6,7 @@ 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 . + 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