Add tutorial 02_multicore_rust

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

@ -1,43 +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.
#
#
SRCS = $(wildcard *.c)
OBJS = $(SRCS:.c=.o)
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
%.o: %.c
aarch64-elf-gcc $(CFLAGS) -c $< -o $@
kernel8.img: start.o $(OBJS)
aarch64-elf-ld -nostdlib -nostartfiles start.o $(OBJS) -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,36 +0,0 @@
Oktatóanyag 02 - Többmagos C
============================
Próbáljunk valami összetettebbet, mit szóltok? Összetettebb alatt azt értem, hogy most is megállítjuk a CPU magokat,
akárcsak az első oktatóanyagban, de most az egyik magot C-ből!
Start
-----
Most már meg kell különböztetnünk a magokat. Ehhez kiolvassuk a *mpidr_el1* rendszer regisztert. Ha nem nulla, akkor
a korábbi végtelen ciklus következik. Ha nulla, akkor meg fogunk hívni egy C eljárást. De ehhez előbb be állítanunk
egy megfelelő vermet, ki kell nulláznunk a bss szegmenst mielőtt kiadhatnánk az ugrás parancsot. Hozzáadtam néhány
Assembly sort, amik mindezt elvégzik. Arra az esetre, ha a C eljárás visszatérne (nem szabadna), ugyanarra a
végtelen ciklusra ugrunk, mint amit a többi CPU mag is épp végrehajt.
Makefile
--------
Egy kisit trükkösebb lett. Hozzáadtam parancsokat a C fordításhoz, de akkor már általánosan. Mostantól ugyanazt
a Makefile-t használhatjuk minden oktatóanyaghoz, függetlenül attól, hány C forrásfájlunk van, és a továbbiakban
nem is szerepeltetem.
Linker script
-------------
Hasonlóan, a linker szkript is bonyolultabbá vált, mivel a C-hez adat és bss szekciókra is szükség van. Hozzáadtam
továbbá egy számolást a bss szegmens méretének megállapítására, így egyszerűen hivatkozhatunk rá Assembly-ben, és
nem kell ott molyolni vele.
Fontos, hogy a text szegmens az Assembly kóddal kezdődjön, mivel ez elé raktuk a vermet, ezért kell a KEEP().
Íly módon mind a betöltési címünk 0x80000, akárcsak a `_start` cimke címe és a verem teteje.
Main
----
Végezetül, az első C kódunk. Csak egy végtelen ciklus, de akkor is! :-)

@ -1,34 +0,0 @@
Tutorial 02 - Multicore C
=========================
Now let's try something more complex, shall we? By complex I mean stop the CPU cores just like in the first tutorial,
but this time stop one of them from C!
Start
-----
Now we have to distinguish the cores. For that, we read the *mpidr_el1* system register. If it's not zero, we'll
do the former infinite loop. If it's zero, then we'll call a C function. But for that, we need a proper stack, and a
zerod out bss segment in memory before the call instruction. I've added some more code to the Assembly to do all of
that. In case the C code returns (shouldn't), we also jump to the same infinite loop the other CPU cores running.
Makefile
--------
It became a bit trickier. I've added commands for compiling C sources, but in a comform way. From now on, we
can use the same Makefile for every tutorial, regardless of the number of C sources, and I won't discuss it any
further.
Linker script
-------------
Likewise, the linker script became more complex too, as C needs data and bss sections. I've also added a calculation
for the bss size, so that we can refer to it from the Assembly without any further hassle.
It is important to start the text segment with the Assembly code, because we set the stack right before it, hence
the KEEP(). This way our load address is 0x80000, the same as `_start` label and stack top.
Main
----
Finaly, our first C code. Just an empty loop, but still! :-)

Binary file not shown.

@ -1,29 +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.
*
*/
void main()
{
while(1);
}

@ -0,0 +1,11 @@
[[package]]
name = "kernel8"
version = "0.1.0"
dependencies = [
"raspi3_glue 0.1.0",
]
[[package]]
name = "raspi3_glue"
version = "0.1.0"

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

@ -0,0 +1,61 @@
#
# 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
clippy:
RUSTFLAGS="-C panic=abort" xargo clippy --target=$(TARGET)
clean:
cargo clean
rm -f kernel8

