Add tutorial 01_bareminimum

pull/4/head
Andre Richter 6 years ago
parent b2f5da7184
commit 8f94a07675

@ -1,38 +0,0 @@
#
# Copyright (C) 2018 bzt (bztsrc@github)
#
# 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.
#
#
CFLAGS = -Wall -O2 -ffreestanding -nostdinc -nostdlib -nostartfiles
all: clean kernel8.img
start.o: start.S
aarch64-elf-gcc $(CFLAGS) -c start.S -o start.o
kernel8.img: start.o
aarch64-elf-ld -nostdlib -nostartfiles start.o -T link.ld -o kernel8.elf
aarch64-elf-objcopy -O binary kernel8.elf kernel8.img
clean:
rm kernel8.elf *.o >/dev/null 2>/dev/null || true

@ -1,37 +0,0 @@
Oktatóanyag 01 - A legszükségesebbek
====================================
Rendben, nem fogunk semmi érdekeset csinálni, csak kipróbáljuk a környezetet. A keletkezett kernel8.img-nek
be kell tudnia bootolni a Raspberry Pi-n, ahol végtelen ciklusban várakoztatja a CPU magokat. Ezt ellenőrizheted a
következő paranccsal:
```sh
$ qemu-system-aarch64 -M raspi3 -kernel kernel8.img -d in_asm
... kimenet törölve az átláthatóság miatt, utolsó sor: ...
0x0000000000080004: 17ffffff b #-0x4 (addr 0x80000)
```
Start
-----
Amikor a vezérlés átadódik a kernel8.img-nek, a környezet még nem alkalmas C kód futtatására. Ezért mindenképp
szükség van egy kis Assembly bevezetőre. Mivel ez az első oktatóanyag nagyon egyszerű, nem is lesz más, nincs
még C kódunk.
Ne feledkezzünk meg róla, hogy 4 CPU magunk van. Most mind ugyanazt a végtelen ciklust hajtja végre.
Makefile
--------
A Makefile-unk végtelenü legyszerű. Lefordítjuk a start.S-t, ez az egyetlen forrás fájlunk. Aztán a szerkesztési
fázisban a linker.ld szrkript segítségével linkeljük. Végezetül pedig a keletkező elf futtahatót nyers programfájllá
kovertáljuk.
Linker szkript
--------------
Nem túl meglepő módon ez is egyszerű. Be kell állítanunk a bázis címet, ahová a kernel8.img töltődik, és mindent
ide rakunk, mivel csak egy szekciónk van. Fontos megjegyezni, hogy AArch64 kód esetén a betöltési cím **0x80000**
és nem **0x8000**, mint AArch32 esetében.

@ -1,34 +0,0 @@
Tutorial 01 - Bare Minimum
==========================
Okay, we're not going to do anything here, just test our toolchain. The resulting kernel8.img should
boot on Raspberry Pi, and stop the CPU cores in an infinite loop. You can check that by running
```sh
$ qemu-system-aarch64 -M raspi3 -kernel kernel8.img -d in_asm
... output removed for clearity, last line: ...
0x0000000000080004: 17ffffff b #-0x4 (addr 0x80000)
```
Start
-----
When the control is passed to kernel8.img, the environment is not ready for C. Therefore we must
implement a small preambule in Assembly. As this first tutorial is very simple, that's all we have, no C
for now.
Note that CPU has 4 cores. All of them are running the same infinite loop for now.
Makefile
--------
Our Makefile is very simple. We compile start.S, as this is our only source. Then in linker phase we
link it using the linker.ld script. Finaly we convert the resulting elf executable into a raw image.
Linker script
-------------
Not surpisingly simple too. We just set the base address where our kernel8.img will be loaded, and we
put the only section we have there. Important note, for AArch64 the load address is **0x80000**, and
not **0x8000** as with AArch32.

@ -0,0 +1,4 @@
[[package]]
name = "kernel8"
version = "0.1.0"

@ -0,0 +1,6 @@
[package]
name = "kernel8"
version = "0.1.0"
authors = ["Andre Richter <andre.o.richter@gmail.com>"]
[dependencies]

@ -0,0 +1,58 @@
#
# 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
QEMU_CONTAINER = andrerichter/raspi3-qemu
DOCKER_CMD = docker run -it --rm -v $(shell pwd):/work -w /work
QEMU_CMD = qemu-system-aarch64 -M raspi3 -kernel kernel8.img
all: clean kernel8.img
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) $(QEMU_CONTAINER) $(QEMU_CMD) -d in_asm
clean:
cargo clean
rm -f kernel8

