Прочитав превосходный http://intermezzos.github.io/, я снова почувствовал радость от работы над моим маленьким ядром, но я решил сосредоточиться на архитектуре ARM, так как с тех пор, как я купил Raspberry, прошло некоторое время. Pi B +, но никогда не играл с ним. Так что я начал читать больше об этом на вики-странице osdev и о том, как начать разработку Raspberry на голом железе. Для этого не так много ссылок, особенно если вы используете Rust. В большинстве руководств / примеров для этой задачи используется C или простая сборка, поэтому было бы интересно попробовать собрать эти вещи, просто используя Rust.

Итак, для начала мы можем сделать встроенные системы привет, мир: мигать светодиодом. Моя цель состояла в том, чтобы добиться этого с минимальным количеством сборки, перенеся все классные вещи в Rust, и, к моему удивлению, этого было довольно легко достичь. Для начала нам понадобится набор инструментов arm-none-eabi для кросс-компиляции, Rust nightly с libcore, скомпилированным для этой архитектуры, загрузочные файлы Raspberry и Raspberry Pi B + для запуска нашего кода. Я проведу процесс.

Настроить кросс-компилятор

Приведенные ниже шаги были выполнены в OSX, но вы можете легко адаптироваться к Linux. Чтобы установить набор инструментов arm-none-eabi, вы можете использовать homebrew или загрузить tarball для OSX, Linux и Windows.
Для homebrew вам просто нужно:

$ brew tap nitsky/stm32
$ brew install arm-none-eabi-gcc
$ arm-none-eabi-gcc --version
arm-none-eabi-gcc (GNU Tools for ARM Embedded Processors) 4.9.3 20150529 (release) [ARM/embedded-4_9-branch revision 227977]

Если вы используете tar-архив, просто скачайте его и добавьте в свой `$ PATH`.

Настроить Rust

Некоторые функции Rust, которые мы собираемся использовать, находятся за воротами функций, поэтому нам нужно использовать Rust каждую ночь. Чтобы установить его, вы можете запустить:

$ curl -sSf https://static.rust-lang.org/rustup.sh | sudo sh -s — — channel=nightly -- yes
$ rustc --version
rustc 1.6.0-nightly (bdfb13518 2015–11–14)

Теперь нам нужно скомпилировать libcore из Rust для нашей целевой архитектуры. Это минимальная свободная от зависимостей основа стандартной библиотеки. Для этого нам нужно скомпилировать его из исходников:

$ git clone https://github.com/rust-lang/rust.git
$ cd rust
$ git checkout bdfb13518
$ mkdir -p /usr/local/lib/rustlib/arm-unknown-linux-gnueabihf/lib
$ rustc --target arm-unknown-linux-gnueabihf -O -Z no-landing-pads src/libcore/lib.rs --out-dir /usr/local/lib/rustlib/arm-unknown-linux-gnueabihf/lib

Обратите внимание, что хэш `git checkout`, который я использовал, тот же, что и у` rustc-version`, вы должны использовать свой собственный, чтобы избежать скрытых ошибок.

Загрузочные файлы Raspberry Pi

Эти файлы можно найти здесь: https://github.com/raspberrypi/firmware/tree/master/boot
Вам понадобятся `bootcode.bin` и` start.elf`. `Kernel.img` будет написан нами.
Имея загрузочную SD-карту, вы можете удалить все файлы и просто поместить туда `bootcode.bin` и` start.elf`. К сожалению, у нас нет доступа к коду этих файлов, и мы пока вынуждены использовать скомпилированную версию. Я планирую подробнее изучить это и попытаться выяснить, как мы можем написать свои собственные загрузочные файлы, но это будет тема для другого поста.

Начиная

После всего этого мы можем приступить к написанию кода. Заставим мигать индикатор ACT, он зеленый рядом с индикатором питания. Таким образом, нашей программе в основном нужно включить его, подождать некоторое время, выключить и зациклить. Итак, запустите ваш редактор и создайте наш файл `kernel.rs`.

// kernel.rs
fn main() {
 println!(“Hello World!”);
}

