Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial support for nRF51822 #17

Merged
merged 5 commits into from Jan 15, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 4 additions & 1 deletion .travis.yml
Expand Up @@ -11,4 +11,7 @@ before_install:
- sudo apt-get install -qq gcc-arm-none-eabi
- mkdir -p build/apps

script: make build/main.elf
script:
- make build/main.elf
- make clean-all
- make PLATFORM=nrf_pca10001 CHIP=nrf51822 APPS=c_blinky
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh great! So this will test compilation for both platforms. I think APPS=c_blinky might be somewhat extraneous since it just gets compiled to a blob and linked -- there is nothing really specific to this platform here.

But no matter, it's at least a place for now to document how to get an example that actually runs on the board.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once UART is implemented for this board, I can simply drop APPS and still have a functional default build. Should be my next task.

2 changes: 2 additions & 0 deletions src/arch/cortex-m0/Makefile.mk
@@ -0,0 +1,2 @@
$(BUILD_DIR)/arch.o: $(SRC_DIR)arch/$(ARCH)/ctx_switch.S $(SRC_DIR)arch/$(ARCH)/syscalls.S
@$(TOOLCHAIN)as -mcpu=$(ARCH) -mthumb $^ -o $@
55 changes: 55 additions & 0 deletions src/arch/cortex-m0/ctx_switch.S
@@ -0,0 +1,55 @@
.cpu cortex-m0
.syntax unified
.thumb
.text

/* Exported functions */
.global SVC_Handler
.globl switch_to_user

.thumb_func
SVC_Handler:
ldr r0, EXC_RETURN_MSP
cmp lr, r0
bne to_kernel
ldr r1, EXC_RETURN_PSP
bx r1

to_kernel:
mrs r0, PSP /* PSP into r0 */
str r0, [sp, #0] /* PSP into Master stack r0 */
ldr r1, EXC_RETURN_MSP
bx r1

EXC_RETURN_MSP:
.word 0xFFFFFFF9
EXC_RETURN_PSP:
.word 0xFFFFFFFD

.thumb_func
/* r0 is top of user stack, r1 is heap base */
switch_to_user:
/* Load bottom of stack into Process Stack Pointer */
msr psp, r0

/* Cortex-M0 can only push registers R0-R7 directly, so move R8-R11 to R0-R3.
* This is equivalent to the 32-bit "push {r4-r11}" instruction. */
push {r4-r7}
mov r4, r8
mov r5, r9
mov r6, r10
mov r7, r11
push {r4-r7}

mov r9, r1
svc 0xff

/* These instructions are equivalent to the 32-bit "pop {r4-r11}" */
pop {r4-r7}
mov r8, r4
mov r9, r5
mov r10, r6
mov r11, r7
pop {r4-r7}

bx lr
34 changes: 34 additions & 0 deletions src/arch/cortex-m0/syscalls.S
@@ -0,0 +1,34 @@
.cpu cortex-m0
.syntax unified
.thumb
.text

.section .syscalls

/* Cortex-M0 can only push/pop registers R0-R7 directly, so move R8-R11 to/from
* R0-R3. This is equivalent to the 32-bit "push/pop {r4-r11}" instructions. */

.macro SYSCALL NAME, NUM
.global \NAME
.thumb_func
\NAME :
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this trick. Perhaps it would make sense to have each architecture define just the macro, and then call the macro (as you do below in lines 31-34) so there is no chance of getting the syscall numbers inconsistent.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used this porting task for testing these fancy features :) I can do that once I confirm that __wait() does indeed not need to save/restore LR (did you test removing it from the push/pop call for storm?)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't tested that yet

push {r4-r7}
mov r4, r8
mov r5, r9
mov r6, r10
mov r7, r11
push {r4-r7}
svc \NUM
pop {r4-r7}
mov r8, r4
mov r9, r5
mov r10, r6
mov r11, r7
pop {r4-r7}
bx lr
.endm

SYSCALL __wait, 0
SYSCALL __subscribe, 1
SYSCALL __command, 2
SYSCALL __allow, 3
19 changes: 19 additions & 0 deletions src/chips/nrf51822/Makefile.mk
@@ -0,0 +1,19 @@
ARCH = cortex-m0

RUSTC_FLAGS += -C opt-level=3 -Z no-landing-pads
RUSTC_FLAGS += --target $(SRC_DIR)chips/$(CHIP)/target.json
RUSTC_FLAGS += -Ctarget-cpu=$(ARCH) -C relocation_model=static
RUSTC_FLAGS += -g -C no-stack-check

