Introduce abstraction tut, shuffle tut numbers again
parent
09d36953a0
commit
1b046df046
@ -0,0 +1,109 @@
|
||||
# Tutorial 07 - Abstraction
|
||||
|
||||
This is a short one regarding code changes, but has lots of text because two
|
||||
important Rust principles are introduced: Abstraction and modularity.
|
||||
|
||||
From a functional perspective, this tutorial is the same as `05_uart0`, but with
|
||||
the key difference that we threw out all manually crafted assembler. Both the
|
||||
main and the glue crate do not use `#![feature(global_asm)]` or
|
||||
`#![feature(asm)]` anymore. Instead, we pulled in the [cortex-a][crate] crate,
|
||||
which now provides `cortex-a` specific features like register access or safe
|
||||
wrappers around assembly instructions.
|
||||
|
||||
[crate]: https://github.com/andre-richter/cortex-a
|
||||
|
||||
For single assembler instructions, we now have the `cortex-a::asm` namespace,
|
||||
e.g. providing `asm::nop()`.
|
||||
|
||||
For registers, there is `cortex-a::register`. For registers like the stack
|
||||
pointer, which are generally read and written as a whole, there's simple
|
||||
[read()][sp_read] and [write()][sp_write] functions which take and return
|
||||
primitive integer types.
|
||||
|
||||
[sp_read]: https://docs.rs/cortex-a/0.1.2/cortex_a/register/sp/fn.read.html
|
||||
[sp_write]: https://docs.rs/cortex-a/0.1.2/cortex_a/register/sp/fn.write.html
|
||||
|
||||
Registers that are divided into multiple fields, e.g. `MPIDR_EL1` ([see the ARM
|
||||
Reference Manual][el1]), on the other hand, are abstracted into their [own
|
||||
types][mpidr_type] and offer getter and/or setter methods, respectively.
|
||||
|
||||
[el1]: http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0500g/BABHBJCI.html
|
||||
[mpidr_type]:https://docs.rs/cortex-a/0.1.2/cortex_a/register/mpidr_el1/struct.MPIDR_EL1.html
|
||||
|
||||
To some extent, this namespacing also makes our code more portable. For example,
|
||||
if we want to reuse parts of it on another processor architecture, we could pull
|
||||
in the respective crate and change our use-clause from `use cortex-a::asm` to
|
||||
`use new_architecture::asm`. Of course this also demands that both crates adhere
|
||||
to a common set of wrappers that provide the same functionality. Assembler and
|
||||
register instructions like we use them here are actually a weak example. Where
|
||||
this modular approach can really pay off is for common peripherals like timers
|
||||
or memory management units, where implementations differ between processors, but
|
||||
usage is often the same (e.g. setting a timer for x amount of microseconds).
|
||||
|
||||
In Rust, we have the [Embedded Devices Working
|
||||
Group](https://github.com/rust-lang-nursery/embedded-wg), which among other
|
||||
goals, tries to establish a common set of wrapper- and interface-crates that
|
||||
introduce abstraction on different levels of the system. Check out the [Awesome
|
||||
Embedded Rust](https://github.com/rust-embedded/awesome-embedded-rust) list for
|
||||
an overview.
|
||||
|
||||
## Glue Code
|
||||
|
||||
Like mentioned above, we threw out the `boot_cores.S` assembler file and
|
||||
replaced it with a Rust function. Why? Because we can, for the fun of it.
|
||||
|
||||
```rust
|
||||
#[link_section = ".text.boot"]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn _boot_cores() -> ! {
|
||||
match register::mpidr_el1::read().core_id() {
|
||||
0 => unsafe {
|
||||
register::sp::write(0x80_000);
|
||||
reset()
|
||||
},
|
||||
_ => loop {
|
||||
// if not core0, infinitely wait for events
|
||||
asm::wfe();
|
||||
},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Since this is the first code that the RPi3 will execute, the stack has not been
|
||||
set up yet. Actually it is this function that will do it for the first
|
||||
time. Therefore, it is important to check that code generated from this function
|
||||
does not call any subroutines that need a working stack themselves.
|
||||
|
||||
The `register` and `asm` wrappers that we use from the `cortex-a` crate are all
|
||||
inlined, so we fulfill this requirement. The compilation result of this function
|
||||
should yield something like the following, where you can see that the stack
|
||||
pointer is not used apart from ourselves setting it.
|
||||
|
||||
```bash
|
||||
./dockcross-linux-aarch64 bash
|
||||
[andre:/work] $ aarch64-linux-gnu-objdump -CD kernel8
|
||||
|
||||
[...] (Some output omitted)
|
||||
|
||||
0000000000080000 <_boot_cores>:
|
||||
80000: d53800a8 mrs x8, mpidr_el1
|
||||
80004: f240051f tst x8, #0x3
|
||||
80008: 54000060 b.eq 80014 <_boot_cores+0x14>
|
||||
8000c: d503205f wfe
|
||||
80010: 17ffffff b 8000c <_boot_cores+0xc>
|
||||
80014: 320d03e8 orr w8, wzr, #0x80000
|
||||
80018: 9100011f mov sp, x8
|
||||
8001c: 9400016b bl 805c8 <raspi3_glue::reset::h2a7ad49cd9d2154d>
|
||||
```
|
||||
|
||||
It is important to always manually check this, and not blindly rely on the
|
||||
compiler.
|
||||
|
||||
## Test it
|
||||
|
||||
Since this is the first tutorial after we've written our own bootloader over
|
||||
serial, you can now for the first time test this convenient interface:
|
||||
|
||||
```bash
|
||||
make raspboot
|
||||
```
|
Binary file not shown.
@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#![no_std]
|
||||
|
||||
extern crate cortex_a;
|
||||
extern crate raspi3_glue;
|
||||
extern crate volatile_register;
|
||||
|
||||
const MMIO_BASE: u32 = 0x3F00_0000;
|
||||
|
||||
mod gpio;
|
||||
mod mbox;
|
||||
mod uart;
|
||||
|
||||
use core::sync::atomic::{compiler_fence, Ordering};
|
||||
|
||||
fn main() {
|
||||
let mut mbox = mbox::Mbox::new();
|
||||
let uart = uart::Uart::new();
|
||||
|
||||
// set up serial console
|
||||
if uart.init(&mut mbox).is_err() {
|
||||
return; // If UART fails, abort early
|
||||
}
|
||||
|
||||
// get the board's unique serial number with a mailbox call
|
||||
mbox.buffer[0] = 8 * 4; // length of the message
|
||||
mbox.buffer[1] = mbox::REQUEST; // this is a request message
|
||||
mbox.buffer[2] = mbox::tag::GETSERIAL; // get serial number command
|
||||
mbox.buffer[3] = 8; // buffer size
|
||||
mbox.buffer[4] = 8;
|
||||
mbox.buffer[5] = 0; // clear output buffer
|
||||
mbox.buffer[6] = 0;
|
||||
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);
|
||||
|
||||
// send the message to the GPU and receive answer
|
||||
let serial_avail = match mbox.call(mbox::channel::PROP) {
|
||||
Err(_) => false,
|
||||
Ok(()) => true,
|
||||
};
|
||||
|
||||
uart.getc(); // Press a key first before being greeted
|
||||
uart.puts("Hello Rustacean!\n");
|
||||
|
||||
if serial_avail {
|
||||
uart.puts("My serial number is: ");
|
||||
uart.hex(mbox.buffer[6]);
|
||||
uart.hex(mbox.buffer[5]);
|
||||
uart.puts("\n");
|
||||
} else {
|
||||
uart.puts("Unable to query serial!\n");
|
||||
}
|
||||
|
||||
// echo everything back
|
||||
loop {
|
||||
uart.send(uart.getc());
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
[[package]]
|
||||
name = "cortex-a"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "kernel8"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cortex-a 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"raspi3_glue 0.1.0",
|
||||
"volatile-register 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "panic-abort"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "r0"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "raspi3_glue"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cortex-a 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"panic-abort 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"r0 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vcell"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "volatile-register"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"vcell 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[metadata]
|
||||
"checksum cortex-a 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7a659ffa30a45a5a05970bb3419796563b6517c03bb68950e7ab4c65dad94680"
|
||||
"checksum panic-abort 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "75553c30311427a2d9f24a646fc8cedb00e1da1c6bd1608d71d634184c60392e"
|
||||
"checksum r0 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e2a38df5b15c8d5c7e8654189744d8e396bddc18ad48041a500ce52d6948941f"
|
||||
"checksum vcell 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "45c297f0afb6928cd08ab1ff9d95e99392595ea25ae1b5ecf822ff8764e57a0d"
|
||||
"checksum volatile-register 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0d67cb4616d99b940db1d6bd28844ff97108b498a6ca850e5b6191a532063286"
|
@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "kernel8"
|
||||
version = "0.1.0"
|
||||
authors = ["Andre Richter <andre.o.richter@gmail.com>"]
|
||||
|
||||
[dependencies]
|
||||
raspi3_glue = { path = "raspi3_glue" }
|
||||
cortex-a = "0.1.2"
|
||||
volatile-register = "0.2.0"
|
@ -0,0 +1,72 @@
|
||||
#
|
||||
# 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-raspi3-none-elf
|
||||
|
||||
CROSS_CONTAINER = ./dockcross-linux-aarch64
|
||||
CROSS_CONTAINER_OBJCOPY = aarch64-linux-gnu-objcopy
|
||||
|
||||
UTILS_CONTAINER = andrerichter/raspi3-utils
|
||||
DOCKER_CMD = docker run -it --rm -v $(shell pwd):/work -w /work
|
||||
DOCKER_TTY = --privileged -v /dev:/dev
|
||||
QEMU_CMD = qemu-system-aarch64 -M raspi3 -kernel kernel8.img
|
||||
RASPBOOT_CMD = raspbootcom /dev/ttyUSB0 kernel8.img
|
||||
|
||||
all: clean cross_cont_download kernel8.img
|
||||
|
||||
cross_cont_download:
|
||||
ifeq (,$(wildcard $(CROSS_CONTAINER)))
|
||||
docker run --rm dockcross/linux-arm64 > $(CROSS_CONTAINER)
|
||||
chmod +x $(CROSS_CONTAINER)
|
||||
endif
|
||||
|
||||
target/$(TARGET)/debug/kernel8: src/main.rs
|
||||
RUST_TARGET_PATH=$(shell pwd) xargo build --target=$(TARGET)
|
||||
cp $@ .
|
||||
|
||||
target/$(TARGET)/release/kernel8: src/main.rs
|
||||
RUST_TARGET_PATH=$(shell pwd) xargo build --target=$(TARGET) --release
|
||||
cp $@ .
|
||||
|
||||
ifeq ($(DEBUG),1)
|
||||
kernel8: target/$(TARGET)/debug/kernel8
|
||||
else
|
||||
kernel8: target/$(TARGET)/release/kernel8
|
||||
endif
|
||||
|
||||
kernel8.img: kernel8
|
||||
$(CROSS_CONTAINER) $(CROSS_CONTAINER_OBJCOPY) -O binary -S $< kernel8.img
|
||||
|
||||
qemu:
|
||||
$(DOCKER_CMD) $(UTILS_CONTAINER) $(QEMU_CMD) -serial stdio
|
||||
|
||||
raspboot:
|
||||
$(DOCKER_CMD) $(DOCKER_TTY) $(UTILS_CONTAINER) $(RASPBOOT_CMD)
|
||||
|
||||
clippy:
|
||||
RUSTFLAGS="-C panic=abort" xargo clippy
|
||||
|
||||
clean:
|
||||
cargo clean
|
||||
rm -f kernel8
|
@ -1,4 +1,4 @@
|
||||
# Tutorial 07 - Hardware Random Number Generator
|
||||
# Tutorial 08 - Hardware Random Number Generator
|
||||
|
||||
This going to be an easy tutorial. We query a number from the (undocumented)
|
||||
hardware random number generator. You can use this to implement a simple, but
|
@ -0,0 +1,32 @@
|
||||
{
|
||||
"arch": "aarch64",
|
||||
"data-layout": "e-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128",
|
||||
"executables": true,
|
||||
"linker-flavor": "ld.lld",
|
||||
"linker-is-gnu": true,
|
||||
"pre-link-args": {
|
||||
"ld.lld": [
|
||||
"--script=link.ld"
|
||||
]
|
||||
},
|
||||
"llvm-target": "aarch64-unknown-none",
|
||||
"no-compiler-rt": true,
|
||||
"features": "+a53,+strict-align",
|
||||
"max-atomic-width": 128,
|
||||
"os": "none",
|
||||
"panic": "abort",
|
||||
"panic-strategy": "abort",
|
||||
"relocation-model": "pic",
|
||||
"target-c-int-width": "32",
|
||||
"target-endian": "little",
|
||||
"target-pointer-width": "64",
|
||||
"disable-redzone": true,
|
||||
"abi-blacklist": [
|
||||
"stdcall",
|
||||
"fastcall",
|
||||
"vectorcall",
|
||||
"thiscall",
|
||||
"win64",
|
||||
"sysv64"
|
||||
]
|
||||
}
|
Binary file not shown.
@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "raspi3_glue"
|
||||
version = "0.1.0"
|
||||
authors = ["Andre Richter <andre.o.richter@gmail.com>"]
|
||||
|
||||
[dependencies]
|
||||
cortex-a = "0.1.2"
|
||||
panic-abort = "0.1.1"
|
||||
r0 = "0.2.2"
|
@ -0,0 +1,90 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2018 Jorge Aparicio
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#![feature(lang_items)]
|
||||
#![no_std]
|
||||
|
||||
extern crate cortex_a;
|
||||
extern crate panic_abort;
|
||||
extern crate r0;
|
||||
|
||||
use core::ptr;
|
||||
use cortex_a::{asm, register};
|
||||
|
||||
#[lang = "start"]
|
||||
extern "C" fn start<T>(user_main: fn() -> T, _argc: isize, _argv: *const *const u8) -> isize
|
||||
where
|
||||
T: Termination,
|
||||
{
|
||||
user_main().report() as isize
|
||||
}
|
||||
|
||||
#[lang = "termination"]
|
||||
trait Termination {
|
||||
fn report(self) -> i32;
|
||||
}
|
||||
|
||||
impl Termination for () {
|
||||
fn report(self) -> i32 {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn reset() -> ! {
|
||||
extern "C" {
|
||||
fn main(argc: isize, argv: *const *const u8) -> isize;
|
||||
|
||||
// Boundaries of the .bss section
|
||||
static mut __bss_start: u32;
|
||||
static mut __bss_end: u32;
|
||||
}
|
||||
|
||||
// Zeroes the .bss section
|
||||
r0::zero_bss(&mut __bss_start, &mut __bss_end);
|
||||
|
||||
main(0, ptr::null());
|
||||
|
||||
loop {}
|
||||
}
|
||||
|
||||
/// Entrypoint of the RPi3.
|
||||
///
|
||||
/// Parks all cores except core0, and then jumps to the internal
|
||||
/// `reset()` function, which will call the user's `main()` after
|
||||
/// initializing the `bss` section.
|
||||
#[link_section = ".text.boot"]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn _boot_cores() -> ! {
|
||||
match register::mpidr_el1::read().core_id() {
|
||||
0 => unsafe {
|
||||
register::sp::write(0x80_000);
|
||||
reset()
|
||||
},
|
||||
_ => loop {
|
||||
// if not core0, infinitely wait for events
|
||||
asm::wfe();
|
||||
},
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
use super::MMIO_BASE;
|
||||
use volatile_register::RW;
|
||||
|
||||
pub const GPFSEL1: *const RW<u32> = (MMIO_BASE + 0x0020_0004) as *const RW<u32>;
|
||||
pub const GPPUD: *const RW<u32> = (MMIO_BASE + 0x0020_0094) as *const RW<u32>;
|
||||
pub const GPPUDCLK0: *const RW<u32> = (MMIO_BASE + 0x0020_0098) as *const RW<u32>;
|
@ -0,0 +1,138 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
use super::MMIO_BASE;
|
||||
use cortex_a::asm;
|
||||
use volatile_register::{RO, WO};
|
||||
|
||||
const VIDEOCORE_MBOX: u32 = MMIO_BASE + 0xB880;
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[repr(C)]
|
||||
struct Registers {
|
||||
READ: RO<u32>, // 0x00
|
||||
__reserved_0: [u32; 3], // 0x04
|
||||
POLL: RO<u32>, // 0x10
|
||||
SENDER: RO<u32>, // 0x14
|
||||
STATUS: RO<u32>, // 0x18
|
||||
CONFIG: RO<u32>, // 0x1C
|
||||
WRITE: WO<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 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;
|
||||
const FULL: u32 = 0x8000_0000;
|
||||
const EMPTY: u32 = 0x4000_0000;
|
||||
|
||||
// Public interface to the mailbox
|
||||
#[repr(C)]
|
||||
pub struct Mbox {
|
||||
// The address for buffer needs to be 16-byte aligned so that the
|
||||
// Videcore can handle it properly. We don't take precautions here
|
||||
// to achieve that, but for now it just works. Since alignment of
|
||||
// data structures in Rust is a bit of a hassle right now, we just
|
||||
// close our eyes and roll with it.
|
||||
pub buffer: [u32; 36],
|
||||
registers: *const Registers,
|
||||
}
|
||||
|
||||
impl Mbox {
|
||||
pub fn new() -> Mbox {
|
||||
Mbox {
|
||||
buffer: [0; 36],
|
||||
registers: VIDEOCORE_MBOX as *const Registers,
|
||||
}
|
||||
}
|
||||
|
||||
/// Make a mailbox call. Returns Err(MboxError) on failure, Ok(()) success
|
||||
pub fn call(&mut self, channel: u32) -> Result<()> {
|
||||
// wait until we can write to the mailbox
|
||||
loop {
|
||||
if (unsafe { (*self.registers).STATUS.read() } & FULL) != FULL {
|
||||
break;
|
||||
}
|
||||
|
||||
asm::nop();
|
||||
}
|
||||
|
||||
// write the address of our message to the mailbox with channel identifier
|
||||
unsafe {
|
||||
(*self.registers)
|
||||
.WRITE
|
||||
.write(((self.buffer.as_mut_ptr() as u32) & !0xF) | (channel & 0xF));
|
||||
}
|
||||
|
||||
// now wait for the response
|
||||
loop {
|
||||
// is there a response?
|
||||
loop {
|
||||
if (unsafe { (*self.registers).STATUS.read() } & EMPTY) != EMPTY {
|
||||
break;
|
||||
}
|
||||
|
||||
asm::nop();
|
||||
}
|
||||
|
||||
let resp: u32 = unsafe { (*self.registers).READ.read() };
|
||||
|
||||
// is it a response to our message?
|
||||
if ((resp & 0xF) == channel) && ((resp & !0xF) == (self.buffer.as_mut_ptr() as u32)) {
|
||||
// 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,191 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
use super::MMIO_BASE;
|
||||
use core::sync::atomic::{compiler_fence, Ordering};
|
||||
use cortex_a::asm;
|
||||
use gpio;
|
||||
use mbox;
|
||||
use volatile_register::*;
|
||||
|
||||
const UART_BASE: u32 = MMIO_BASE + 0x20_1000;
|
||||
|
||||
// PL011 UART registers
|
||||
#[allow(non_snake_case)]
|
||||
#[repr(C)]
|
||||
struct Registers {
|
||||
DR: RW<u32>, // 0x00
|
||||
__reserved_0: [u32; 5], // 0x04
|
||||
FR: RO<u32>, // 0x18
|
||||
__reserved_1: [u32; 2], // 0x1c
|
||||
IBRD: WO<u32>, // 0x24
|
||||
FBRD: WO<u32>, // 0x28
|
||||
LCRH: WO<u32>, // 0x2C
|
||||
CR: WO<u32>, // 0x30
|
||||
__reserved_2: [u32; 4], // 0x34
|
||||
ICR: WO<u32>, // 0x44
|
||||
}
|
||||
|
||||
pub enum UartError {
|
||||
MailboxError,
|
||||
}
|
||||
pub type Result<T> = ::core::result::Result<T, UartError>;
|
||||
|
||||
pub struct Uart {
|
||||
registers: *const Registers,
|
||||
}
|
||||
|
||||
impl Uart {
|
||||
pub fn new() -> Uart {
|
||||
Uart {
|
||||
registers: UART_BASE as *const Registers,
|
||||
}
|
||||
}
|
||||
|
||||
///Set baud rate and characteristics (115200 8N1) and map to GPIO
|
||||
pub fn init(&self, mbox: &mut mbox::Mbox) -> Result<()> {
|
||||
// turn off UART0
|
||||
unsafe { (*self.registers).CR.write(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
|
||||
unsafe {
|
||||
(*gpio::GPFSEL1).modify(|x| {
|
||||
// Modify with a closure
|
||||
let mut ret = x;
|
||||
ret &= !((7 << 12) | (7 << 15)); // gpio14, gpio15
|
||||
ret |= (4 << 12) | (4 << 15); // alt0
|
||||
|
||||
ret
|
||||
});
|
||||
|
||||
(*gpio::GPPUD).write(0); // enable pins 14 and 15
|
||||
for _ in 0..150 {
|
||||
asm::nop();
|
||||
}
|
||||
|
||||
(*gpio::GPPUDCLK0).write((1 << 14) | (1 << 15));
|
||||
for _ in 0..150 {
|
||||
asm::nop();
|
||||
}
|
||||
(*gpio::GPPUDCLK0).write(0);
|
||||
|
||||
(*self.registers).ICR.write(0x7FF); // clear interrupts
|
||||
(*self.registers).IBRD.write(2); // 115200 baud
|
||||
(*self.registers).FBRD.write(0xB);
|
||||
(*self.registers).LCRH.write(0b11 << 5); // 8n1
|
||||
(*self.registers).CR.write(0x301); // enable Tx, Rx, FIFO
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Send a character
|
||||
pub fn send(&self, c: char) {
|
||||
// wait until we can send
|
||||
loop {
|
||||
if (unsafe { (*self.registers).FR.read() } & 0x20) != 0x20 {
|
||||
break;
|
||||
}
|
||||
|
||||
asm::nop();
|
||||
}
|
||||
|
||||
// write the character to the buffer
|
||||
unsafe { (*self.registers).DR.write(c as u32) };
|
||||
}
|
||||
|
||||
/// Receive a character
|
||||
pub fn getc(&self) -> char {
|
||||
// wait until something is in the buffer
|
||||
loop {
|
||||
if (unsafe { (*self.registers).FR.read() } & 0x10) != 0x10 {
|
||||
break;
|
||||
}
|
||||
|
||||
asm::nop();
|
||||
}
|
||||
|
||||
// read it and return
|
||||
let mut ret = unsafe { (*self.registers).DR.read() 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);
|
||||
}
|
||||
}
|
||||
|
||||
/// Display a binary value in hexadecimal
|
||||
pub fn hex(&self, d: u32) {
|
||||
let mut n;
|
||||
|
||||
for i in 0..8 {
|
||||
// get highest tetrad
|
||||
n = d.wrapping_shr(28 - i * 4) & 0xF;
|
||||
|
||||
// 0-9 => '0'-'9', 10-15 => 'A'-'F'
|
||||
// Add proper offset for ASCII table
|
||||
if n > 9 {
|
||||
n += 0x37;
|
||||
} else {
|
||||
n += 0x30;
|
||||
}
|
||||
|
||||
self.send(n as u8 as char);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue