Skip to content

boards: arm: add QEMU support for Cortex-M0 #19479

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

Merged
merged 7 commits into from
Oct 3, 2019
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
/boards/arm/nrf*/ @carlescufi @lemrey @ioannisg
/boards/arm/nucleo*/ @erwango
/boards/arm/nucleo_f401re/ @rsalveti @idlethread
/boards/arm/qemu_cortex_m*/ @ioannisg
/boards/arm/sam4s_xplained/ @fallrisk
/boards/arm/v2m_beetle/ @fvincenzo
/boards/arm/olimexino_stm32/ @ydamigos
Expand Down
7 changes: 7 additions & 0 deletions boards/arm/qemu_cortex_m0/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#
# Copyright (c) 2019, Nordic Semiconductor ASA
#
# SPDX-License-Identifier: Apache-2.0
#

zephyr_sources_if_kconfig( nrf_timer_timer.c)
10 changes: 10 additions & 0 deletions boards/arm/qemu_cortex_m0/Kconfig.board
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Kconfig - QEMU Cortex-M0 board configuration
#
# Copyright (c) 2019 Nordic Semiconductor ASA.
#
# SPDX-License-Identifier: Apache-2.0

config BOARD_QEMU_CORTEX_M0
bool "Cortex-M0 Emulation (QEMU)"
depends on SOC_NRF51822_QFAA
select QEMU_TARGET
42 changes: 42 additions & 0 deletions boards/arm/qemu_cortex_m0/Kconfig.defconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Kconfig - QEMU Cortex-M0 board configuration
#
# Copyright (c) 2019 Nordic Semiconductor ASA.
#
# SPDX-License-Identifier: Apache-2.0

if BOARD_QEMU_CORTEX_M0

config BOARD
default "qemu_cortex_m0"

if SYS_CLOCK_EXISTS

config NRF_TIMER_TIMER
bool "nRF Timer Counter (NRF_TIMER0) Timer"
depends on CLOCK_CONTROL
depends on SOC_COMPATIBLE_NRF
select TICKLESS_CAPABLE
default y
help
This module implements a kernel device driver for the nRF Timer
Counter NRF_TIMER0 and provides the standard "system clock driver"
interfaces.

config NRF_RTC_TIMER
default n

endif # SYS_CLOCK_EXISTS

config SYS_CLOCK_HW_CYCLES_PER_SEC
int
default 1000000

config SYS_CLOCK_TICKS_PER_SEC
int
default 100

config ENTROPY_NRF_FORCE_ALT
bool
default y

endif # BOARD_QEMU_CORTEX_M0
16 changes: 16 additions & 0 deletions boards/arm/qemu_cortex_m0/board.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#
# Copyright (c) 2019, Nordic Semiconductor ASA
#
# SPDX-License-Identifier: Apache-2.0

set(EMU_PLATFORM qemu)

set(QEMU_CPU_TYPE_${ARCH} cortex-m0)
set(QEMU_FLAGS_${ARCH}
-cpu ${QEMU_CPU_TYPE_${ARCH}}
-machine microbit
-nographic
-vga none
)

board_set_debugger_ifnset(qemu)
118 changes: 118 additions & 0 deletions boards/arm/qemu_cortex_m0/doc/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
.. _qemu_cortex_m0:

ARM Cortex-M0 Emulation (QEMU)
##############################

Overview
********

This board configuration will use QEMU to emulate the
BBC Microbit (Nordic nRF51822) platform.

.. figure:: qemu_cortex_m0.png
:width: 600px
:align: center
:alt: Qemu

Qemu (Credit: qemu.org)

This configuration provides support for an ARM Cortex-M0 CPU and these devices:

* Nested Vectored Interrupt Controller
* TIMER (nRF TIMER System Clock)

.. note::
This board configuration makes no claims about its suitability for use
with an actual nRF51 Microbit hardware system, or any other hardware system.

Hardware
********
Supported Features
==================

The following hardware features are supported:

+--------------+------------+----------------------+
| Interface | Controller | Driver/Component |
+==============+============+======================+
| NVIC | on-chip | nested vectored |
| | | interrupt controller |
+--------------+------------+----------------------+
| nRF | on-chip | serial port |
| UART | | |
+--------------+------------+----------------------+
| nRF TIMER | on-chip | system clock |
+--------------+------------+----------------------+

The kernel currently does not support other hardware features on this platform.

Devices
========
System Clock
------------

This board configuration uses a system clock frequency of 1 MHz.

Serial Port
-----------

This board configuration uses a single serial communication channel with the
CPU's UART0.

Known Problems or Limitations
==============================

The following platform features are unsupported:

* Writing to the hardware's flash memory


Programming and Debugging
*************************

Use this configuration to run basic Zephyr applications and kernel tests in the QEMU
emulated environment, for example, with the :ref:`synchronization_sample`:

.. zephyr-app-commands::
:zephyr-app: samples/synchronization
:host-os: unix
:board: qemu_cortex_m0
:goals: run