CFLAGS += -g -O3 -std=gnu99 -mcpu=$(ARCH) -mthumb -nostdlib -T$(SRC_DIR)chips/$(CHIP)/loader.ld
LDFLAGS += -mcpu=$(ARCH) -mthumb
LOADER = $(SRC_DIR)chips/$(CHIP)/loader.ld
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like we may be able to get away with having most/all of this file unified for all chips and just rely on variables for CHIP, ARCH, etc

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe moved upper to src/chips/Makefile.mk?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest TOCK_CHIP and TOCK_ARCH; that way you can pull them from environment variables if needed.


$(BUILD_DIR)/lib$(CHIP).rlib: $(call rwildcard,$(SRC_DIR)chips/$(CHIP),*.rs) $(BUILD_DIR)/libcore.rlib $(BUILD_DIR)/libhil.rlib $(BUILD_DIR)/libcommon.rlib
@echo "Building $@"
@$(RUSTC) $(RUSTC_FLAGS) --out-dir $(BUILD_DIR) $(SRC_DIR)chips/$(CHIP)/lib.rs

$(BUILD_DIR)/crt1.o: $(SRC_DIR)chips/$(CHIP)/crt1.c
@echo "Building $@"
@$(CC) $(CFLAGS) -c $< -o $@ -lc -lgcc

153 changes: 153 additions & 0 deletions src/chips/nrf51822/crt1.c
@@ -0,0 +1,153 @@
/*
* 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, see <http://www.gnu.org/licenses/>.
*
* Copyright 2014, Michael Andersen <m.andersen@eecs.berkeley.edu>
*/

#include <stdint.h>
#include <string.h>

/* Symbols defined in the linker file */
extern uint32_t _estack;
extern uint32_t _etext;
extern uint32_t _szero;
extern uint32_t _ezero;
extern uint32_t _srelocate;
extern uint32_t _erelocate;

int main(void);

void Dummy_Handler(void)
{
while (1) {
}
}

void Reset_Handler(void);
void NMI_Handler(void) __attribute__ ((weak, alias("Dummy_Handler")));
void HardFault_Handler(void)
__attribute__ ((weak, alias("Dummy_Handler")));
void SVC_Handler(void) __attribute__ ((weak, alias("Dummy_Handler")));
void PendSV_Handler(void) __attribute__ ((weak, alias("Dummy_Handler")));
void SysTick_Handler(void) __attribute__ ((weak, alias("Dummy_Handler")));

typedef void (*interrupt_function_t) (void);

__attribute__ ((section(".vectors")))
interrupt_function_t interrupt_table[] = {
(interrupt_function_t) (&_estack),
Reset_Handler,
NMI_Handler,
HardFault_Handler,
0, 0, 0, 0, 0, 0, 0, /* Reserved */
SVC_Handler,
0, 0, /* Reserved */
PendSV_Handler,
SysTick_Handler,
};

void Reset_Handler(void)
{
uint32_t *pSrc, *pDest;

/* Power on RAM blocks manually (see nRF51822-PAN v2.4, PAN #16). Note
* that xxAA/xxAB variants have only two RAM blocks. For xxAC, change
* to 0x0F. */
*((uint32_t volatile * ) 0x40000524) = 0x03;

/* Move the relocate segment
* This assumes it is located after the
* text segment, which is where the storm
* linker file puts it
*/
pSrc = &_etext;
pDest = &_srelocate;

if (pSrc != pDest) {
for (; pDest < &_erelocate;) {
*pDest++ = *pSrc++;
}
}

/* Clear the zero segment */
for (pDest = &_szero; pDest < &_ezero;) {
*pDest++ = 0;
}

/* Branch to main function */
main();
}

// IMPORTANT!! __aeabi_memset has count and value arguments reversed from ANSI
// memset. TODO(alevy): Why does arm-none-eabi's libc not have __aeabi_memset?
__attribute__ ((weak))
void __aeabi_memset(void *dest, size_t count, int value)
{
memset(dest, value, count);
}

__attribute__ ((weak))
extern void __aeabi_memcpy(void *dest, void *src, unsigned int n)
{
memcpy(dest, src, n);
}

__attribute__ ((weak))
extern void __aeabi_memcpy4(void *dest, void *src, unsigned int n)
{
memcpy(dest, src, n);
}

__attribute__ ((weak))
extern void __aeabi_memcpy8(void *dest, void *src, unsigned int n)
{
memcpy(dest, src, n);
}

__attribute__ ((weak))
extern void __aeabi_memclr(void *dest, size_t n)
{
memset(dest, 0, n);
}

__attribute__ ((weak))
extern void __aeabi_memclr4(void *dest, size_t n)
{
memset(dest, 0, n);
}

__attribute__ ((weak))
extern void __aeabi_memclr8(void *dest, size_t n)
{
memset(dest, 0, n);
}

/* Based on reference code from GCC documentation, see "Legacy __sync Built-in
* Functions for Atomic Memory Access" */

