Skip to content
Closed
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
45 changes: 45 additions & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1274,6 +1274,51 @@ dependencies = [
"kernel",
]

[[package]]
name = "qemu_arm_mps2_an385"
version = "0.2.3-dev"
dependencies = [
"cortexm3",
"kernel",
"qemu_arm_mps2_chip",
"qemu_arm_mps2_lib",
"tock_build_scripts",
]

[[package]]
name = "qemu_arm_mps2_an386"
version = "0.2.3-dev"
dependencies = [
"cortexm4",
"kernel",
"qemu_arm_mps2_chip",
"qemu_arm_mps2_lib",
"tock_build_scripts",
]

[[package]]
name = "qemu_arm_mps2_chip"
version = "0.2.3-dev"
dependencies = [
"cortexm",
"cortexm3",
"cortexm4",
"kernel",
]

[[package]]
name = "qemu_arm_mps2_lib"
version = "0.2.3-dev"
dependencies = [
"capsules-core",
"capsules-extra",
"capsules-system",
"components",
"cortexm",
"kernel",
"qemu_arm_mps2_chip",
]

[[package]]
name = "qemu_i486_q35"
version = "0.2.3-dev"
Expand Down
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ members = [
"boards/teensy40",
"boards/nano33ble",
"boards/nano33ble_rev2",
"boards/qemu_arm_mps2_an385",
"boards/qemu_arm_mps2_an386",
"boards/qemu_arm_mps2_lib",
"boards/qemu_i486_q35",
"boards/qemu_rv32_virt",
"boards/qemu_rv64_virt",
Expand Down Expand Up @@ -104,6 +107,7 @@ members = [
"chips/nrf5x-unsafe",
"chips/pci-x86",
"chips/x86_q35",
"chips/qemu_arm_mps2_chip",
"chips/qemu_rv32_virt_chip",
"chips/qemu_virt_chip",
"chips/psc3",
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -626,7 +626,7 @@ define ci_setup_qemu_riscv
@# Use the latest QEMU as it has OpenTitan support
@printf "Building QEMU, this could take a few minutes\n\n"
@git clone https://github.com/qemu/qemu ./tools/ci/qemu 2>/dev/null || echo "qemu already cloned, checking out"
@cd tools/ci/qemu; git checkout ${QEMU_COMMIT_HASH}; ../qemu/configure --target-list=riscv32-softmmu --disable-linux-io-uring --disable-libdaxctl;
@cd tools/ci/qemu; git checkout ${QEMU_COMMIT_HASH}; ../qemu/configure --target-list=riscv32-softmmu,arm-softmmu --disable-linux-io-uring --disable-libdaxctl;
@# Build qemu
@$(MAKE) -C "tools/ci/qemu/build" -j2 || (echo "You might need to install some missing packages" || exit 127)
endef
Expand Down
84 changes: 84 additions & 0 deletions arch/cortex-m/src/support.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,3 +184,87 @@ pub fn is_interrupt_context() -> bool {
pub fn is_interrupt_context() -> bool {
unimplemented!()
}

/// Issue an ARM semihosting call.
///
/// `operation` is the semihosting operation number (e.g. `0x18` for
/// `SYS_EXIT`) and `parameter` is its operation-specific argument.
///
/// Not exposed outside this module: it's a general, unrestricted semihosting
/// interface, whereas callers should only need specific, narrow operations
/// (e.g. [`semihost_terminate`]) that are safe to expose more broadly.
///
/// # Safety
///
/// Only meaningful when running under a semihosting host (e.g. QEMU started
/// with `-semihosting`, or an attached debug probe); otherwise the `bkpt`
/// instruction traps with no host to service it, so the caller must not
/// assume this call takes effect. Depending on `operation`, the host may
/// dereference `parameter` as a pointer (e.g. `SYS_WRITEC`) -- the caller is
/// responsible for passing a value valid for whichever `operation` it
/// selects.
#[cfg(any(doc, all(target_arch = "arm", target_os = "none")))]
#[inline(always)]
unsafe fn semihost_command(operation: u32, parameter: u32) -> u32 {
use core::arch::asm;
let result;

// SAFETY: r0/r1 are set to `operation`/`parameter`, per the ABI ARM
// semihosting defines (ARM's "Semihosting for AArch32 and AArch64"
// specification); the caller is responsible for those being valid for
// the chosen `operation`, per this function's own `# Safety` doc above.
// - OUTPUTS: r0 is overwritten with the semihosting call's result.
// - Options set:
// - nostack: This does not use the stack.
// - Options not set:
// - nomem: not guaranteed in general -- some operations (e.g.
// `SYS_WRITEC`) dereference `parameter` as a pointer.
// - pure, readonly: not applicable, as above.
// - preserves_flags: not documented by the semihosting spec.
// - noreturn: we do fall through (there may be no host to service
// this call at all, e.g. real hardware with no debugger attached).
// - att_syntax: not on arm.
// - raw: not required.
unsafe {
asm!(
"bkpt #0xAB",
inout("r0") operation => result,
in("r1") parameter,
options(nostack),
);
}
result
}

/// Ask a semihosting host to terminate execution, reporting an abnormal
/// exit.
///
/// Issues ARM semihosting's `SYS_EXIT` (`0x18`) with reason
/// `ADP_Stopped_ApplicationExit` (`0x20026`). Intended for use from an
/// already-unrecoverable state, such as a panic handler.
///
/// # Safety
///
/// Only meaningful when running under a semihosting host (e.g. QEMU started
/// with `-semihosting`, or an attached debug probe); otherwise this falls
/// through with no effect. This does not itself diverge -- the caller must
/// not rely on it halting execution, and must not resume normal operation
/// afterwards regardless of whether a host was present to service the call.
#[cfg(any(doc, all(target_arch = "arm", target_os = "none")))]
#[inline(always)]
pub unsafe fn semihost_terminate() {
const SYS_EXIT: u32 = 0x18;
const ADP_STOPPED_APPLICATION_EXIT: u32 = 0x20026;
// SAFETY: fixed, well-formed arguments per `semihost_command`'s safety
// doc above -- `ADP_STOPPED_APPLICATION_EXIT` is a plain reason code,
// not a pointer, so nothing here is dereferenced.
unsafe {
semihost_command(SYS_EXIT, ADP_STOPPED_APPLICATION_EXIT);
}
}

/// Mock implementation for tests on Travis-CI.
#[cfg(not(any(doc, all(target_arch = "arm", target_os = "none"))))]
pub unsafe fn semihost_terminate() {
unimplemented!()
}
2 changes: 2 additions & 0 deletions boards/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ Virtual hardware platforms that are regularly tested as part of the CI.
|-------------------------------------------------------------------|------------------|----------------|------------|-----------------------------|---------------|
| [QEMU RISC-V 32 bit `virt` platform](qemu_rv32_virt/README.md) | RISC-V RV32IMAC | QEMU | custom | custom | Yes (7.2.0) |
| [QEMU RISC-V 64 bit `virt` platform](qemu_rv64_virt/README.md) | RISC-V RV64IMAC | QEMU | custom | custom | Yes |
| [QEMU ARM MPS2 AN385](qemu_arm_mps2_an385/README.md) | ARM Cortex-M3 | QEMU | custom | custom | Yes (10.2.1) |
| [QEMU ARM MPS2 AN386](qemu_arm_mps2_an386/README.md) | ARM Cortex-M4 | QEMU | custom | custom | Yes (10.2.1) |
| [LiteX on Digilent Arty A-7](litex/arty/README.md) | RISC-V RV32IMC | LiteX+VexRiscV | custom | tockloader (flash-file)[^1] | No |
| [Verilated LiteX Simulation](litex/sim/README.md) | RISC-V RV32IMC | LiteX+VexRiscv | custom | tockloader (flash-file)[^1] | No |
| [VeeR EL2 simulation](veer_el2_sim/README.md) | RISC-V RV32IMC | VeeR EL2 | custom | custom | No |
Expand Down
11 changes: 11 additions & 0 deletions boards/qemu_arm_mps2_an385/.cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Licensed under the Apache License, Version 2.0 or the MIT License.
# SPDX-License-Identifier: Apache-2.0 OR MIT
# Copyright Tock Contributors 2026.

include = [
"../../cargo/tock_flags.toml",
"../../cargo/unstable_flags.toml",
]

[build]
target = "thumbv7m-none-eabi"
22 changes: 22 additions & 0 deletions boards/qemu_arm_mps2_an385/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Licensed under the Apache License, Version 2.0 or the MIT License.
# SPDX-License-Identifier: Apache-2.0 OR MIT
# Copyright Tock Contributors 2026.

[package]
name = "qemu_arm_mps2_an385"
version.workspace = true
authors.workspace = true
build = "../build.rs"
edition.workspace = true

[dependencies]
cortexm3 = { path = "../../arch/cortex-m3" }
kernel = { path = "../../kernel" }
qemu_arm_mps2_chip = { path = "../../chips/qemu_arm_mps2_chip", features = ["cortex-m3"] }
qemu_arm_mps2_lib = { path = "../qemu_arm_mps2_lib" }

[build-dependencies]
tock_build_scripts = { path = "../build_scripts" }

[lints]
workspace = true
53 changes: 53 additions & 0 deletions boards/qemu_arm_mps2_an385/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Licensed under the Apache License, Version 2.0 or the MIT License.
# SPDX-License-Identifier: Apache-2.0 OR MIT
# Copyright Tock Contributors 2026.

# Makefile for building the Tock kernel for the qemu-system-arm `mps2-an385`
# (Cortex-M3) platform / machine type.

include ../Makefile.common

QEMU_CMD := qemu-system-arm

# Base address of the "prog" (app) flash region; must match chip_layout.ld.
APP_ADDRESS := 0x00040000

# Peripherals attached by default:
# - CMSDK APB UART0 (attached to stdio)
QEMU_BASE_CMDLINE := \
$(QEMU_CMD) \
-machine mps2-an385 \
-nographic \
-semihosting

# Run the kernel inside a qemu-system-arm "mps2-an385" machine simulation.
.PHONY: run
run: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).elf
@echo
@echo -e "Running $$($(QEMU_CMD) --version | head -n1) with\n"\
" - kernel $<"
@echo "To exit type C-a x"
@echo
$(QEMU_BASE_CMDLINE) -kernel $<

.PHONY: qemu
qemu: run

# Same as `run`, but load an app (or several apps concatenated together,
# e.g. via `cat app1.tbf app2.tbf > apps.bin` — see README.md for the flash
# alignment requirement when combining more than one) at $(APP_ADDRESS).
#
# Uses the `.bin` kernel image (with the placeholder `.apps` section
# stripped by the standard $(OBJCOPY_FLAGS)) rather than the `.elf`: QEMU
# refuses to load two overlapping ROM blobs, and the kernel `.elf`'s own
# 4-byte `.apps` placeholder otherwise collides with $(APP) at the same
# address.
.PHONY: run-app
run-app: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).bin
@echo
@echo -e "Running $$($(QEMU_CMD) --version | head -n1) with\n"\
" - kernel $<\n"\
" - app(s) $(APP)"
@echo "To exit type C-a x"
@echo
$(QEMU_BASE_CMDLINE) -kernel $< -device loader,file=$(APP),addr=$(APP_ADDRESS)
90 changes: 90 additions & 0 deletions boards/qemu_arm_mps2_an385/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
QEMU ARM MPS2 AN385 (Cortex-M3) Platform
=========================================

This board crate targets QEMU's `mps2-an385` machine: an emulation of ARM's
own "MPS2 + AN385" Cortex-M System Design Kit (CMSDK) reference platform,
not a real vendor chip. It is the ARM counterpart to `qemu_rv32_virt` /
`qemu_rv64_virt`: a stable, purely virtual target for exercising the
Cortex-M port under QEMU, useful for CI and kernel development without
access to real ARM hardware.

Currently supported peripherals:

- One CMSDK APB UART (of the five present on the machine), used as the
console/debug UART.
- One CMSDK APB Timer, backing the kernel's `Alarm`/`Time` HIL.
- The `FPGAIO` block's `LED0` register, exposing the machine's two
simulated LEDs. There's no display on this `-nographic` machine to show
them; observe their state by reading the register directly through the
QEMU monitor (`C-a c` to switch from the serial console, then `xp/1xw
0x40028000`).
- One PL022 SPI controller (the "Shield0" instance), run in hardware
loopback mode — see the note below.
- The CMSDK APB Watchdog, backing the kernel's `WatchDog` resource.

Not supported, and not planned for this machine specifically:

- **GPIO.** QEMU emulates all four CMSDK AHB GPIO banks on this machine (and
on every other MPS2/MPS2-TZ machine, including the Cortex-M33 `an505`/
`an521` images) as inert stubs: writes are discarded and reads always
return 0 (see QEMU's `hw/arm/mps2.c`, `create_unimplemented_device(...,
"cmsdk-ahb-gpio", ...)`). There is no way to observe pin state changes
under this QEMU machine, so this board does not implement a GPIO capsule.
LEDs are wired to the separate, genuinely-emulated `FPGAIO` register
instead (see `chips/qemu_arm_mps2_chip/src/led.rs`).
- I2C and the machine's LAN9118 Ethernet controller: present on the memory
map but not driven by this chip crate.

**SPI note**: none of the machine's five PL022 instances have an SSI slave
device attached in QEMU, so a non-loopback transfer just reads back
whatever the empty bus's default is, not meaningful data. The driver
therefore always enables `CR1.LBM` (loopback) — see
`chips/qemu_arm_mps2_chip/src/spi.rs`'s module docs. Chip select is a
zero-sized placeholder for the same reason GPIO is unavailable: there's no
functional GPIO pin to toggle for it, and no real device to select in the
first place.

Running QEMU
------------

To run the board in QEMU, `qemu-system-arm` must be started with the
`-machine mps2-an385` argument and `-kernel $TOCK_KERNEL.elf`. Unlike the
RISC-V `virt` boards, QEMU loads and executes a Cortex-M ELF directly from
its vector table at address 0; no bootloader or `-bios` indirection is
needed. `-nographic` suppresses QEMU's graphical window (there is no display
device on this machine to show regardless).

- **`run`**: Start Tock on an emulated QEMU board:

```
$ make run
[...]
text data bss dec hex filename
57388 0 13356 70744 11458 target/thumbv7m-none-eabi/release/qemu_arm_mps2_an385

Running QEMU emulator version 10.2.1 with
- kernel target/thumbv7m-none-eabi/release/qemu_arm_mps2_an385.elf
To exit type C-a x

QEMU MPS2 AN385 (Cortex-M3) initialization complete.
Entering main loop.
tock$
```

See `qemu_arm_mps2_lib`'s crate docs for the memory layout (shared by both
this board and `qemu_arm_mps2_an386`).

Running an application
-----------------------

- **`run-app`**: Start Tock with one or more apps loaded at
`APP_ADDRESS` (0x00040000):

```
$ make run-app APP=$PATH_TO_APP.tbf
```

To load more than one app at once, concatenate their `.tbf` files (e.g.
`cat app1.tbf app2.tbf > apps.bin`) largest-first: `elf2tab` pads each
`.tbf` to a power-of-two size for MPU alignment, and the loader assumes
that ordering.
29 changes: 29 additions & 0 deletions boards/qemu_arm_mps2_an385/chip_layout.ld
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/* Licensed under the Apache License, Version 2.0 or the MIT License. */
/* SPDX-License-Identifier: Apache-2.0 OR MIT */
/* Copyright Tock Contributors 2026. */

/* Memory layout for the QEMU ARM MPS2 AN385 (Cortex-M3) machine.
*
* QEMU maps code/flash (SSRAM1) at 0x0, up to a hard 4 MiB cap
* (`armv7m_load_kernel(..., 0, 0x400000)` in `hw/arm/mps2.c`), and RAM at
* 0x21000000, backed by a fixed 16 MiB (`default_ram_size` in the same
* file; QEMU errors if `-m` is overridden).
*
* We only expose a more limited portion of the SRAM, to better reflect
* the constraints of typical chips. However, QEMU's emulation does not
* enforce the limited range here (i.e., it will always emulate the full 16 MiB
* of SRAM and allow accesses outside the range we specify here).
*
* rom = 256KB (kernel)
* prog = 256KB (apps)
* ram = 128KB
*/

MEMORY
{
rom (rx) : ORIGIN = 0x00000000, LENGTH = 0x00040000
prog (rx) : ORIGIN = 0x00040000, LENGTH = 0x00040000
ram (rwx) : ORIGIN = 0x21000000, LENGTH = 0x00020000
}

PAGE_SIZE = 2K;
6 changes: 6 additions & 0 deletions boards/qemu_arm_mps2_an385/layout.ld
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/* Licensed under the Apache License, Version 2.0 or the MIT License. */
/* SPDX-License-Identifier: Apache-2.0 OR MIT */
/* Copyright Tock Contributors 2026. */

INCLUDE ./chip_layout.ld
INCLUDE tock_kernel_layout.ld
Loading