@ -0,0 +1,75 @@
# Tutorial 02 - Multicore Rust
Now let's try something more complex, shall we? By complex I mean stopping the
CPU cores just like in the first tutorial, but this time stop one of them from
**Rust**!
## Glue code
In order to incorporate Rust code, we are setting up a binary crate and add
`#![no_std]` to `main.rs`, since we are writing our own bare metal code and do
not want to rely on the standard library.
However, a lot of steps are needed to make a `no_std` crate build. All of this
and even more is explained in detail in [The Embedonomicon][nom], so please take
your time and read up on it. Afterwards, you can compare to the files in this
crate and see what we actually kept to get our Raspberry Pi 3 tutorial
going. Here's a short summary of the crate's structure:
- `raspi3_glue/`: The extern crate containing glue code as presented in the
Embedonomicon.
- In a small deviation to the Embedonomicon, `lib.rs` also includes
`_boot_cores.S` from the previous tutorial via the [global_asm!][gasm]
macro.
- Therefore, `_boot_cores.S` has been moved into `raspi3_glue/src/`.
- `src`: Source code of our actual crate, currently only containing `main.rs`
executing an endless loop.
[nom]: https://japaric.github.io/embedonomicon/
[gasm]: https://doc.rust-lang.org/unstable-book/language-features/global-asm.html
### Changes to `_boot_cores.S`
In contrast to the previous tutorial, we now we have to [distinguish the
cores][dist]. To do so, we read the [mpidr_el1][mpdir] system register. If it
is not zero, we'll do the former infinite loop. If it is zero, aka we are
executing on core0, then we'll call the Rust `reset()` function. For that, we
also need a proper stack, and have space reserved in memory for the [bss
segment][bss].
We added the `bss` segment to the linker script and export its properties via
`__bss_start` and `__bss_size`, which will be picked up and zeroed out by the
glue code in `raspi3_glue/src/lib.rs`. Additionally, we set the stack in the
Assembly in `_boot_cores.S`, and then finally call the `reset()` function of the
glue code, which in turn calls `main()` after zeroing `bss`. In case the Rust
code returns (which it never should not), we also jump to the same infinite loop
the other CPU cores running.
[dist]: http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.den0024a/CFHCIDCH.html
[mpdir]: http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0500g/BABHBJCI.html
[bss]: https://en.wikipedia.org/wiki/.bss
## Changes to `Makefile`
It became a bit trickier. We've added more targets:
- `kernel8.img` now depends on `kernel8`, which compiles the crate either in
release or debug mode. For the latter, add `DEBUG=1` before invoking make,
e.g. `DEBUG=1 make`
- [clippy] is Rust's linter, and can give you useful advise to improve your
code. Invoke with `make clippy`.
[clippy]: https://github.com/rust-lang-nursery/rust-clippy
From now on, we can use the same Makefile for every tutorial, regardless of the
number of Rust sources, and we won't discuss it any further.
## Changes to `link.ld`
Apart from the added bss section, it is important to start the text segment with
the Assembly code and not the Rust code, because we set the stack right before
it, hence the `KEEP()`. This way, the assembly stays at 0x80000, the same as
`_boot_cores` label and stack top.
## main.rs
Finally, our first Rust code. Just an empty loop, but still! :-)

@ -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.
#
################################################################################

Binary file not shown.

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

@ -1,5 +1,6 @@
/*
* 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
@ -25,9 +26,9 @@
.section ".text.boot"
.global _start
.global _boot_cores
_start:
_boot_cores:
// read cpu id, stop slave cores
mrs x1, mpidr_el1
and x1, x1, #3
@ -38,18 +39,10 @@ _start:
2: // cpu id == 0
// set stack before our code
ldr x1, =_start
ldr x1, =_boot_cores
mov sp, x1
// clear bss
ldr x1, =__bss_start
ldr w2, =__bss_size
3: cbz w2, 4f
str xzr, [x1], #8
sub w2, w2, #1
cbnz w2, 3b
// jump to C code, should not return
4: bl main
// jump to Rust code, should not return
bl reset
// for failsafe, halt this core too
b 1b

@ -0,0 +1,88 @@
/*
* 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]
#![feature(global_asm)]
use core::ptr;
#[lang = "panic_fmt"]
unsafe extern "C" fn panic_fmt(
_args: core::fmt::Arguments,
_file: &'static str,
_line: u32,
_col: u32,
) -> ! {
loop {}
}
#[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
}
}
#[no_mangle]
pub unsafe extern "C" fn reset() -> ! {
extern "C" {
fn main(argc: isize, argv: *const *const u8) -> isize;
static mut __bss_start: u32;
static mut __bss_end: u32;
}
zero_bss(&mut __bss_start, &mut __bss_end);
main(0, ptr::null());
loop {}
}
unsafe fn zero_bss(bss_start: *mut u32, bss_end: *mut u32) {
let mut bss = bss_start;
while bss < bss_end {
// NOTE(ptr::write*) to force aligned stores
// NOTE(volatile) to prevent the compiler from optimizing this into `memclr`
ptr::write_volatile(bss, 0);
bss = bss.offset(1);
}
}
// Disable all cores except core 0, and then jump to reset()
global_asm!(include_str!("boot_cores.S"));

@ -0,0 +1,31 @@
/*
* 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 raspi3_glue;
fn main() {
loop {}
}
Loading…
Cancel
Save