Это будет стандартный пример hello world, но макрос println! Является частью стандартной библиотеки rust и использует платформенный ввод-вывод для вывода чего-либо на экран, и сейчас у нас _не__ есть платформа для запуска поверх ! Итак, нам нужно сообщить компилятору, что наш автономный код будет автономным:

#![feature(no_std)]
#![crate_type = “staticlib”]
#![no_std]
// kernel.rs
fn main() {
 println!(“Hello World!”);
}

В первой строке мы сообщаем компилятору, что собираемся использовать стробированную функцию `no_std`, с помощью атрибута` #! [Feature ()] `мы сообщаем компилятору такие вещи. С помощью `#! [Crate_type]` мы говорим, что компилируем статическую библиотеку, что в основном означает, что весь наш код является самодостаточным, и, наконец, с помощью `#! [No_std]` мы говорим, что мы ' re не будет использовать стандартную библиотеку, поэтому она не загружается автоматически в процессе прелюдии. Теперь, если мы попытаемся скомпилировать это, мы получим ошибку:

$ rustc --target arm-unknown-linux-gnueabihf kernel.rs
kernel.rs:8:5: 8:12 error: macro undefined: ‘println!’
kernel.rs:8 println!(“Hello World!”);
 ^~~~~~~
error: aborting due to previous error

Как и ожидалось, не существует определенного макроса `println!`, Поскольку нет реализованного ввода-вывода и времени выполнения, мы сами по себе!
Если мы удалим вызов `println!` И попытаемся снова скомпилировать, мы получу еще одну ошибку:

$ rustc --target arm-unknown-linux-gnueabihf kernel.rs
error: language item required, but not found: `panic_fmt`
error: language item required, but not found: `eh_personality`
error: aborting due to 2 previous errors

Это означает, что минимальная среда выполнения Rust ожидает, что эти две функции будут определены. Обе функции используются механизмом отказа / обработкой компилятора. Для простого автономного hello world мы можем просто определить их как пустые реализации:

// kernel.rs
#![feature(no_std, lang_items)]
#![crate_type = “staticlib”]
#![no_std]
pub extern fn main() {
 loop {}
}
#[lang = “eh_personality”]
extern fn eh_personality() {}
#[lang = “panic_fmt”]
extern fn panic_fmt() {}

Атрибут `# [lang]` сообщает компилятору, что эта функция определяется для среды выполнения. Поскольку этот атрибут также является закрытым для функции, нам нужно добавить его в наш список атрибутов `#! [Feature ()]`. Теперь для компиляции нам также нужно сообщить компилятору, что мы просто хотим, чтобы он выдал объектный файл. Объектный файл - это в основном ваш скомпилированный код с определенными символами. Мы собираемся использовать сгенерированный объектный файл для связи с `arm-none-eabi-gcc` и сгенерировать наш файл` kernel.elf`. Мы также добавим флаг `-O`, чтобы удалить некоторые ненужные символы и провести некоторую оптимизацию.
Мы также изменили нашу функцию` main` на общедоступную и внешнюю. Это сообщает компилятору, что наша функция будет использоваться извне.
Итак, теперь мы запускаем:

$ rustc — target arm-unknown-linux-gnueabihf -O — emit=obj kernel.rs

Это сгенерирует наш файл `kernel.o`. Мы можем убедиться, что наша кросс-компиляция работает и генерирует ARM-совместимый машинный код, используя команду `file`:

$ file kernel.o
kernel.o: ELF 32-bit LSB relocatable, ARM, version 1 (GNU/Linux), not stripped

Мы можем проверить символы нашего объектного файла с помощью `arm-none-eabi-nm`:

$ arm-none-eabi-nm kernel.o
00000000 T _ZN4main20h0197e2cdb53c0194haaE
00000000 T rust_begin_unwind
00000000 T rust_eh_personality

Это показывает нам три символа, определенных в нашем исходном коде. Странный - это наша основная функция. Это происходит из-за того, что называется искажением имен, что компилятор делает, чтобы избежать конфликтов имен. Мы хотим, чтобы наши символы были четкими, чтобы внешние инструменты знали, как на них ссылаться. Чтобы компилятор не искажал нашу основную функцию, нам необходимо:

// kernel.rs
#![feature(no_std, lang_items)]
#![crate_type = “staticlib”]
#![no_std]
#[no_mangle]
pub extern fn main() {
 loop {}
}
#[lang = “eh_personality”]
extern fn eh_personality() {}
#[lang = “panic_fmt”]
extern fn panic_fmt() {}

Если мы снова скомпилируем и проверим наши символы, мы увидим:

$ arm-none-eabi-nm kernel.o
00000000 T main
00000000 T rust_begin_unwind
00000000 T rust_eh_personality

Все ясно! Это будет полезно, когда нам нужно вызвать его из сборки.

Заставляем светодиод мигать

Базовый адрес Raspberry Pi B + GPIO - 0x20200000. Регистр, который позволит нам включить наш светодиод, находится по базовому адресу + смещение «0x8», и нам нужно установить 15-й бит в 1, чтобы включить его. Чтобы выключить его, нам нужно установить 15-й бит в 1 на базовом адресе + смещение «0xB». Между этими изменениями нам нужно на некоторое время заблокировать программу. Наш код будет выглядеть так:

// kernel.rs
#![feature(no_std, lang_items, asm)]
#![crate_type = “staticlib”]
#![no_std]
const GPIO_BASE: u32 = 0x20200000;
fn sleep(value: u32) {
 for _ in 1..value {
 unsafe { asm!(“”); }
 }
}
#[no_mangle]
pub extern fn main() {
 let gpio = GPIO_BASE as *const u32;
 let led_on = unsafe { gpio.offset(8) as *mut u32 };
 let led_off = unsafe { gpio.offset(11) as *mut u32 };
loop {
 unsafe { *(led_on) = 1 << 15; }
 sleep(500000);
 unsafe { *(led_off) = 1 << 15; }
 sleep(500000);
 }
}
#[lang = “eh_personality”] extern fn eh_personality() {}
#[lang = “panic_fmt”] extern fn panic_fmt() {}

Мы добавили функцию `sleep`, которая по сути представляет собой пустой цикл для заданного диапазона. Часть `unsafe {asm! (" ")} - это способ, которым я нашел способ избежать исключения компилятором этой части кода на этапе оптимизации, поскольку он, похоже, ничего не делает. В основной функции мы создаем необработанный указатель на адрес GPIO_BASE, а затем получаем изменяемый указатель на места, которые мы хотим изменить, которые должны включать и выключать светодиод. Поскольку вызов смещения включает в себя разыменование необработанного указателя, это небезопасная операция, поэтому блоки `unsafe`. С этим мы можем просто сделать бесконечный цикл, который включает светодиод, ждет, выключает его и ждет.

Подготовка кода к запуску на Raspberry PI

Теперь мы можем скомпилировать наш код для работы на нашей малине:

$ rustc — target arm-unknown-linux-gnueabihf -O — emit=obj kernel.rs

После того как сгенерирован наш файл `kernel.o`, нам нужно связать наш код с помощью` arm-none-eabi-gcc`, чтобы он мог установить наш символ `main` в качестве точки входа для нашей программы, сгенерировать наш` kernel.elf `файл, а затем используйте` arm-none-eabi-objcopy` для генерации нашего финального двоичного файла `kernel.img`:

$ arm-none-eabi-gcc -O0 -mfpu=vfp -mfloat-abi=hard -march=armv6zk -mtune=arm1176jzf-s -nostartfiles kernel.o -o kernel.elf
$ arm-none-eabi-objcopy kernel.elf -O binary kernel.img

Создав наш `kernel.img`, нам просто нужно скопировать его на нашу Raspberry SD вместе с другими загрузочными файлами (` start.elf` и `bootcode.bin`), вставить нашу микро SD обратно в Raspberry PI и включить включите его, чтобы увидеть, как работает наше крошечное ядро ​​и мигает светодиод!

Есть много других интересных вещей, которые мы можем сделать, например, использовать системный таймер на кристалле для реализации нашей функции «сна» или создать некоторые абстракции вершины адресов GPIO для инкапсуляции всех «небезопасных» вызовов в более элегантный API. Но на данный момент я думаю, что это хороший способ начать программирование на «голом железе» с помощью Rust.