Raspberry Pi: Difference between revisions
[unchecked revision] | [unchecked revision] |
(→boot.S) |
|||
Line 300: | Line 300: | ||
} |
} |
||
const char hello[] = " |
const char hello[] = "\n\rHello World\n\r"; |
||
const char halting[] = "\n\r*** system halting ***"; |
|||
void main(void) { |
void main(void) { |
||
const char *p = hello; |
const char *p = hello; |
||
while(*p) { |
while(*p) { |
||
UART::putc(*p++); |
|||
⚫ | |||
} |
|||
// Wait a bit |
|||
⚫ | |||
p = halting; |
|||
while(*p) { |
|||
UART::putc(*p++); |
UART::putc(*p++); |
||
} |
} |
Revision as of 23:14, 12 January 2013
Intro
This is a tutorial on bare-metal [OS] development on the Raspberry Pi. This tutorial is written specifically for the Raspberry Pi Model B Rev 2 because the author has no other hardware to test on. But so far the models are basically identical for the purpose of this tutorial (Rev 1 has 256MB ram, Model A has no ethernet).
This is the authors very first ARM system and we learn as we write without any prior knowledge about arm. Experience in Linux/Unix (very important) and C/C++ language (incredibly important, including how to use inline asm) is assumed and required.
Materials
You will need a:
- Raspberry Pi, RPi in short.
- SD Card to boot from.
- A SD Card reader so you can write to the SD Card from your developement system.
- A serial adaptor for the RPi.
- Power from an external power supply, usb or the serial adaptor.
- Debian, or other *nix with arm-binutils [guide will document installation for Debian]
- A copy of the ARM ARM (ARM Architecture Reference Manual) (download a PDF, I'm using the one for ARMv6 and not the newer ARMv7 one because RPi only has an ARMv6 cpu)
Serial adaptor
The RPi has 2 serials. A basic UART for console/debug output (UART0) and a full featured serial (UART1). This tutorial only concerns itself with UART0, called simply UART or serial port. UART1 is ignored from now on. The basic UART onboard uses a 3.3V TTL and is connected to some of the GPIO pins labeled "P1" on the board. x86 PCs and MACs do use 5V TTL so you need some adaptor to convert the TTL. I recommend a USB to TTL Serial Cable - Debug / Console Cable for Raspberry Pi with seperate connectors per lead, like commercial RPi serial adaptor. Which is then connected to the RPi like this.
Note: The serial adaptor I use provides both a 0V and 5V lead (black and red) which provide power to the RPi. No extra power supply is needed besides this.
Preparations
Testing your hardware/serial port
First things first, you're going to want to make sure all your hardware works. Connect your serial adaptor to the RPi and boot up the official Raspian image. The boot process will output to both the serial and the HDMI and will start a getty on the serial. Set up your serial port, however yours works, and open up minicom. Make sure you have flow control turned off. Ensure you can run at 115200 baud, 8N1, which is what the RPi uses.
If you get 'Permission Denied' do NOT become root! This is unnecessary. Instead do:
sudo adduser <user> dialout
This will let your user use serial ports without needing root.
Or do ls -l /dev/ttyS* to find out the group that own the device, then add you into that group under /etc/group (normally the group is uucp)
If you started minicom only after the RPi has booted then simply press return in minicom so the getty will output a fresh login prompt. Otherwise wait for the boot messages to appear. If you don't get any output then connect the RPi to a monitor to check that it actually boots, check your connections and minicom settings.
Building a cross compiler
Like me you are probably using a x86 PC as main machine and want to edit and compile the source on that and the RPi is an ARM cpu so you absoluetly need a cross compiler. But even if you are developing on an ARM system it is still a good idea to build a cross compiler to avoid accidentally mixing stuff from your developement system with your own kernel. Follow the steps from [GCC_Cross-Compiler] to build your own cross compiler but use:
export TARGET=arm-none-eabi
Now we are ready to start.
Bare minimum kernel
Lets start with a minimum of 4 files. The kernel is going to use a subset of C++, meaning C++ without exceptions and without runtime types. The main function will be in main.cc. Before the main function can be called though some things have to be set up using assembly. This will be placed in boot.S. On top of that we also need a linker script and a Makefile to build the kernel and need to create an include directory for later use.
main.cc
/* main.cc - the entry point for the kernel */
/* Copyright (C) 2013 Goswin von Brederlow <goswin-v-b@web.de>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
extern "C" {
void main(void);
}
void main(void) {
}
boot.S
/* boot.S - assembly startup code */
/* Copyright (C) 2013 Goswin von Brederlow <goswin-v-b@web.de>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
// To keep this in the first portion of the binary.
.text
// Make Start global.
.globl Start
// Entry point for the kernel.
// r15 -> should begin execution at 0x8000.
Start:
// Setup the stack.
mov sp, #0x8000
// FIXME: Clear out bss.
// Call main
bl main
// halt
halt:
wfe
b halt
link-arm-eabi.ld
* link-arm-eabi.ld - linker script for arm eabi */
/* Copyright (C) 2013 Goswin von Brederlow <goswin-v-b@web.de>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
ENTRY(Start)
SECTIONS
{
/* Starts at LOADER_ADDR. */
.text 0x8000 :
_text_start = .;
_start = .;
{
*(.text)
}
. = ALIGN(4096); /* align to page size */
_text_end = .;
.rodata:
_rodata_start = .;
{
*(.rodata)
}
. = ALIGN(4096); /* align to page size */
_rodata_end = .;
.data :
_data_start = .;
{
*(.data)
}
. = ALIGN(4096); /* align to page size */
_data_end = .;
.bss :
_bss_start = .;
{
bss = .;
*(.bss)
}
. = ALIGN(4096); /* align to page size */
_bss_end = .;
_end = .;
}
Makefile
# Makefile - build script */
# Copyright (C) 2013 Goswin von Brederlow <goswin-v-b@web.de>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
# build environment
PREFIX ?= /usr/local/cross
ARMGNU ?= $(PREFIX)/bin/arm-none-eabi
# source files
SOURCES_ASM := $(wildcard *.S)
SOURCES_CC := $(wildcard *.cc)
# object files
OBJS := $(patsubst %.S,%.o,$(SOURCES_ASM))
OBJS += $(patsubst %.cc,%.o,$(SOURCES_CC))
# Build flags
DEPENDFLAGS := -MD -MP
INCLUDES := -I include
BASEFLAGS := -O2 -fpic -pedantic -pedantic-errors -nostdlib
BASEFLAGS += -nostartfiles -ffreestanding -nodefaultlibs
BASEFLAGS += -fno-builtin -fomit-frame-pointer
WARNFLAGS := -Wall -Wextra -Wshadow -Wcast-align -Wwrite-strings
WARNFLAGS += -Wredundant-decls -Winline
WARNFLAGS += -Wno-attributes -Wno-deprecated-declarations
WARNFLAGS += -Wno-div-by-zero -Wno-endif-labels -Wfloat-equal
WARNFLAGS += -Wformat=2 -Wno-format-extra-args -Winit-self
WARNFLAGS += -Winvalid-pch -Wmissing-format-attribute
WARNFLAGS += -Wmissing-include-dirs -Wno-multichar
WARNFLAGS += -Wredundant-decls -Wshadow
WARNFLAGS += -Wno-sign-compare -Wswitch -Wsystem-headers -Wundef
WARNFLAGS += -Wno-pragmas -Wno-unused-but-set-parameter
WARNFLAGS += -Wno-unused-but-set-variable -Wno-unused-result
WARNFLAGS += -Wwrite-strings -Wdisabled-optimization -Wpointer-arith
WARNFLAGS += -Werror
ASFLAGS := $(INCLUDES) $(DEPENDFLAGS) -D__ASSEMBLY__
CXXFLAGS := $(INCLUDES) $(DEPENDFLAGS) $(BASEFLAGS) $(WARNFLAGS)
CXXFLAGS += -fno-exceptions -std=c++0x
# build rules
all: kernel.img
include $(wildcard *.d)
kernel.elf: $(OBJS) link-arm-eabi.ld
$(ARMGNU)-ld $(OBJS) -Tlink-arm-eabi.ld -o $@
kernel.img: kernel.elf
$(ARMGNU)-objcopy kernel.elf -O binary kernel.img
clean:
$(RM) -f $(OBJS) kernel.elf kernel.img
dist-clean: clean
$(RM) -f *.d
# C++.
%.o: %.cc Makefile
$(ARMGNU)-g++ $(CXXFLAGS) -c $< -o $@
# AS.
%.o: %.S Makefile
$(ARMGNU)-g++ $(ASFLAGS) -c $< -o $@
And there you go. Try building it. A minimum kernel that does absolutely nothing.
Hello World kernel
Lets make the kernel do something. Lets say hello to the world using the serial port.
main.cc
/* main.cc - the entry point for the kernel */
/* Copyright (C) 2013 Goswin von Brederlow <goswin-v-b@web.de>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <uart.h>
extern "C" {
void main(void);
}
const char hello[] = "\n\rHello World\n\r";
const char halting[] = "\n\r*** system halting ***";
void main(void) {
const char *p = hello;
while(*p) {
UART::putc(*p++);
}
// Wait a bit
for(volatile int i = 0; i < 10000000; ++i) { }
p = halting;
while(*p) {
UART::putc(*p++);
}
}
include/mmio.h
/* mmio.h - access to MMIO registers */
/* Copyright (C) 2013 Goswin von Brederlow <goswin-v-b@web.de>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef MMIO_H
#define MMIO_H
#include <stdint.h>
namespace MMIO {
// write to MMIO register
static inline void write(uint32_t reg, uint32_t data) {
uint32_t *ptr = (uint32_t*)reg;
asm volatile("str %[data], [%[reg]]"
: : [reg]"r"(ptr), [data]"r"(data));
}
// read from MMIO register
static inline uint32_t read(uint32_t reg) {
uint32_t *ptr = (uint32_t*)reg;
uint32_t data;
asm volatile("ldr %[data], [%[reg]]"
: [data]"=r"(data) : [reg]"r"(ptr));
return data;
}
}
#endif // #ifndef MMIO_H
include/uart.h
/* uart.h - UART initialization & communication */
/* Copyright (C) 2013 Goswin von Brederlow <goswin-v-b@web.de>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef UART_H
#define UART_H
#include <stdint.h>
namespace UART {
/*
* Transmit a byte via UART0.
* uint8_t Byte: byte to send.
*/
void putc(uint8_t byte);
/*
* Receive a byte via UART0.
*
* Returns:
* uint8_t: byte received.
*/
uint8_t getc(void);
}
#endif // #ifndef UART_H
uart.cc
/* uart.cc - UART initialization & communication */
/* Copyright (C) 2013 Goswin von Brederlow <goswin-v-b@web.de>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <stdint.h>
#include <mmio.h>
#include <uart.h>
namespace UART {
enum {
// The base address for MMIO for UART.
UART0_BASE = 0x20201000,
// The offsets for reach register for the UART.
UART0_DR = (UART0_BASE + 0x00),
UART0_RSRECR = (UART0_BASE + 0x04),
UART0_FR = (UART0_BASE + 0x18),
UART0_ILPR = (UART0_BASE + 0x20),
UART0_IBRD = (UART0_BASE + 0x24),
UART0_FBRD = (UART0_BASE + 0x28),
UART0_LCRH = (UART0_BASE + 0x2C),
UART0_CR = (UART0_BASE + 0x30),
UART0_IFLS = (UART0_BASE + 0x34),
UART0_IMSC = (UART0_BASE + 0x38),
UART0_RIS = (UART0_BASE + 0x3C),
UART0_MIS = (UART0_BASE + 0x40),
UART0_ICR = (UART0_BASE + 0x44),
UART0_DMACR = (UART0_BASE + 0x48),
UART0_ITCR = (UART0_BASE + 0x80),
UART0_ITIP = (UART0_BASE + 0x84),
UART0_ITOP = (UART0_BASE + 0x88),
UART0_TDR = (UART0_BASE + 0x8C),
};
/*
* Transmit a byte via UART0.
* uint8_t Byte: byte to send.
*/
void putc(uint8_t byte) {
while(true) {
if (!(MMIORead(UART0_FR) & (1 << 5))) {
break;
}
}
MMIOWrite(UART0_DR, byte);
}
/*
* Receive a byte via UART0.
*
* Returns:
* uint8_t: byte received.
*/
uint8_t getc(void) {
while(true) {
if (!(MMIORead(UART0_FR) & (1 << 4))) {
break;
}
}
return MMIORead(UART0_DR);
}
}