Extend JTAG tutorial
parent
59c6c15c1d
commit
28cf26a28a
@ -0,0 +1,98 @@
|
||||
#
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2018-2019 Andre Richter <andre.o.richter@gmail.com>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
#
|
||||
|
||||
TARGET = aarch64-unknown-none
|
||||
|
||||
SOURCES = $(wildcard **/*.rs) $(wildcard **/*.S) link.ld
|
||||
|
||||
|
||||
XRUSTC_CMD = cargo xrustc --target=$(TARGET) --release
|
||||
CARGO_OUTPUT = target/$(TARGET)/release/kernel8
|
||||
|
||||
OBJCOPY = cargo objcopy --
|
||||
OBJCOPY_PARAMS = --strip-all -O binary
|
||||
|
||||
CONTAINER_UTILS = andrerichter/raspi3-utils
|
||||
CONTAINER_OPENOCD = andrerichter/raspi3-openocd
|
||||
CONTAINER_GDB = andrerichter/raspi3-gdb
|
||||
|
||||
DOCKER_CMD = docker run -it --rm
|
||||
DOCKER_ARG_CURDIR = -v $(shell pwd):/work -w /work
|
||||
DOCKER_ARG_TTY = --privileged -v /dev:/dev
|
||||
DOCKER_ARG_JTAG = -v $(shell pwd)/../X1_JTAG_boot:/jtag
|
||||
DOCKER_ARG_NET = --network host
|
||||
|
||||
DOCKER_EXEC_QEMU = qemu-system-aarch64 -M raspi3 -kernel kernel8.img
|
||||
DOCKER_EXEC_RASPBOOT = raspbootcom /dev/ttyUSB0
|
||||
|
||||
.PHONY: all qemu raspboot clippy clean objdump nm jtag
|
||||
|
||||
all: clean kernel8.img
|
||||
|
||||
$(CARGO_OUTPUT): $(SOURCES)
|
||||
$(XRUSTC_CMD)
|
||||
|
||||
kernel8.img: $(CARGO_OUTPUT)
|
||||
cp $< .
|
||||
$(OBJCOPY) $(OBJCOPY_PARAMS) $< kernel8.img
|
||||
|
||||
qemu: all
|
||||
$(DOCKER_CMD) $(DOCKER_ARG_CURDIR) $(CONTAINER_UTILS) \
|
||||
$(DOCKER_EXEC_QEMU) -serial stdio
|
||||
|
||||
raspboot: all
|
||||
$(DOCKER_CMD) $(DOCKER_ARG_CURDIR) $(DOCKER_ARG_TTY) \
|
||||
$(CONTAINER_UTILS) $(DOCKER_EXEC_RASPBOOT) kernel8.img
|
||||
|
||||
clippy:
|
||||
cargo xclippy --target=$(TARGET)
|
||||
|
||||
clean:
|
||||
cargo clean
|
||||
|
||||
objdump:
|
||||
cargo objdump --target $(TARGET) -- -disassemble -print-imm-hex kernel8
|
||||
|
||||
nm:
|
||||
cargo nm --target $(TARGET) -- kernel8 | sort
|
||||
|
||||
jtagboot:
|
||||
$(DOCKER_CMD) $(DOCKER_ARG_TTY) $(DOCKER_ARG_JTAG) $(CONTAINER_UTILS) \
|
||||
$(DOCKER_EXEC_RASPBOOT) /jtag/jtag_boot.img
|
||||
|
||||
openocd:
|
||||
$(DOCKER_CMD) $(DOCKER_ARG_TTY) $(DOCKER_ARG_NET) $(CONTAINER_OPENOCD)
|
||||
|
||||
define gen_gdb
|
||||
$(XRUSTC_CMD) -- $1
|
||||
cp $(CARGO_OUTPUT) kernel8_for_jtag
|
||||
$(DOCKER_CMD) $(DOCKER_ARG_CURDIR) $(DOCKER_ARG_NET) $(CONTAINER_GDB) \
|
||||
gdb-multiarch -q kernel8_for_jtag
|
||||
endef
|
||||
|
||||
gdb: clean $(SOURCES)
|
||||
$(call gen_gdb,-C debuginfo=2)
|
||||
|
||||
gdb-opt0: clean $(SOURCES)
|
||||
$(call gen_gdb,-C debuginfo=2 -C opt-level=0)
|
@ -0,0 +1,260 @@
|
||||
# Tutorial 0B - Hardware Debugging using JTAG
|
||||
|
||||
In the upcoming tutorials, we are going to touch sensitive areas of the RPi's
|
||||
SoC that can make our debugging life very hard. For example, changing the
|
||||
processor's `Exception Level` or introducing `virtual memory`.
|
||||
|
||||
A hardware based debugger can sometimes be the last resort when searching for a
|
||||
tricky bug. Especially for debugging intricate, architecture-specific HW issues,
|
||||
it will be handy, because in this area `QEMU` sometimes can not help, because it
|
||||
abstracts certain features of our RPi's HW and doesn't simulate down to the very
|
||||
last bit.
|
||||
|
||||
So lets introduce `JTAG` debugging. Once set up, it will allow us to single-step
|
||||
through our kernel on the real HW. How cool is that?!
|
||||
|
||||
![JTAG live demo](../doc/jtag_demo.gif)
|
||||
|
||||
## Hardware
|
||||
|
||||
Unlike microcontroller boards like the `STM32F3DISCOVERY`, which is used in our
|
||||
WG's [Embedded Rust Book](https://rust-embedded.github.io/book/start/hardware.html),
|
||||
the RPi does not have an embedded debugger on it's board. Hence, you need to buy one.
|
||||
|
||||
For this tutorial, we will use the
|
||||
[ARM-USB-TINY-H](https://www.olimex.com/Products/ARM/JTAG/ARM-USB-TINY-H/) by
|
||||
OLIMEX. It has a standard
|
||||
[ARM JTAG 20 connector](http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0499dj/BEHEIHCE.html).
|
||||
Unfortunately, the RPi does not, so we have to connect it via jumper wires.
|
||||
|
||||
### Wiring
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>GPIO #</th>
|
||||
<th>Name</th>
|
||||
<th>JTAG #</th>
|
||||
<th>Note</th>
|
||||
<th width="60%">Diagram</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>VTREF</td>
|
||||
<td>1</td>
|
||||
<td>to 3.3V</td>
|
||||
<td rowspan="8"><img src="../doc/wiring_jtag.png"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>GND</td>
|
||||
<td>4</td>
|
||||
<td>to GND</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>22</td>
|
||||
<td>TRST</td>
|
||||
<td>3</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>26</td>
|
||||
<td>TDI</td>
|
||||
<td>5</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>27</td>
|
||||
<td>TMS</td>
|
||||
<td>7</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>25</td>
|
||||
<td>TCK</td>
|
||||
<td>9</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>23</td>
|
||||
<td>RTCK</td>
|
||||
<td>11</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>24</td>
|
||||
<td>TDO</td>
|
||||
<td>13</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p align="center"><img src="../doc/image_jtag_connected.jpg" width="50%"></p>
|
||||
|
||||
## Configuring GPIO for JTAG
|
||||
|
||||
Before it is possible to connect, we additionally have to configure the
|
||||
respective GPIO pins for `JTAG` functionality from software. Our approach is as
|
||||
allows:
|
||||
|
||||
Via `raspboot`, we load a tiny helper binary onto the RPi which configures the
|
||||
pins respectively and then parks the executing core in an endless loop, waiting
|
||||
for the `JTAG` debugger to connect. The helper binary is maintained separately
|
||||
in this repository's [X1_JTAG_boot](../X1_JTAG_boot) folder, and is a
|
||||
stripped-down version of the code we use in our tutorials.
|
||||
|
||||
This functionality is provided by the new `Makefile` target `make jtagboot`.
|
||||
|
||||
```console
|
||||
ferris@box:~$ make jtagboot
|
||||
Raspbootcom V1.0
|
||||
### Listening on /dev/ttyUSB0
|
||||
RBIN64
|
||||
### sending kernel /jtag/jtag_boot.img [759 byte]
|
||||
### finished sending
|
||||
|
||||
|
||||
[i] JTAG is live. Please connect.
|
||||
```
|
||||
|
||||
It is important to keep the USB serial connected and the terminal open with
|
||||
`raspboot` running. When we load the actual kernel later, `UART` output will
|
||||
appear here.
|
||||
|
||||
## OpenOCD
|
||||
|
||||
Next, we need to launch the [Open On-Chip Debugger](http://openocd.org/), aka
|
||||
`OpenOCD` to actually connect the `JTAG`.
|
||||
|
||||
As always, our tutorials try to be as painless as possible regarding dev-tools,
|
||||
which is why we have packaged everything into a [dedicated Docker container](../docker/raspi3-openocd) that will be provisioned automagically on the first run.
|
||||
|
||||
|
||||
|
||||
Now connect the Olimex USB JTAG debugger, open a new terminal and in the same
|
||||
folder, type `make openocd` (in that order!). You will see some initial output:
|
||||
|
||||
```console
|
||||
ferris@box:~$ make openocd
|
||||
Open On-Chip Debugger 0.10.0+dev-ge243075 (2019-03-07-19:07)
|
||||
Licensed under GNU GPL v2
|
||||
For bug reports, read
|
||||
http://openocd.org/doc/doxygen/bugs.html
|
||||
trst_and_srst separate srst_gates_jtag trst_push_pull srst_open_drain connect_deassert_srst
|
||||
adapter speed: 1000 kHz
|
||||
jtag_ntrst_delay: 500
|
||||
Info : Listening on port 6666 for tcl connections
|
||||
Info : Listening on port 4444 for telnet connections
|
||||
Info : clock speed 1000 kHz
|
||||
Info : JTAG tap: rpi3.tap tap/device found: 0x4ba00477 (mfg: 0x23b (ARM Ltd.), part: 0xba00, ver: 0x4)
|
||||
Info : rpi3.core0: hardware has 6 breakpoints, 4 watchpoints
|
||||
Info : rpi3.core1: hardware has 6 breakpoints, 4 watchpoints
|
||||
Info : rpi3.core2: hardware has 6 breakpoints, 4 watchpoints
|
||||
Info : rpi3.core3: hardware has 6 breakpoints, 4 watchpoints
|
||||
Info : Listening on port 3333 for gdb connections
|
||||
Info : Listening on port 3334 for gdb connections
|
||||
Info : Listening on port 3335 for gdb connections
|
||||
Info : Listening on port 3336 for gdb connections
|
||||
```
|
||||
|
||||
`OpenOCD` has detected the four cores of the RPi, and opened four network ports
|
||||
to which `gdb` can now connect to debug the respective core.
|
||||
|
||||
## GDB
|
||||
|
||||
Finally, we need an `AArch64`-capable version of `gdb`. You guessed right, we
|
||||
packaged [another container for you](../docker/raspi3-gdb). It can be launched
|
||||
via `make gdb`.
|
||||
|
||||
This Makefile target actually does a little more. It builds a special version of
|
||||
our kernel with debug information included. This enables `gdb` to show the `Rust`
|
||||
source code line we are currently debugging. It also launches `gdb` such
|
||||
that it already loads this debug build (`kernel8_for_jtag`).
|
||||
|
||||
We can now use the `gdb` commandline to
|
||||
1. Set breakpoints in our kernel
|
||||
2. Load the kernel via JTAG in memory (remember that currently, the RPi is still executing
|
||||
the minimal JTAG pin enablement binary).
|
||||
3. Manipulate the program counter of the RPi to start execution at our kernel's entry point.
|
||||
4. Single-step through its execution.
|
||||
|
||||
```shell
|
||||
>>> break _boot_cores
|
||||
Breakpoint 1 at 0x80000
|
||||
>>> target remote :3333 # Connect to OpenOCD, raspi3.core0
|
||||
>>> load kernel8_for_jtag # Load the kernel into the Raspi's DRAM over JTAG.
|
||||
Loading section .text, size 0x6cc lma 0x80000
|
||||
Loading section .rodata, size 0x9a lma 0x806cc
|
||||
Start address 0x80000, load size 1894
|
||||
Transfer rate: 66 KB/sec, 947 bytes/write.
|
||||
>>> set $pc = 0x80000 # Set RPI's program counter to the start of the kernel binary.
|
||||
>>> cont
|
||||
Breakpoint 1, 0x0000000000080000 in _boot_cores ()
|
||||
>>> step
|
||||
>>> step # Single-step through the kernel
|
||||
>>> ...
|
||||
```
|
||||
|
||||
### Remarks
|
||||
|
||||
#### Optimization
|
||||
|
||||
When debugging an OS binary, you have to make a trade-off between the
|
||||
granularity at which you can step through your Rust source-code and the
|
||||
optimization level of the generated binary. The `make` and `make gdb` targets
|
||||
produce a `--release` binary, which includes an optimization level of three
|
||||
(`-opt-level=3`). However, in this case, the compiler will inline very
|
||||
aggressively and pack together reads and writes where possible. As a result, it
|
||||
will not always be possible to hit breakpoints exactly where you want to
|
||||
regarding the line of source code file.
|
||||
|
||||
For this reason, the Makefile also provides the `make gdb-opt0` target, which
|
||||
uses `-opt-level=0`. Hence, it will allow you to have finer debugging
|
||||
granularity. However, please keep in mind that when debugging code that closely
|
||||
deals with HW, a compiler optimization that squashes reads or writes to volatile
|
||||
registers can make all the difference in execution. FYI, the demo gif above has
|
||||
been recorded with `gdb-opt0`.
|
||||
|
||||
#### GDB control
|
||||
|
||||
At some point, you may reach delay loops or code that waits on user input from
|
||||
the serial. Here, single stepping might not be feasible or work anymore. You can
|
||||
jump over these roadblocks by setting other breakpoints beyond these areas, and
|
||||
reach them using the `cont` command.
|
||||
|
||||
Pressing `ctrl+c` in `gdb` will stop execution of the RPi again in case you
|
||||
continued it without further breakpoints.
|
||||
|
||||
## Notes on USB connection constraints
|
||||
|
||||
If you followed the tutorial from top to bottom, everything should be fine
|
||||
regarding USB connections.
|
||||
|
||||
Still, please note that in its current form, our `Makefile` makes implicit
|
||||
assumptions about the naming of the connected USB devices. It expects
|
||||
`/dev/ttyUSB0` to be the `UART` device.
|
||||
|
||||
Hence, please ensure the following order of connecting the devices to your box:
|
||||
1. Connect the USB serial.
|
||||
2. Afterwards, the Olimex debugger.
|
||||
|
||||
This way, Linux enumerates the devices accordingly. This has to be done only
|
||||
once. It is fine to disconnect and connect the serial multiple times, e.g. for
|
||||
kicking off different `make jtagboot` runs, while keeping the debugger
|
||||
connected.
|
||||
|
||||
## In summary
|
||||
|
||||
1. `make jtagboot` and keep terminal open.
|
||||
2. Connect USB serial device.
|
||||
3. Connect `JTAG` debugger USB device.
|
||||
4. In new terminal, `make openocd`.
|
||||
5. In new terminal, `make gdb` or make `make gdb-opt0`.
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
Thanks to [@naotaco](https://github.com/naotaco) for laying the groundwork for
|
||||
this tutorial.
|
Binary file not shown.
Binary file not shown.
@ -0,0 +1,121 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2018-2019 Andre Richter <andre.o.richter@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
use super::MMIO_BASE;
|
||||
use core::ops;
|
||||
use register::{mmio::ReadWrite, register_bitfields};
|
||||
|
||||
// Descriptions taken from
|
||||
// https://github.com/raspberrypi/documentation/files/1888662/BCM2837-ARM-Peripherals.-.Revised.-.V2-1.pdf
|
||||
register_bitfields! {
|
||||
u32,
|
||||
|
||||
/// GPIO Function Select 1
|
||||
GPFSEL1 [
|
||||
/// Pin 15
|
||||
FSEL15 OFFSET(15) NUMBITS(3) [
|
||||
Input = 0b000,
|
||||
Output = 0b001,
|
||||
RXD0 = 0b100, // UART0 - Alternate function 0
|
||||
RXD1 = 0b010 // Mini UART - Alternate function 5
|
||||
|
||||
],
|
||||
|
||||
/// Pin 14
|
||||
FSEL14 OFFSET(12) NUMBITS(3) [
|
||||
Input = 0b000,
|
||||
Output = 0b001,
|
||||
TXD0 = 0b100, // UART0 - Alternate function 0
|
||||
TXD1 = 0b010 // Mini UART - Alternate function 5
|
||||
]
|
||||
],
|
||||
|
||||
/// GPIO Pull-up/down Clock Register 0
|
||||
GPPUDCLK0 [
|
||||
/// Pin 15
|
||||
PUDCLK15 OFFSET(15) NUMBITS(1) [
|
||||
NoEffect = 0,
|
||||
AssertClock = 1
|
||||
],
|
||||
|
||||
/// Pin 14
|
||||
PUDCLK14 OFFSET(14) NUMBITS(1) [
|
||||
NoEffect = 0,
|
||||
AssertClock = 1
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
const GPIO_BASE: u32 = MMIO_BASE + 0x200_000;
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[repr(C)]
|
||||
pub struct RegisterBlock {
|
||||
pub GPFSEL0: ReadWrite<u32>, // 0x00
|
||||
pub GPFSEL1: ReadWrite<u32, GPFSEL1::Register>, // 0x04
|
||||
pub GPFSEL2: ReadWrite<u32>, // 0x08
|
||||
pub GPFSEL3: ReadWrite<u32>, // 0x0C
|
||||
pub GPFSEL4: ReadWrite<u32>, // 0x10
|
||||
pub GPFSEL5: ReadWrite<u32>, // 0x14
|
||||
__reserved_0: u32, // 0x18
|
||||
GPSET0: ReadWrite<u32>, // 0x1C
|
||||
GPSET1: ReadWrite<u32>, // 0x20
|
||||
__reserved_1: u32, //
|
||||
GPCLR0: ReadWrite<u32>, // 0x28
|
||||
__reserved_2: [u32; 2], //
|
||||
GPLEV0: ReadWrite<u32>, // 0x34
|
||||
GPLEV1: ReadWrite<u32>, // 0x38
|
||||
__reserved_3: u32, //
|
||||
GPEDS0: ReadWrite<u32>, // 0x40
|
||||
GPEDS1: ReadWrite<u32>, // 0x44
|
||||
__reserved_4: [u32; 7], //
|
||||
GPHEN0: ReadWrite<u32>, // 0x64
|
||||
GPHEN1: ReadWrite<u32>, // 0x68
|
||||
__reserved_5: [u32; 10], //
|
||||
pub GPPUD: ReadWrite<u32>, // 0x94
|
||||
pub GPPUDCLK0: ReadWrite<u32, GPPUDCLK0::Register>, // 0x98
|
||||
pub GPPUDCLK1: ReadWrite<u32>, // 0x9C
|
||||
}
|
||||
|
||||
/// Public interface to the GPIO MMIO area
|
||||
pub struct GPIO;
|
||||
|
||||
impl ops::Deref for GPIO {
|
||||
type Target = RegisterBlock;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
unsafe { &*Self::ptr() }
|
||||
}
|
||||
}
|
||||
|
||||
impl GPIO {
|
||||
pub fn new() -> GPIO {
|
||||
GPIO
|
||||
}
|
||||
|
||||
/// Returns a pointer to the register block
|
||||
fn ptr() -> *const RegisterBlock {
|
||||
GPIO_BASE as *const _
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2018-2019 Andre Richter <andre.o.richter@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
const MMIO_BASE: u32 = 0x3F00_0000;
|
||||
|
||||
mod delays;
|
||||
mod gpio;
|
||||
mod mbox;
|
||||
mod power;
|
||||
mod uart;
|
||||
|
||||
fn kernel_entry() -> ! {
|
||||
let gpio = gpio::GPIO::new();
|
||||
let mut mbox = mbox::Mbox::new();
|
||||
let uart = uart::Uart::new();
|
||||
let power = power::Power::new();
|
||||
|
||||
// set up serial console
|
||||
match uart.init(&mut mbox, &gpio) {
|
||||
Ok(_) => uart.puts("\n[0] UART is live!\n"),
|
||||
Err(_) => loop {
|
||||
cortex_a::asm::wfe() // If UART fails, abort early
|
||||
},
|
||||
}
|
||||
|
||||
uart.puts("[1] Press a key to continue booting... ");
|
||||
uart.getc();
|
||||
uart.puts("Greetings fellow Rustacean!\n");
|
||||
|
||||
loop {
|
||||
uart.puts("\n 1 - power off\n 2 - reset\nChoose one: ");
|
||||
let c = uart.getc();
|
||||
uart.send(c);
|
||||
|
||||
match c {
|
||||
'1' => {
|
||||
if power.off(&mut mbox, &gpio).is_err() {
|
||||
uart.puts("Mailbox error in Power::off()");
|
||||
}
|
||||
}
|
||||
'2' => power.reset(),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
raspi3_boot::entry!(kernel_entry);
|
@ -0,0 +1,163 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2018-2019 Andre Richter <andre.o.richter@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
use super::MMIO_BASE;
|
||||
use core::ops;
|
||||
use cortex_a::asm;
|
||||
use register::{
|
||||
mmio::{ReadOnly, WriteOnly},
|
||||
register_bitfields,
|
||||
};
|
||||
|
||||
register_bitfields! {
|
||||
u32,
|
||||
|
||||
STATUS [
|
||||
FULL OFFSET(31) NUMBITS(1) [],
|
||||
EMPTY OFFSET(30) NUMBITS(1) []
|
||||
]
|
||||
}
|
||||
|
||||
const VIDEOCORE_MBOX: u32 = MMIO_BASE + 0xB880;
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[repr(C)]
|
||||
pub struct RegisterBlock {
|
||||
READ: ReadOnly<u32>, // 0x00
|
||||
__reserved_0: [u32; 5], // 0x04
|
||||
STATUS: ReadOnly<u32, STATUS::Register>, // 0x18
|
||||
__reserved_1: u32, // 0x1C
|
||||
WRITE: WriteOnly<u32>, // 0x20
|
||||
}
|
||||
|
||||
// Custom errors
|
||||
pub enum MboxError {
|
||||
ResponseError,
|
||||
UnknownError,
|
||||
}
|
||||
pub type Result<T> = ::core::result::Result<T, MboxError>;
|
||||
|
||||
// Channels
|
||||
pub mod channel {
|
||||
pub const PROP: u32 = 8;
|
||||
}
|
||||
|
||||
// Tags
|
||||
pub mod tag {
|
||||
pub const SETPOWER: u32 = 0x28001;
|
||||
pub const SETCLKRATE: u32 = 0x38002;
|
||||
pub const LAST: u32 = 0;
|
||||
}
|
||||
|
||||
// Clocks
|
||||
pub mod clock {
|
||||
pub const UART: u32 = 0x0_0000_0002;
|
||||
}
|
||||
|
||||
// Responses
|
||||
mod response {
|
||||
pub const SUCCESS: u32 = 0x8000_0000;
|
||||
pub const ERROR: u32 = 0x8000_0001; // error parsing request buffer (partial response)
|
||||
}
|
||||
|
||||
pub const REQUEST: u32 = 0;
|
||||
|
||||
// Public interface to the mailbox
|
||||
#[repr(C)]
|
||||
#[repr(align(16))]
|
||||
pub struct Mbox {
|
||||
// The address for buffer needs to be 16-byte aligned so that the
|
||||
// Videcore can handle it properly.
|
||||
pub buffer: [u32; 36],
|
||||
}
|
||||
|
||||
/// Deref to RegisterBlock
|
||||
///
|
||||
/// Allows writing
|
||||
/// ```
|
||||
/// self.STATUS.read()
|
||||
/// ```
|
||||
/// instead of something along the lines of
|
||||
/// ```
|
||||
/// unsafe { (*Mbox::ptr()).STATUS.read() }
|
||||
/// ```
|
||||
impl ops::Deref for Mbox {
|
||||
type Target = RegisterBlock;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
unsafe { &*Self::ptr() }
|
||||
}
|
||||
}
|
||||
|
||||
impl Mbox {
|
||||
pub fn new() -> Mbox {
|
||||
Mbox { buffer: [0; 36] }
|
||||
}
|
||||
|
||||
/// Returns a pointer to the register block
|
||||
fn ptr() -> *const RegisterBlock {
|
||||
VIDEOCORE_MBOX as *const _
|
||||
}
|
||||
|
||||
/// Make a mailbox call. Returns Err(MboxError) on failure, Ok(()) success
|
||||
pub fn call(&self, channel: u32) -> Result<()> {
|
||||
// wait until we can write to the mailbox
|
||||
loop {
|
||||
if !self.STATUS.is_set(STATUS::FULL) {
|
||||
break;
|
||||
}
|
||||
|
||||
asm::nop();
|
||||
}
|
||||
|
||||
let buf_ptr = self.buffer.as_ptr() as u32;
|
||||
|
||||
// write the address of our message to the mailbox with channel identifier
|
||||
self.WRITE.set((buf_ptr & !0xF) | (channel & 0xF));
|
||||
|
||||
// now wait for the response
|
||||
loop {
|
||||
// is there a response?
|
||||
loop {
|
||||
if !self.STATUS.is_set(STATUS::EMPTY) {
|
||||
break;
|
||||
}
|
||||
|
||||
asm::nop();
|
||||
}
|
||||
|
||||
let resp: u32 = self.READ.get();
|
||||
|
||||
// is it a response to our message?
|
||||
if ((resp & 0xF) == channel) && ((resp & !0xF) == buf_ptr) {
|
||||
// is it a valid successful response?
|
||||
return match self.buffer[1] {
|
||||
response::SUCCESS => Ok(()),
|
||||
response::ERROR => Err(MboxError::ResponseError),
|
||||
_ => Err(MboxError::UnknownError),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,143 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2018-2019 Andre Richter <andre.o.richter@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
use super::MMIO_BASE;
|
||||
use crate::delays;
|
||||
use crate::gpio;
|
||||
use crate::mbox;
|
||||
use core::ops;
|
||||
use core::sync::atomic::{compiler_fence, Ordering};
|
||||
use register::mmio::*;
|
||||
|
||||
const POWER_BASE: u32 = MMIO_BASE + 0x100_01C;
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[repr(C)]
|
||||
pub struct RegisterBlock {
|
||||
PM_RSTC: ReadWrite<u32>, // 0x1C
|
||||
PM_RSTS: ReadWrite<u32>, // 0x20
|
||||
PM_WDOG: ReadWrite<u32>, // 0x24
|
||||
}
|
||||
|
||||
const PM_PASSWORD: u32 = 0x5a_000_000;
|
||||
const PM_RSTC_WRCFG_CLR: u32 = 0xffff_ffcf;
|
||||
const PM_RSTC_WRCFG_FULL_RESET: u32 = 0x0000_0020;
|
||||
|
||||
// The Raspberry Pi firmware uses the RSTS register to know which
|
||||
// partition to boot from. The partition value is spread into bits 0, 2,
|
||||
// 4, 6, 8, 10. Partition 63 is a special partition used by the
|
||||
// firmware to indicate halt.
|
||||
const PM_RSTS_RASPBERRYPI_HALT: u32 = 0x555;
|
||||
|
||||
pub enum PowerError {
|
||||
MailboxError,
|
||||
}
|
||||
pub type Result<T> = ::core::result::Result<T, PowerError>;
|
||||
|
||||
/// Public interface to the Power subsystem
|
||||
pub struct Power;
|
||||
|
||||
impl ops::Deref for Power {
|
||||
type Target = RegisterBlock;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
unsafe { &*Self::ptr() }
|
||||
}
|
||||
}
|
||||
|
||||
impl Power {
|
||||
pub fn new() -> Power {
|
||||
Power
|
||||
}
|
||||
|
||||
/// Returns a pointer to the register block
|
||||
fn ptr() -> *const RegisterBlock {
|
||||
POWER_BASE as *const _
|
||||
}
|
||||
|
||||
/// Shutdown the board
|
||||
pub fn off(&self, mbox: &mut mbox::Mbox, gpio: &gpio::GPIO) -> Result<()> {
|
||||
// power off devices one by one
|
||||
for dev_id in 0..16 {
|
||||
mbox.buffer[0] = 8 * 4;
|
||||
mbox.buffer[1] = mbox::REQUEST;
|
||||
mbox.buffer[2] = mbox::tag::SETPOWER;
|
||||
mbox.buffer[3] = 8;
|
||||
mbox.buffer[4] = 8;
|
||||
mbox.buffer[5] = dev_id; // device id
|
||||
mbox.buffer[6] = 0; // bit 0: off, bit 1: no wait
|
||||
mbox.buffer[7] = mbox::tag::LAST;
|
||||
|
||||
// Insert a compiler fence that ensures that all stores to the
|
||||
// mbox buffer are finished before the GPU is signaled (which
|
||||
// is done by a store operation as well).
|
||||
compiler_fence(Ordering::Release);
|
||||
|
||||
if mbox.call(mbox::channel::PROP).is_err() {
|
||||
return Err(PowerError::MailboxError); // Abort if UART clocks couldn't be set
|
||||
};
|
||||
}
|
||||
|
||||
// power off gpio pins (but not VCC pins)
|
||||
gpio.GPFSEL0.set(0);
|
||||
gpio.GPFSEL1.set(0);
|
||||
gpio.GPFSEL2.set(0);
|
||||
gpio.GPFSEL3.set(0);
|
||||
gpio.GPFSEL4.set(0);
|
||||
gpio.GPFSEL5.set(0);
|
||||
|
||||
gpio.GPPUD.set(0);
|
||||
delays::wait_cycles(150);
|
||||
|
||||
gpio.GPPUDCLK0.set(0xffff_ffff);
|
||||
gpio.GPPUDCLK1.set(0xffff_ffff);
|
||||
delays::wait_cycles(150);
|
||||
|
||||
// flush GPIO setup
|
||||
gpio.GPPUDCLK0.set(0);
|
||||
gpio.GPPUDCLK1.set(0);
|
||||
|
||||
// We set the watchdog hard reset bit here to distinguish this
|
||||
// reset from the normal (full) reset. bootcode.bin will not
|
||||
// reboot after a hard reset.
|
||||
let mut val = self.PM_RSTS.get();
|
||||
val |= PM_PASSWORD | PM_RSTS_RASPBERRYPI_HALT;
|
||||
self.PM_RSTS.set(val);
|
||||
|
||||
// Continue with normal reset mechanism
|
||||
self.reset();
|
||||
}
|
||||
|
||||
/// Reboot
|
||||
pub fn reset(&self) -> ! {
|
||||
// use a timeout of 10 ticks (~150us)
|
||||
self.PM_WDOG.set(PM_PASSWORD | 10);
|
||||
let mut val = self.PM_RSTC.get();
|
||||
val &= PM_RSTC_WRCFG_CLR;
|
||||
val |= PM_PASSWORD | PM_RSTC_WRCFG_FULL_RESET;
|
||||
self.PM_RSTC.set(val);
|
||||
|
||||
loop {}
|
||||
}
|
||||
}
|
@ -0,0 +1,262 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2018-2019 Andre Richter <andre.o.richter@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
use super::MMIO_BASE;
|
||||
use crate::delays;
|
||||
use crate::gpio;
|
||||
use crate::mbox;
|
||||
use core::{
|
||||
ops,
|
||||
sync::atomic::{compiler_fence, Ordering},
|
||||
};
|
||||
use cortex_a::asm;
|
||||
use register::{mmio::*, register_bitfields};
|
||||
|
||||
// PL011 UART registers.
|
||||
//
|
||||
// Descriptions taken from
|
||||
// https://github.com/raspberrypi/documentation/files/1888662/BCM2837-ARM-Peripherals.-.Revised.-.V2-1.pdf
|
||||
register_bitfields! {
|
||||
u32,
|
||||
|
||||
/// Flag Register
|
||||
FR [
|
||||
/// Transmit FIFO full. The meaning of this bit depends on the
|
||||
/// state of the FEN bit in the UARTLCR_ LCRH Register. If the
|
||||
/// FIFO is disabled, this bit is set when the transmit
|
||||
/// holding register is full. If the FIFO is enabled, the TXFF
|
||||
/// bit is set when the transmit FIFO is full.
|
||||
TXFF OFFSET(5) NUMBITS(1) [],
|
||||
|
||||
/// Receive FIFO empty. The meaning of this bit depends on the
|
||||
/// state of the FEN bit in the UARTLCR_H Register. If the
|
||||
/// FIFO is disabled, this bit is set when the receive holding
|
||||
/// register is empty. If the FIFO is enabled, the RXFE bit is
|
||||
/// set when the receive FIFO is empty.
|
||||
RXFE OFFSET(4) NUMBITS(1) []
|
||||
],
|
||||
|
||||
/// Integer Baud rate divisor
|
||||
IBRD [
|
||||
/// Integer Baud rate divisor
|
||||
IBRD OFFSET(0) NUMBITS(16) []
|
||||
],
|
||||
|
||||
/// Fractional Baud rate divisor
|
||||
FBRD [
|
||||
/// Fractional Baud rate divisor
|
||||
FBRD OFFSET(0) NUMBITS(6) []
|
||||
],
|
||||
|
||||
/// Line Control register
|
||||
LCRH [
|
||||
/// Word length. These bits indicate the number of data bits
|
||||
/// transmitted or received in a frame.
|
||||
WLEN OFFSET(5) NUMBITS(2) [
|
||||
FiveBit = 0b00,
|
||||
SixBit = 0b01,
|
||||
SevenBit = 0b10,
|
||||
EightBit = 0b11
|
||||
]
|
||||
],
|
||||
|
||||
/// Control Register
|
||||
CR [
|
||||
/// Receive enable. If this bit is set to 1, the receive
|
||||
/// section of the UART is enabled. Data reception occurs for
|
||||
/// UART signals. When the UART is disabled in the middle of
|
||||
/// reception, it completes the current character before
|
||||
/// stopping.
|
||||
RXE OFFSET(9) NUMBITS(1) [
|
||||
Disabled = 0,
|
||||
Enabled = 1
|
||||
],
|
||||
|
||||
/// Transmit enable. If this bit is set to 1, the transmit
|
||||
/// section of the UART is enabled. Data transmission occurs
|
||||
/// for UART signals. When the UART is disabled in the middle
|
||||
/// of transmission, it completes the current character before
|
||||
/// stopping.
|
||||
TXE OFFSET(8) NUMBITS(1) [
|
||||
Disabled = 0,
|
||||
Enabled = 1
|
||||
],
|
||||
|
||||
/// UART enable
|
||||
UARTEN OFFSET(0) NUMBITS(1) [
|
||||
/// If the UART is disabled in the middle of transmission
|
||||
/// or reception, it completes the current character
|
||||
/// before stopping.
|
||||
Disabled = 0,
|
||||
Enabled = 1
|
||||
]
|
||||
],
|
||||
|
||||
/// Interupt Clear Register
|
||||
ICR [
|
||||
/// Meta field for all pending interrupts
|
||||
ALL OFFSET(0) NUMBITS(11) []
|
||||
]
|
||||
}
|
||||
|
||||
const UART_BASE: u32 = MMIO_BASE + 0x20_1000;
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[repr(C)]
|
||||
pub struct RegisterBlock {
|
||||
DR: ReadWrite<u32>, // 0x00
|
||||
__reserved_0: [u32; 5], // 0x04
|
||||
FR: ReadOnly<u32, FR::Register>, // 0x18
|
||||
__reserved_1: [u32; 2], // 0x1c
|
||||
IBRD: WriteOnly<u32, IBRD::Register>, // 0x24
|
||||
FBRD: WriteOnly<u32, FBRD::Register>, // 0x28
|
||||
LCRH: WriteOnly<u32, LCRH::Register>, // 0x2C
|
||||
CR: WriteOnly<u32, CR::Register>, // 0x30
|
||||
__reserved_2: [u32; 4], // 0x34
|
||||
ICR: WriteOnly<u32, ICR::Register>, // 0x44
|
||||
}
|
||||
|
||||
pub enum UartError {
|
||||
MailboxError,
|
||||
}
|
||||
pub type Result<T> = ::core::result::Result<T, UartError>;
|
||||
|
||||
pub struct Uart;
|
||||
|
||||
impl ops::Deref for Uart {
|
||||
type Target = RegisterBlock;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
unsafe { &*Self::ptr() }
|
||||
}
|
||||
}
|
||||
|
||||
impl Uart {
|
||||
pub fn new() -> Uart {
|
||||
Uart
|
||||
}
|
||||
|
||||
/// Returns a pointer to the register block
|
||||
fn ptr() -> *const RegisterBlock {
|
||||
UART_BASE as *const _
|
||||
}
|
||||
|
||||
///Set baud rate and characteristics (115200 8N1) and map to GPIO
|
||||
pub fn init(&self, mbox: &mut mbox::Mbox, gpio: &gpio::GPIO) -> Result<()> {
|
||||
// turn off UART0
|
||||
self.CR.set(0);
|
||||
|
||||
// set up clock for consistent divisor values
|
||||
mbox.buffer[0] = 9 * 4;
|
||||
mbox.buffer[1] = mbox::REQUEST;
|
||||
mbox.buffer[2] = mbox::tag::SETCLKRATE;
|
||||
mbox.buffer[3] = 12;
|
||||
mbox.buffer[4] = 8;
|
||||
mbox.buffer[5] = mbox::clock::UART; // UART clock
|
||||
mbox.buffer[6] = 4_000_000; // 4Mhz
|
||||
mbox.buffer[7] = 0; // skip turbo setting
|
||||
mbox.buffer[8] = mbox::tag::LAST;
|
||||
|
||||
// Insert a compiler fence that ensures that all stores to the
|
||||
// mbox buffer are finished before the GPU is signaled (which
|
||||
// is done by a store operation as well).
|
||||
compiler_fence(Ordering::Release);
|
||||
|
||||
if mbox.call(mbox::channel::PROP).is_err() {
|
||||
return Err(UartError::MailboxError); // Abort if UART clocks couldn't be set
|
||||
};
|
||||
|
||||
// map UART0 to GPIO pins
|
||||
gpio.GPFSEL1
|
||||
.modify(gpio::GPFSEL1::FSEL14::TXD0 + gpio::GPFSEL1::FSEL15::RXD0);
|
||||
|
||||
gpio.GPPUD.set(0); // enable pins 14 and 15
|
||||
delays::wait_cycles(150);
|
||||
|
||||
gpio.GPPUDCLK0.modify(
|
||||
gpio::GPPUDCLK0::PUDCLK14::AssertClock + gpio::GPPUDCLK0::PUDCLK15::AssertClock,
|
||||
);
|
||||
delays::wait_cycles(150);
|
||||
|
||||
gpio.GPPUDCLK0.set(0);
|
||||
|
||||
self.ICR.write(ICR::ALL::CLEAR);
|
||||
self.IBRD.write(IBRD::IBRD.val(2)); // Results in 115200 baud
|
||||
self.FBRD.write(FBRD::FBRD.val(0xB));
|
||||
self.LCRH.write(LCRH::WLEN::EightBit); // 8N1
|
||||
self.CR
|
||||
.write(CR::UARTEN::Enabled + CR::TXE::Enabled + CR::RXE::Enabled);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Send a character
|
||||
pub fn send(&self, c: char) {
|
||||
// wait until we can send
|
||||
loop {
|
||||
if !self.FR.is_set(FR::TXFF) {
|
||||
break;
|
||||
}
|
||||
|
||||
asm::nop();
|
||||
}
|
||||
|
||||
// write the character to the buffer
|
||||
self.DR.set(c as u32);
|
||||
}
|
||||
|
||||
/// Receive a character
|
||||
pub fn getc(&self) -> char {
|
||||
// wait until something is in the buffer
|
||||
loop {
|
||||
if !self.FR.is_set(FR::RXFE) {
|
||||
break;
|
||||
}
|
||||
|
||||
asm::nop();
|
||||
}
|
||||
|
||||
// read it and return
|
||||
let mut ret = self.DR.get() as u8 as char;
|
||||
|
||||
// convert carrige return to newline
|
||||
if ret == '\r' {
|
||||
ret = '\n'
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
/// Display a string
|
||||
pub fn puts(&self, string: &str) {
|
||||
for c in string.chars() {
|
||||
// convert newline to carrige return + newline
|
||||
if c == '\n' {
|
||||
self.send('\r')
|
||||
}
|
||||
|
||||
self.send(c);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,60 +0,0 @@
|
||||
#
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2018 Andre Richter <andre.o.richter@gmail.com>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
#
|
||||
|
||||
TARGET = aarch64-unknown-none
|
||||
|
||||
SOURCES = $(wildcard **/*.rs) $(wildcard **/*.S) link.ld
|
||||
|
||||
OBJCOPY = cargo objcopy --
|
||||
OBJCOPY_PARAMS = --strip-all -O binary
|
||||
|
||||
UTILS_CONTAINER = andrerichter/raspi3-utils
|
||||
DOCKER_CMD = docker run -it --rm -v $(shell pwd):/work -w /work
|
||||
QEMU_CMD = qemu-system-aarch64 -M raspi3 -kernel kernel8.img
|
||||
|
||||
.PHONY: all qemu clippy clean objdump nm
|
||||
|
||||
all: clean kernel8.img
|
||||
|
||||
target/$(TARGET)/release/kernel8: $(SOURCES)
|
||||
cargo xbuild --target=$(TARGET) --release
|
||||
|
||||
kernel8.img: target/$(TARGET)/release/kernel8
|
||||
cp $< .
|
||||
$(OBJCOPY) $(OBJCOPY_PARAMS) $< kernel8.img
|
||||
|
||||
qemu: all
|
||||
$(DOCKER_CMD) $(UTILS_CONTAINER) $(QEMU_CMD) -serial stdio
|
||||
|
||||
clippy:
|
||||
cargo xclippy --target=$(TARGET)
|
||||
|
||||
clean:
|
||||
cargo clean
|
||||
|
||||
objdump:
|
||||
cargo objdump --target $(TARGET) -- -disassemble -print-imm-hex kernel8
|
||||
|
||||
nm:
|
||||
cargo nm --target $(TARGET) -- kernel8 | sort
|
@ -1,153 +0,0 @@
|
||||
# Tutorial 13 - Using debugger
|
||||
|
||||
Debugging with a debugger is very effective, but it's a bit difficult on our Raspberry Pi.
|
||||
|
||||
[The Embedded Rust Book mentions](https://rust-embedded.github.io/book/start/hardware.html) about using debugger on `STM32F3DISCOVERY`, however, there are some differences from our environment. The biggest one is lack of debugger hardware. Unlike `STM32F3DISCOVERY`, Raspberry Pi does not have embedded debugger on it's board; it means we need to get, connect, and setup it.
|
||||
|
||||
## Hardware debugger
|
||||
|
||||
A debugger `ARM-USB-TINY-H` made by OLIMEX has tested with Raspberry Pi3 and openocd.
|
||||
|
||||
https://www.olimex.com/Products/ARM/JTAG/ARM-USB-TINY-H/
|
||||
|
||||
It has standard [ARM JTAG 20 connector](http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0499dj/BEHEIHCE.html), but unfortunately, Raspberry Pi doesn't; we have to connect like following:
|
||||
|
||||
| GPIO# | Name | JTAG# | Note |
|
||||
|-------|-------|-------|---------|
|
||||
| | VTREF | 1 | to 3.3V |
|
||||
| | GND | 4 | to GND |
|
||||
| 22 | TRST | 3 | |
|
||||
| 26 | TDI | 5 | |
|
||||
| 27 | TMS | 7 | |
|
||||
| 25 | TCK | 9 | |
|
||||
| 23 | RTCK | 11 | |
|
||||
| 24 | TDO | 13 | |
|
||||
|
||||
![Connected debugger](doc/raspi3-arm-usb-tiny-h.jpg)
|
||||
|
||||
![wiring](doc/rapi3-jtag-wiring.png)
|
||||
|
||||
## debugger.rs
|
||||
|
||||
And, GPIO pins have to be changed to alternative functions. In this tutorial, `debugger.rs` sets the pins JTAG functions(all of them are assigned to Alt4) from the default.
|
||||
|
||||
```rust
|
||||
pub fn setup_debug() {
|
||||
unsafe {
|
||||
(*GPFSEL2).modify(
|
||||
GPFSEL2::FSEL27::Alt4
|
||||
+ GPFSEL2::FSEL26::Alt4
|
||||
+ GPFSEL2::FSEL25::Alt4
|
||||
+ GPFSEL2::FSEL24::Alt4
|
||||
+ GPFSEL2::FSEL23::Alt4
|
||||
+ GPFSEL2::FSEL22::Alt4,
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## main.rs
|
||||
|
||||
After enabling debugger, it goes empty loop to wait debugger connection.
|
||||
|
||||
## Running debugger on Linux with Docker
|
||||
|
||||
Using pre-built docker image like following command is easier way. This is tested on Ubuntu18.04.
|
||||
|
||||
Note that a device you have to specify in this command (`--device=XXX`) may be attached on different point on your machine. You can find it on `syslog` after you connect the debugger to your PC. It's like `/dev/ttyUSB0` on Ubuntu.
|
||||
|
||||
```console
|
||||
$ sudo docker run -p 3333:3333 -p 4444:4444 --rm --privileged --device=/dev/ttyUSB0 naotaco/openocd:armv8 /bin/sh -c "cd openocd-armv8 && openocd -f tcl/interface/ftdi/olimex-arm-usb-tiny-h.cfg -f tcl/target/rpi3.cfg"
|
||||
|
||||
Open On-Chip Debugger 0.9.0-dev-gb796a58 (2019-02-19-01:36)
|
||||
Licensed under GNU GPL v2
|
||||
For bug reports, read
|
||||
http://openocd.sourceforge.net/doc/doxygen/bugs.html
|
||||
trst_and_srst separate srst_gates_jtag trst_push_pull srst_open_drain connect_deassert_srst
|
||||
adapter speed: 1000 kHz
|
||||
jtag_ntrst_delay: 500
|
||||
Info : clock speed 1000 kHz
|
||||
Info : JTAG tap: rpi3.dap tap/device found: 0x4ba00477 (mfg: 0x23b, part: 0xba00, ver: 0x4)
|
||||
Info : rpi3.cpu: hardware has 6 breakpoints, 4 watchpoints
|
||||
Info : rpi3.cpu1: hardware has 6 breakpoints, 4 watchpoints
|
||||
Info : rpi3.cpu2: hardware has 6 breakpoints, 4 watchpoints
|
||||
Info : rpi3.cpu3: hardware has 6 breakpoints, 4 watchpoints
|
||||
```
|
||||
|
||||
Then, from another console, use telnet to connect to openocd. Type `targets` to show status.
|
||||
|
||||
```console
|
||||
$ telnet localhost 4444
|
||||
Trying ::1...
|
||||
Connection failed: Connection refused
|
||||
Trying 127.0.0.1...
|
||||
Connected to localhost.
|
||||
Escape character is '^]'.
|
||||
Open On-Chip Debugger
|
||||
> targets
|
||||
TargetName Type Endian TapName State
|
||||
-- ------------------ ---------- ------ ------------------ ------------
|
||||
0 rpi3.cpu aarch64 little rpi3.dap running
|
||||
1 rpi3.cpu1 aarch64 little rpi3.dap running
|
||||
2 rpi3.cpu2 aarch64 little rpi3.dap running
|
||||
3* rpi3.cpu3 aarch64 little rpi3.dap running
|
||||
```
|
||||
|
||||
If the Raspberry Pi is running and configured correctly, `State` will be `running`.
|
||||
|
||||
You can change target cpu and break it.
|
||||
|
||||
```console
|
||||
> targets rpi3.cpu # switch to core0
|
||||
> halt # stop CPU
|
||||
number of cache level 2
|
||||
cache l2 present :not supported
|
||||
rpi3.cpu cluster 0 core 0 multi core
|
||||
target state: halted
|
||||
target halted in ARM64 state due to debug-request, current mode: EL2H
|
||||
cpsr: 0x600003c9 pc: 0x8004c
|
||||
MMU: disabled, D-Cache: disabled, I-Cache: disabled
|
||||
> reg # show registers
|
||||
===== arm v8 registers
|
||||
(0) x0 (/64): 0x0000000000000000 (dirty)
|
||||
(1) x1 (/64): 0x0000000000080000
|
||||
...
|
||||
(29) x29 (/64): 0xE55C2E08279A78D0
|
||||
(30) x30 (/64): 0x0000000000080080
|
||||
(31) sp (/64): 0x0000000000080000
|
||||
(32) pc (/64): 0x000000000008004C
|
||||
(33) CPSR (/32): 0x600003C9
|
||||
```
|
||||
|
||||
In this timing, value of `pc` may point an address of the empty loop.
|
||||
|
||||
|
||||
## Build/setup openocd directly
|
||||
|
||||
Alternatively, you can build openocd to install to your local machine.
|
||||
|
||||
### Installing Openocd on Ubuntu 18.04
|
||||
|
||||
Unfortunately, openocd from apt on Ubuntu 18.04 does not support ARMv8; we need to build it.
|
||||
|
||||
```bash
|
||||
sudo apt install build-essential automake libtool libudev-dev pkg-config libusb-1.0-0-dev gcc-6
|
||||
git clone https://github.com/daniel-k/openocd.git openocd-armv8
|
||||
cd openocd-armv8
|
||||
git checkout origin/armv8
|
||||
./bootstrap
|
||||
CC=gcc-6 ./configure --enable-ftdi
|
||||
# Error on gcc 7.3
|
||||
make
|
||||
sudo make install
|
||||
```
|
||||
|
||||
### Running debugger
|
||||
|
||||
```console
|
||||
# go to the repository you built to find config files
|
||||
$ cd openocd-armv8
|
||||
$ sudo openocd -f tcl/interface/ftdi/olimex-arm-usb-tiny-h.cfg -f tcl/target/rpi3.cfg
|
||||
```
|
||||
|
||||
Now it waits connection at 3333/4444; you can connect as same as when using docker.
|
Binary file not shown.
Before Width: | Height: | Size: 142 KiB |
Binary file not shown.
@ -1,48 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 bzt (bztsrc@github)
|
||||
* Copyright (c) 2018 Andre Richter <andre.o.richter@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use, copy,
|
||||
* modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
* of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
.section ".text.boot"
|
||||
|
||||
.global _boot_cores
|
||||
|
||||
_boot_cores:
|
||||
// read cpu id, stop slave cores
|
||||
mrs x1, mpidr_el1
|
||||
and x1, x1, #3
|
||||
cbz x1, 2f
|
||||
// cpu id > 0, stop
|
||||
1: wfe
|
||||
b 1b
|
||||
2: // cpu id == 0
|
||||
|
||||
// set stack before our code
|
||||
ldr x1, =_boot_cores
|
||||
mov sp, x1
|
||||
|
||||
// jump to Rust code, should not return
|
||||
bl reset
|
||||
// for failsafe, halt this core too
|
||||
b 1b
|
@ -1,120 +0,0 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2019 Nao Taco <naotaco@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
use super::MMIO_BASE;
|
||||
|
||||
// use core::ops;
|
||||
use register::{mmio::ReadWrite, register_bitfields};
|
||||
|
||||
register_bitfields! {
|
||||
u32,
|
||||
|
||||
/// function Select 2
|
||||
GPFSEL2 [
|
||||
// based on 6.2 Alternative Function Assignments in BCM2835 ARM Peripherals
|
||||
|
||||
FSEL27 OFFSET(21) NUMBITS(3)[ // GPIO27
|
||||
Input = 0b000,
|
||||
Output = 0b001,
|
||||
Alt0 = 0b100,
|
||||
Alt1 = 0b101,
|
||||
Alt2 = 0b110,
|
||||
Alt3 = 0b111,
|
||||
Alt4 = 0b011, // JTAG ARM debug: ARM_TMS
|
||||
Alt5 = 0b010
|
||||
],
|
||||
|
||||
FSEL26 OFFSET(18) NUMBITS(3)[ // GPIO26
|
||||
Input = 0b000,
|
||||
Output = 0b001,
|
||||
Alt0 = 0b100,
|
||||
Alt1 = 0b101,
|
||||
Alt2 = 0b110,
|
||||
Alt3 = 0b111,
|
||||
Alt4 = 0b011, // JTAG ARM debug: ARM_TDI
|
||||
Alt5 = 0b010
|
||||
],
|
||||
|
||||
FSEL25 OFFSET(15) NUMBITS(3)[ // GPIO25
|
||||
Input = 0b000,
|
||||
Output = 0b001,
|
||||
Alt0 = 0b100,
|
||||
Alt1 = 0b101,
|
||||
Alt2 = 0b110,
|
||||
Alt3 = 0b111,
|
||||
Alt4 = 0b011, // JTAG ARM debug: ARM_TCK
|
||||
Alt5 = 0b010
|
||||
],
|
||||
|
||||
FSEL24 OFFSET(12) NUMBITS(3)[ // GPIO24
|
||||
Input = 0b000,
|
||||
Output = 0b001,
|
||||
Alt0 = 0b100,
|
||||
Alt1 = 0b101,
|
||||
Alt2 = 0b110,
|
||||
Alt3 = 0b111,
|
||||
Alt4 = 0b011, // JTAG ARM debug: ARM_TDO
|
||||
Alt5 = 0b010
|
||||
],
|
||||
|
||||
FSEL23 OFFSET(9) NUMBITS(3)[ // GPIO23
|
||||
Input = 0b000,
|
||||
Output = 0b001,
|
||||
Alt0 = 0b100,
|
||||
Alt1 = 0b101,
|
||||
Alt2 = 0b110,
|
||||
Alt3 = 0b111,
|
||||
Alt4 = 0b011, // JTAG ARM debug: ARM_RTCK
|
||||
Alt5 = 0b010
|
||||
],
|
||||
|
||||
FSEL22 OFFSET(6) NUMBITS(3)[ // GPIO22
|
||||
Input = 0b000,
|
||||
Output = 0b001,
|
||||
Alt0 = 0b100,
|
||||
Alt1 = 0b101,
|
||||
Alt2 = 0b110,
|
||||
Alt3 = 0b111,
|
||||
Alt4 = 0b011, // JTAG ARM debug: ARM_TRST
|
||||
Alt5 = 0b010
|
||||
]
|
||||
|
||||
]
|
||||
}
|
||||
|
||||
const GPFSEL2: *const ReadWrite<u32, GPFSEL2::Register> =
|
||||
(MMIO_BASE + 0x0020_0008) as *const ReadWrite<u32, GPFSEL2::Register>;
|
||||
|
||||
pub fn setup_debug() {
|
||||
unsafe {
|
||||
(*GPFSEL2).modify(
|
||||
GPFSEL2::FSEL27::Alt4
|
||||
+ GPFSEL2::FSEL26::Alt4
|
||||
+ GPFSEL2::FSEL25::Alt4
|
||||
+ GPFSEL2::FSEL24::Alt4
|
||||
+ GPFSEL2::FSEL23::Alt4
|
||||
+ GPFSEL2::FSEL22::Alt4,
|
||||
);
|
||||
}
|
||||
}
|
@ -1 +0,0 @@
|
||||
newline_style = "Unix"
|
Binary file not shown.
After Width: | Height: | Size: 113 KiB |
Binary file not shown.
After Width: | Height: | Size: 990 KiB |
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 95 KiB |
Loading…
Reference in New Issue