2015-05-14

Adding libcore

In the last article, we translated our C program to Rust. The resulting Rust code, however, was kind of a mess. It had to use weird intrinsic functions for basic tasks, that simple operators would have been more suited for.

As I explained back then, the reason for this is that the Rust language relies on its standard library for a lot of very basic features, that in other languages are built into the language itself. And of course, we couldn't just use Rust's standard library, as it is designed to be run on top of an operating system.

There is a solution though: While Rust's standard library looks like a monolithic block from the outside, it actually is very modular, built up from a few different libraries under the hood. The most basic of those is libcore. libcore provides basic pieces of functionality, while being completely platform-agnostic. It doesn't even rely a heap being available[1], making it suitable for event the most constrained of embedded platforms.

Before I show you how to use it though, I'd like to talk about my motivation first. So far, I've insisted on removing all third-party libraries and replaced them with custom code. Why not re-implement libcore, as we re-implemented those other libraries?

The answer to this question is pretty simple: Re-implementing libcore would teach us about Rust, but my goal here is to learn about the hardware. The libraries that we removed up till now took away our control. libcore is different, in that is provides us with tools to help write our code without obscuring what is happening. If any of you are interested in the inner workings of Rust, then writing your own libcore is probably a great project. But it's out of scope for what I'm doing here.

Now that this is out of the way, let's get to the new program:

#![feature(core, intrinsics, lang_items, no_std)]

#![no_main]
#![no_std]
			

The only change in our header here is that core has been added to the list of features. libcore is still considered unstable, which means it might be changed in future versions. We need to declare the unstable features we need before the compiler lets us use them[2].

extern crate core;

use core::prelude::*;
			

Here we're declaring that we want to use libcore and import everything from its prelude. Normally, we have to include everything we'd like to use explicitly. The prelude contains types and functions from libcore that are so essential that every program should have access to them.

#[lang = "panic_fmt"]
pub extern fn panic_fmt() { loop {} }

#[lang = "stack_exhausted"]
pub extern fn stack_exhausted() { loop {} }

#[lang = "eh_personality"]
pub extern fn eh_personality() { loop {} }
			

These functions are used in libcore, but can't be defined there. libcore can't know their appropriate implementations. For example, code within libcore should be able to cause a program panic. What that actually means though, depends on the platform. On a desktop operating system, a panicking process would print an error message and stop executing. For our case, just stopping the program (by looping forever) is good enough for now.

#[no_mangle] pub extern fn __aeabi_unwind_cpp_pr0() { loop {} }
			

Unfortunately we still have to declare this mystery function. There's one important change to it: To make the function usable by code from libcore, it needs to be public (the pub extern bit).

// This is the top of the stack, as provided to us by the linker.
extern {
	static _estack: u32;
}


// Type declaration for the vector table.
pub struct VectorTable {
	pub initial_stack_pointer_value: &'static u32,
	pub reset_handler              : fn(),

	pub other_interrupt_vectors: [u32; 44],
}

unsafe impl Sync for VectorTable {}


// The actual vector table.
#[link_section=".vectors"]
pub static VECTOR_TABLE: VectorTable = VectorTable {
	initial_stack_pointer_value: &_estack,
	reset_handler              : start,
	other_interrupt_vectors    : [0; 44],
};


// Addresses of several registers used to control parallel I/O.
const PB_PIO_ENABLE       : *mut u32 = 0x400E1000 as *mut u32;
const PB_OUTPUT_ENABLE    : *mut u32 = 0x400E1010 as *mut u32;
const PB_SET_OUTPUT_DATA  : *mut u32 = 0x400E1030 as *mut u32;
const PB_CLEAR_OUTPUT_DATA: *mut u32 = 0x400E1034 as *mut u32;

// Bit mask for PB27. This is pin 13 (the built-in LED) on the Arduino Due.
const PB27_MASK: u32 = 0x08000000;

// Addresses of several registers used to control the real-time timer.
const TIMER_MODE_REGISTER : *mut   u32 = 0x400E1A30 as *mut   u32;
const TIMER_VALUE_REGISTER: *const u32 = 0x400E1A38 as *const u32;
			

All of this is largely unchanged, and there's not a lot to say about it.

// As the name suggests, this function sleeps for a given number of
// milliseconds. Our replacement for Arduino's delay function.
fn sleep_ms(milliseconds: u32) {
	unsafe {
		let sleep_until = *TIMER_VALUE_REGISTER + milliseconds;
		while *TIMER_VALUE_REGISTER < sleep_until {}
	}
}
			

This is a nice example of what libcore does for us. After the translation from C, this function became really weird, having to use intrinsic compiler functions. Now with libcore, we can use operators like + and < again.

// This function is the entry point for our application and the handler
// function for the reset interrupt.
fn start() {
	unsafe {
		// Enable PB27 (pin 13) and configure it for output.
		*PB_PIO_ENABLE    = PB27_MASK;
		*PB_OUTPUT_ENABLE = PB27_MASK;

		// Set the timer to a resolution of a millisecond.
		*TIMER_MODE_REGISTER = 0x00000020;

		// Continuously set and clear output on PB27 (pin 13). This
		// blinks the Due's built-in LED, which is the single
		// purpose of this program.
		loop {
			*PB_SET_OUTPUT_DATA = PB27_MASK;
			sleep_ms(200);
			*PB_CLEAR_OUTPUT_DATA = PB27_MASK;
			sleep_ms(800);
		}
	}
}
			

The rest of the program is also unchanged.

Now that we have an updated program, we still need to build it. I'm developing on a x86-64 machine, so the libcore that comes with my installation of Rust has been compiled for that platform. Since we need an ARM version, we're going to have to build it ourselves. Fortunately, this is really easy: Just clone the Rust repository[3] and build it with the following command[4]:

rustc \
	--target=target.json \
	-o output/libcore.rlib \
	vendor/rust/src/libcore/lib.rs
			

This requires an output directory to work, so be sure to create that first. target.json refers to the same target specification used in the last article, with one minor change: I had to change arch from armv7-m to the less specific arm. I don't know why that's required, as our own program works with both settings. libcore wouldn't compile without that change though.

Once libcore is compiled, we can build our program:

rustc \
	--target=target.json \
	-C link-args="-Tlinker-script.ld" \
	-L output \
	-o output/blink.elf \
	blink/blink.rs
			

This command is essentially unchanged, except that we have to tell the Rust compiler to look in the output directory for libraries. The rest of the program upload is, yet again, unchanged.

And that's it for today. As always, the full code is available on GitHub. See you next time!