__attribute__ ((weak))
extern uint32_t __sync_fetch_and_add_4(uint32_t * ptr, uint32_t val)
{
uint32_t tmp = *ptr;
*ptr += val;
return tmp;
}

__attribute__ ((weak))
extern uint32_t __sync_fetch_and_sub_4(uint32_t * ptr, uint32_t val)
{
uint32_t tmp = *ptr;
*ptr -= val;
return tmp;
}
121 changes: 121 additions & 0 deletions src/chips/nrf51822/gpio.rs
@@ -0,0 +1,121 @@
use core::mem;
use core::ops::{Index, IndexMut};
use hil;

// Source: https://github.com/hackndev/zinc/tree/master/volatile_cell
#[derive(Copy, Clone)]
#[repr(C)]
pub struct VolatileCell<T> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this abstraction. We should consider pulling it out and using it more widely

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed!

value: T,
}

#[allow(dead_code)]
impl<T> VolatileCell<T> {
#[inline]
pub fn get(&self) -> T {
unsafe {
::core::intrinsics::volatile_load(&self.value)
}
}

#[inline]
pub fn set(&self, value: T) {
unsafe {
::core::intrinsics::volatile_store(&self.value as *const T as *mut T, value)
}
}
}

#[allow(non_snake_case)]
struct GPIO {
_pad0: [u8; 1284],
pub OUT: VolatileCell<u32>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

field names should have snake_case (like variables and function names)

pub OUTSET: VolatileCell<u32>,
pub OUTCLR: VolatileCell<u32>,
pub IN: VolatileCell<u32>,
pub DIR: VolatileCell<u32>,
pub DIRSET: VolatileCell<u32>,
pub DIRCLR: VolatileCell<u32>,
_pad1: [u8; 480],
pub PIN_CNF: [VolatileCell<u32>; 32],
}

#[allow(non_snake_case)]
fn GPIO() -> &'static GPIO {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The connonical way of doing this is:

impl GPIO {
    fn new() -> &'static GPIO {
        unsafe { mem::transmute(0x50000000 as usize) }
    }
}

However, this might be a nice alternative. As I'm suggesting, the obvious thing to do would be to stick the result in a static variable, at which point the contents has to remain an unsafe pointer because we can't call mem::transmute from statics (yet anyway).

This actually ends up being a reasonably nice interface.

unsafe { mem::transmute(0x50000000 as usize) }
}

pub struct GPIOPin {
pin: u8,
}

impl GPIOPin {
const fn new(pin: u8) -> GPIOPin {
GPIOPin { pin: pin }
}
}

impl hil::gpio::GPIOPin for GPIOPin {
fn enable_output(&self) {
GPIO().PIN_CNF[self.pin as usize].set((1 << 0) | (1 << 1) | (0 << 2) | (0 << 8) | (0 << 16));
}

fn enable_input(&self, _mode: hil::gpio::InputMode) {
unimplemented!();
}

fn disable(&self) {
unimplemented!();
}

fn set(&self) {
GPIO().OUTSET.set(1 << self.pin);
}

fn clear(&self) {
GPIO().OUTCLR.set(1 << self.pin);
}

fn toggle(&self) {
unimplemented!();
}

fn read(&self) -> bool {
unimplemented!();
}

fn enable_interrupt(&self, _identifier: usize, _mode: hil::gpio::InterruptMode) {
unimplemented!();
}
}

pub struct Port {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the nrf51822 has only one port, so the abstraction into a Port may not end up being very useful.

pins: [GPIOPin; 32]
}

impl Index<usize> for Port {
type Output = GPIOPin;

fn index(&self, index: usize) -> &GPIOPin {
&self.pins[index]
}
}

impl IndexMut<usize> for Port {
fn index_mut(&mut self, index: usize) -> &mut GPIOPin {
&mut self.pins[index]
}
}

pub static mut PA : Port = Port {
pins: [
GPIOPin::new(0), GPIOPin::new(1), GPIOPin::new(2), GPIOPin::new(3),
GPIOPin::new(4), GPIOPin::new(5), GPIOPin::new(6), GPIOPin::new(7),
GPIOPin::new(8), GPIOPin::new(9), GPIOPin::new(10), GPIOPin::new(11),
GPIOPin::new(12), GPIOPin::new(13), GPIOPin::new(14), GPIOPin::new(15),
GPIOPin::new(16), GPIOPin::new(17), GPIOPin::new(18), GPIOPin::new(19),
GPIOPin::new(20), GPIOPin::new(21), GPIOPin::new(22), GPIOPin::new(23),
GPIOPin::new(24), GPIOPin::new(25), GPIOPin::new(26), GPIOPin::new(27),
GPIOPin::new(28), GPIOPin::new(29), GPIOPin::new(30), GPIOPin::new(31),
],
};