This will build an image with the synchronization sample app, boot it using
QEMU, and display the following console output:

.. code-block:: console

***** BOOTING ZEPHYR OS v1.8.99 - BUILD: Jun 27 2017 13:09:26 *****
threadA: Hello World from arm!
threadB: Hello World from arm!
threadA: Hello World from arm!
threadB: Hello World from arm!
threadA: Hello World from arm!
threadB: Hello World from arm!
threadA: Hello World from arm!
threadB: Hello World from arm!
threadA: Hello World from arm!
threadB: Hello World from arm!

Exit QEMU by pressing :kbd:`CTRL+A` :kbd:`x`.

Debugging
=========

Refer to the detailed overview about :ref:`application_debugging`.

Networking
==========

References
**********

1. The Definitive Guide to the ARM Cortex-M0, Second Edition by Joseph Yiu (ISBN
978-0-12-803278-7)
2. ARMv6-M Architecture Technical Reference Manual (ARM DDI 0419D 0403D ID051917)
3. Procedure Call Standard for the ARM Architecture (ARM IHI 0042E, current
through ABI release 2.09, 2012/11/30)
4. Cortex-M0 Revision r2p1 Technical Reference Manual (ARM DDI 0432C ID113009)
5. Cortex-M0 Devices Generic User Guide (ARM DUI 0497A ID112109)
Binary file added boards/arm/qemu_cortex_m0/doc/qemu_cortex_m0.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
188 changes: 188 additions & 0 deletions boards/arm/qemu_cortex_m0/nrf_timer_timer.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
/*
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why did the whole driver have to be copied to the board directory?

Copy link
Member Author

@ioannisg ioannisg Oct 1, 2019

Choose a reason for hiding this comment

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

That's because there is no point having a second system clock driver based on Timer peripheral , besides the basic RTC driver. I only added the driver because the qemu does not emulate the RTC :)

Copy link
Collaborator

Choose a reason for hiding this comment

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

Sorry, I'm confused. Where does the emulation come from then? I mean, it looks like nrf_timer_cc_read() is returning your counter value from somewhere, right? So the HAL is doing the emulation? If that's true, why won't the default driver work?

Copy link
Member Author

@ioannisg ioannisg Oct 1, 2019

Choose a reason for hiding this comment

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

@andyross the default nRF driver, used in Zephyr, is based on the NRF RTC peripheral. This peripheral is, however, not supported in the QEMU emulation of the Microbit platform (nRF51). Instead, the QEMU guys have added support for the NRF TIMER peripheral. So, I had to write a system timer driver, based on the NRF TIMER peripheral.

But there is no need to have this driver in the /drivers/timer directory as a generic timer driver; cause there is no use case for that; nRF SoCs use the nRF RTC timer that can utilize the low-frequency low-power clock source. This driver is not, even, specific to Microbit; actual microbit HW boards can use the default nRF RTC driver. The new driver is specific for the qemu_cortex_m0 platform. Its sole purpose is to add system timer support for qemu cortex-m0, so we get coverage on ARMv6-M architecture.

Copy link
Member Author

Choose a reason for hiding this comment

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

nrf_timer_cc_read() is returning your counter value from somewhere, right? So the HAL is doing the emulation? If that's true, why won't the default driver work?

Correct. The default driver is a different peripheral. The "corresponding function, there, is called nrf_rtc_cc_read(). But as I said, this peripheral does not yet work in qemu.

* Copyright (c) 2016-2019 Nordic Semiconductor ASA
* Copyright (c) 2018 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/

#include <soc.h>
#include <drivers/clock_control.h>
#include <drivers/clock_control/nrf_clock_control.h>
#include <drivers/timer/system_timer.h>
#include <sys_clock.h>
#include <hal/nrf_timer.h>
#include <spinlock.h>

#define TIMER NRF_TIMER0

#define COUNTER_MAX 0xffffffff
#define CYC_PER_TICK (sys_clock_hw_cycles_per_sec() \
/ CONFIG_SYS_CLOCK_TICKS_PER_SEC)
#define MAX_TICKS ((COUNTER_MAX - CYC_PER_TICK) / CYC_PER_TICK)

static struct k_spinlock lock;

static u32_t last_count;

static u32_t counter_sub(u32_t a, u32_t b)
{
return (a - b) & COUNTER_MAX;
}

static void set_comparator(u32_t cyc)
{
nrf_timer_cc_write(TIMER, 0, cyc & COUNTER_MAX);
}

static u32_t counter(void)
{
nrf_timer_task_trigger(TIMER, nrf_timer_capture_task_get(1));

return nrf_timer_cc_read(TIMER, 1);
}

void timer0_nrf_isr(void *arg)
{
ARG_UNUSED(arg);
TIMER->EVENTS_COMPARE[0] = 0;

k_spinlock_key_t key = k_spin_lock(&lock);
u32_t t = counter();
u32_t dticks = counter_sub(t, last_count) / CYC_PER_TICK;

last_count += dticks * CYC_PER_TICK;

if (!IS_ENABLED(CONFIG_TICKLESS_KERNEL)) {
u32_t next = last_count + CYC_PER_TICK;

/* As below: we're guaranteed to get an interrupt as
* long as it's set two or more cycles in the future
*/
if (counter_sub(next, t) < 3) {
next += CYC_PER_TICK;
}
set_comparator(next);
}