@ -0,0 +1,86 @@
# Tutorial 01 - Bare Minimum
Okay, we're not going to do much here, just test our toolchain. The resulting
kernel8.img should boot on the Raspberry Pi 3, and stop all CPU cores in an
infinite waiting loop. You can check that by running
```bash
$ make qemu
... some output removed for clearity: ...
----------------
IN:
0x00080000: d503205f wfe
0x00080004: 17ffffff b #0x80000
```
## Crate setup
In this tutorial, we are compiling a kernel that is in the end only executing a
single assembly instruction which we program with an assembly file.
However, since we want to use the toolchain that is delivered with `rustup` as
much as possible, we are already setting up a Rust crate. This allows us to use
`rustc` and LLVM's `lld.ld` linker to process our assembly file.
## main.rs
We define the crate to not use the standard library (`#![no_std]`), indicate
that it does not have a main function via `#![no_main]`, and also define a stub
for the `panic_fmt()` handler, which is a requirement for `no_std` crates.
In th end, we (mis)use `main.rs` as a wrapper to process our assembly file via
`rustc`. The assembly file iself is included with the [global_asm!()][gasm]
macro.
[gasm]: https://doc.rust-lang.org/unstable-book/language-features/global-asm.html
## boot_cores.S
When the control is passed to kernel8.img, the environment is not ready yet for
Rust. Therefore we must implement a small preamble in assembly, no Rust for now.
All we do is executing [wfe][wfe], an instruction that puts the CPU cores to
sleep until an asynchronous event occurs. If that happens, we jump right back to
`wfe` again.
[wfe]: http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0360e/CHDBGCFH.html
Note that the CPU has 4 cores. All of them will execute the same infinite loop
for now.
## aarch64-raspi3-none-elf.json
This is our custom target definition of the RPi3 for [Xargo][xargo]. It also
includes a directive to use the `link.ld` linker script.
```json
"pre-link-args": {
"ld.lld": [
"--script=link.ld"
]
},
```
[xargo]: https://github.com/japaric/xargo
## Makefile
Our Makefile has a few useful targets:
- `kernel8` compiles the crate either in release or debug mode. For the latter,
add `DEBUG=1` before invoking make, e.g. `DEBUG=1 make`
- `kernel8.img` uses our cross-toolchain's `objcopy` in the docker container to
generate our kernel binary. Citing the [binutils documentation][butils]:
- "_When objcopy generates a raw binary file, it will essentially produce a
memory dump of the contents of the input object file. All symbols and
relocation information will be discarded. The memory dump will start at
the load address of the lowest section copied into the output file._"
- `qemu` loads our kernel into an emulated RPi3, and shows as output the
assembler blocks that are executed. This happens in another docker container.
[butils]: https://sourceware.org/binutils/docs/binutils/objcopy.html
## Linker script `link.ld`
We just set the base address where our kernel8.img will be loaded, and we put
the only section we have there, which is `.text.boot`. Important note, for
AArch64 the load address is **0x80_000**, and not **0x80_00** as with AArch32.

@ -0,0 +1,24 @@
{
"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
}