k_spin_unlock(&lock, key);
z_clock_announce(IS_ENABLED(CONFIG_TICKLESS_KERNEL) ? dticks : 1);
}

int z_clock_driver_init(struct device *device)
{
struct device *clock;

ARG_UNUSED(device);

clock = device_get_binding(DT_INST_0_NORDIC_NRF_CLOCK_LABEL "_16M");
if (!clock) {
return -1;
}

/* turn on clock in blocking mode. */
clock_control_on(clock, (void *)1);

nrf_timer_frequency_set(TIMER, NRF_TIMER_FREQ_1MHz);
nrf_timer_bit_width_set(TIMER, NRF_TIMER_BIT_WIDTH_32);
nrf_timer_cc_write(TIMER, 0, CYC_PER_TICK);
nrf_timer_int_enable(TIMER, TIMER_INTENSET_COMPARE0_Msk);

/* Clear the event flag and possible pending interrupt */
nrf_timer_event_clear(TIMER, NRF_TIMER_EVENT_COMPARE0);
NVIC_ClearPendingIRQ(TIMER0_IRQn);

IRQ_CONNECT(TIMER0_IRQn, 1, timer0_nrf_isr, 0, 0);
irq_enable(TIMER0_IRQn);

nrf_timer_task_trigger(TIMER, NRF_TIMER_TASK_CLEAR);
nrf_timer_task_trigger(TIMER, NRF_TIMER_TASK_START);

if (!IS_ENABLED(TICKLESS_KERNEL)) {
set_comparator(counter() + CYC_PER_TICK);
}

return 0;
}

void z_clock_set_timeout(s32_t ticks, bool idle)
{
ARG_UNUSED(idle);

#ifdef CONFIG_TICKLESS_KERNEL
ticks = (ticks == K_FOREVER) ? MAX_TICKS : ticks;
ticks = MAX(MIN(ticks - 1, (s32_t)MAX_TICKS), 0);

k_spinlock_key_t key = k_spin_lock(&lock);
u32_t cyc, dt, t = counter();
bool zli_fixup = IS_ENABLED(CONFIG_ZERO_LATENCY_IRQS);

/* Round up to next tick boundary */
cyc = ticks * CYC_PER_TICK + 1 + counter_sub(t, last_count);
cyc += (CYC_PER_TICK - 1);
cyc = (cyc / CYC_PER_TICK) * CYC_PER_TICK;
cyc += last_count;

if (counter_sub(cyc, t) > 2) {
set_comparator(cyc);
} else {
set_comparator(cyc);
dt = counter_sub(cyc, counter());
if (dt == 0 || dt > 0x7fffff) {
/* Missed it! */
NVIC_SetPendingIRQ(TIMER0_IRQn);
if (IS_ENABLED(CONFIG_ZERO_LATENCY_IRQS)) {
zli_fixup = false;
}
} else if (dt == 1) {
/* Too soon, interrupt won't arrive. */
set_comparator(cyc + 2);
}
/* Otherwise it was two cycles out, we're fine */
}

#ifdef CONFIG_ZERO_LATENCY_IRQS
/* Failsafe. ZLIs can preempt us even though interrupts are
* masked, blowing up the sensitive timing above. If the
* feature is enabled and we haven't recorded the presence of
* a pending interrupt then we need a final check (in a loop!
* because this too can be interrupted) to confirm that the
* comparator is still in the future. Don't bother being
* fancy with cycle counting here, just set an interrupt
* "soon" that we know will get the timer back to a known
* state. This handles (via some hairy modular expressions)
* the wraparound cases where we are preempted for as much as
* half the counter space.
*/
if (zli_fixup && counter_sub(cyc, counter()) <= 0x7fffff) {
while (counter_sub(cyc, counter() + 2) > 0x7fffff) {
cyc = counter() + 3;
set_comparator(cyc);
}
}
#endif

k_spin_unlock(&lock, key);
#endif /* CONFIG_TICKLESS_KERNEL */
}

u32_t z_clock_elapsed(void)
{
if (!IS_ENABLED(CONFIG_TICKLESS_KERNEL)) {
return 0;
}

k_spinlock_key_t key = k_spin_lock(&lock);
u32_t ret = counter_sub(counter(), last_count) / CYC_PER_TICK;

k_spin_unlock(&lock, key);
return ret;
}

u32_t z_timer_cycle_get_32(void)
{
k_spinlock_key_t key = k_spin_lock(&lock);
u32_t ret = counter_sub(counter(), last_count) + last_count;

k_spin_unlock(&lock, key);
return ret;
}
Loading