@ -0,0 +1,246 @@
#!/bin/bash
DEFAULT_DOCKCROSS_IMAGE=dockcross/linux-arm64
#------------------------------------------------------------------------------
# Helpers
#
err() {
echo -e >&2 ERROR: $@\\n
}
die() {
err $@
exit 1
}
has() {
# eg. has command update
local kind=$1
local name=$2
type -t $kind:$name | grep -q function
}
#------------------------------------------------------------------------------
# Command handlers
#
command:update-image() {
docker pull $FINAL_IMAGE
}
help:update-image() {
echo Pull the latest $FINAL_IMAGE .
}
command:update-script() {
if cmp -s <( docker run --rm $FINAL_IMAGE ) $0; then
echo $0 is up to date
else
echo -n Updating $0 '... '
docker run --rm $FINAL_IMAGE > $0 && echo ok
fi
}
help:update-image() {
echo Update $0 from $FINAL_IMAGE .
}
command:update() {
command:update-image
command:update-script
}
help:update() {
echo Pull the latest $FINAL_IMAGE, and then update $0 from that.
}
command:help() {
if [[ $# != 0 ]]; then
if ! has command $1; then
err \"$1\" is not an dockcross command
command:help
elif ! has help $1; then
err No help found for \"$1\"
else
help:$1
fi
else
cat >&2 <<ENDHELP
Usage: dockcross [options] [--] command [args]
By default, run the given *command* in an dockcross Docker container.
The *options* can be one of:
--args|-a Extra args to the *docker run* command
--image|-i Docker cross-compiler image to use
--config|-c Bash script to source before running this script
Additionally, there are special update commands:
update-image
update-script
update
For update command help use: $0 help <command>
ENDHELP
exit 1
fi
}
#------------------------------------------------------------------------------
# Option processing
#
special_update_command=''
while [[ $# != 0 ]]; do
case $1 in
--)
shift
break
;;
--args|-a)
ARG_ARGS="$2"
shift 2
;;
--config|-c)
ARG_CONFIG="$2"
shift 2
;;
--image|-i)
ARG_IMAGE="$2"
shift 2
;;
update|update-image|update-script)
special_update_command=$1
break
;;
-*)
err Unknown option \"$1\"
command:help
exit
;;
*)
break
;;
esac
done
# The precedence for options is:
# 1. command-line arguments
# 2. environment variables
# 3. defaults
# Source the config file if it exists
DEFAULT_DOCKCROSS_CONFIG=~/.dockcross
FINAL_CONFIG=${ARG_CONFIG-${DOCKCROSS_CONFIG-$DEFAULT_DOCKCROSS_CONFIG}}
[[ -f "$FINAL_CONFIG" ]] && source "$FINAL_CONFIG"
# Set the docker image
FINAL_IMAGE=${ARG_IMAGE-${DOCKCROSS_IMAGE-$DEFAULT_DOCKCROSS_IMAGE}}
# Handle special update command
if [ "$special_update_command" != "" ]; then
case $special_update_command in
update)
command:update
exit $?
;;
update-image)
command:update-image
exit $?
;;
update-script)
command:update-script
exit $?
;;
esac
fi
# Set the docker run extra args (if any)
FINAL_ARGS=${ARG_ARGS-${DOCKCROSS_ARGS}}
# Bash on Ubuntu on Windows
UBUNTU_ON_WINDOWS=$([ -e /proc/version ] && grep -l Microsoft /proc/version || echo "")
# MSYS, Git Bash, etc.
MSYS=$([ -e /proc/version ] && grep -l MINGW /proc/version || echo "")
if [ -z "$UBUNTU_ON_WINDOWS" -a -z "$MSYS" ]; then
USER_IDS="-e BUILDER_UID=$( id -u ) -e BUILDER_GID=$( id -g ) -e BUILDER_USER=$( id -un ) -e BUILDER_GROUP=$( id -gn )"
fi
# Change the PWD when working in Docker on Windows
if [ -n "$UBUNTU_ON_WINDOWS" ]; then
HOST_PWD=$PWD
HOST_PWD=${HOST_PWD/\/mnt\//}
HOST_PWD=${HOST_PWD/\//:\/}
elif [ -n "$MSYS" ]; then
HOST_PWD=$PWD
HOST_PWD=${HOST_PWD/\//}
HOST_PWD=${HOST_PWD/\//:\/}
else
HOST_PWD=$PWD
fi
# Mount Additional Volumes
if [ -z "$SSH_DIR" ]; then
SSH_DIR="$HOME/.ssh"
fi
HOST_VOLUMES=
if [ -e "$SSH_DIR" ]; then
HOST_VOLUMES+="-v $SSH_DIR:/home/$(id -un)/.ssh"
fi
#------------------------------------------------------------------------------
# Now, finally, run the command in a container
#
tty -s && TTY_ARGS=-ti || TTY_ARGS=
CONTAINER_NAME=dockcross_$RANDOM
docker run $TTY_ARGS --name $CONTAINER_NAME \
-v "$HOST_PWD":/work \
$HOST_VOLUMES \
$USER_IDS \
$FINAL_ARGS \
$FINAL_IMAGE "$@"
run_exit_code=$?
# Attempt to delete container
rm_output=$(docker rm -f $CONTAINER_NAME 2>&1)
rm_exit_code=$?
if [[ $rm_exit_code != 0 ]]; then
if [[ "$CIRCLECI" == "true" ]] && [[ $rm_output == *"Driver btrfs failed to remove"* ]]; then
: # Ignore error because of https://circleci.com/docs/docker-btrfs-error/
else
echo "$rm_output"
exit $rm_exit_code
fi
fi
exit $run_exit_code
################################################################################
#
# This image is not intended to be run manually.
#
# To create a dockcross helper script for the
# dockcross/linux-armv7 image, run:
#
# docker run --rm dockcross/linux-armv7 > dockcross-linux-armv7
# chmod +x dockcross-linux-armv7
#
# You may then wish to move the dockcross script to your PATH.
#
################################################################################

@ -26,7 +26,7 @@
SECTIONS
{
. = 0x80000;
.text : { *(.text.boot) }
.text : { KEEP(*(.text.boot)) }
/DISCARD/ : { *(.comment) *(.gnu*) *(.note*) *(.eh_frame*) }
}

@ -25,8 +25,8 @@
.section ".text.boot"
.global _start
.global _boot_cores
_start:
_boot_cores:
1: wfe
b 1b

@ -0,0 +1,40 @@
/*
* 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]
#![no_main]
#![feature(lang_items)]
#![feature(global_asm)]
#[lang = "panic_fmt"]
unsafe extern "C" fn panic_fmt(
_args: core::fmt::Arguments,
_file: &'static str,
_line: u32,
_col: u32,
) -> ! {
loop {}
}
global_asm!(include_str!("boot_cores.S"));
Loading…
Cancel
Save