diff --git a/.github/workflows/build-rpi-baremetal.yml b/.github/workflows/build-rpi-baremetal.yml new file mode 100644 index 00000000..1a3a833b --- /dev/null +++ b/.github/workflows/build-rpi-baremetal.yml @@ -0,0 +1,39 @@ +name: Build Raspberry Pi Baremetal Examples + +on: + push: + branches: ["main"] + pull_request: + branches: ["main"] + schedule: + # Build on Mondays at 9am PST every week + - cron: '0 17 * * 1' +jobs: + build-rpi-baremetal: + runs-on: ubuntu-24.04 + strategy: + fail-fast: false + matrix: + example: [rpi5-blink, rpi4b-blink] + swift: [swift-DEVELOPMENT-SNAPSHOT-2024-12-04-a] + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Install apt dependencies + run: sudo apt-get -qq update && sudo apt-get -qq -y install make llvm + + - name: Install ${{ matrix.swift }} + run: | + wget -q https://download.swift.org/development/ubuntu2404/${{ matrix.swift }}/${{ matrix.swift }}-ubuntu24.04.tar.gz + tar xzf ${{ matrix.swift }}-ubuntu24.04.tar.gz + export PATH="`pwd`/${{ matrix.swift }}-ubuntu24.04/usr/bin/:$PATH" + echo "PATH=$PATH" >> $GITHUB_ENV + which swiftc + swiftc --version + + - name: Build ${{ matrix.example }} + run: | + cd ${{ matrix.example }} + make diff --git a/README.md b/README.md index ff7051a8..86e2bbc6 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,8 @@ Each example in this repository contains build and deployment instructions, howe | [pico-blink](./pico-blink) | Raspberry Pi Pico | None | Blink an LED repeatedly. | | | [pico-w-blink-sdk](./pico-w-blink-sdk) | Raspberry Pi Pico W | Pico SDK | Blink an LED to signal 'SOS' in Morse code repeatedly with Swift & the Pico SDK. | | | [pico2-neopixel](./pico2-neopixel) | Raspberry Pi Pico 2 | None | Control Neopixel LEDs using the RP2350 PIO. | | +| [rpi4b-blink](./rpi4b-blink) | Raspberry Pi 4B | None | Blink the Pi's status green LED repeatedly using Swift MMIO. | | +| [rpi5-blink](./rpi5-blink) | Raspberry Pi 5 | None | Blink the Pi's status green LED repeatedly with Swift MMIO. | | | [stm32-blink](./stm32-blink) | STM32F746G-DISCO | None | Blink an LED repeatedly. | | | [stm32-lcd-logo](./stm32-lcd-logo) | STM32F746G-DISCO | None | Animate the Swift Logo on the built-in LCD. | | | [stm32-neopixel](./stm32-neopixel) | STM32F746G-DISCO | None | Control NeoPixel LEDs using SPI. | | diff --git a/rpi4b-blink/Makefile b/rpi4b-blink/Makefile new file mode 100644 index 00000000..da37442b --- /dev/null +++ b/rpi4b-blink/Makefile @@ -0,0 +1,28 @@ +SWIFT_EXEC ?= $(shell if [ "$(shell uname)" = "Darwin" ]; then xcrun -f swift; else which swift; fi) +CLANG ?= $(shell if [ "$(shell uname)" = "Darwin" ]; then xcrun -f clang; else which clang; fi) +LLVM_OBJCOPY ?= $(shell if [ "$(shell uname)" = "Darwin" ]; then xcrun -f llvm-objcopy; else which llvm-objcopy; fi) + +BUILDROOT := $(shell $(SWIFT_EXEC) build --triple aarch64-none-none-elf -Xswiftc -Xfrontend -Xswiftc -disable-stack-protector --show-bin-path) + +.PHONY: all clean + +all: kernel8.img + +kernel8.img: kernel8.elf + @echo "💾 Converting to binary kernel image with llvm-objcopy..." + $(LLVM_OBJCOPY) -O binary kernel8.elf kernel8.img + @echo "" + @echo "🥳 Done! kernel8.img was saved to this directory." + +kernel8.elf: $(BUILDROOT)/libMainApp.a $(BUILDROOT)/Support.build/boot.S.o link.ld + @echo "🔗 Linking with clang..." + $(CLANG) --target=aarch64-elf -o kernel8.elf $< $^ -fuse-ld=lld -nostdlib -Wl,--unresolved-symbols=ignore-in-object-files -Wl,-T ./link.ld + @echo "" + +$(BUILDROOT)/libMainApp.a $(BUILDROOT)/Support.build/boot.S.o: + @echo "🛠️ Building with Swift Package Manager..." + $(SWIFT_EXEC) build --triple aarch64-none-none-elf -Xswiftc -Xfrontend -Xswiftc -disable-stack-protector + @echo "" + +clean: + rm -rf kernel8.elf kernel8.img .build \ No newline at end of file diff --git a/rpi4b-blink/Package.swift b/rpi4b-blink/Package.swift new file mode 100644 index 00000000..f004c0b8 --- /dev/null +++ b/rpi4b-blink/Package.swift @@ -0,0 +1,36 @@ +// swift-tools-version: 6.1 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "RPi4B-Blink", + platforms: [ + .macOS(.v14) + ], + products: [ + .library( + name: "MainApp", + type: .static, + targets: ["MainApp"]) + ], + dependencies: [ + .package( + url: "https://github.com/apple/swift-mmio.git", + branch: "swift-embedded-examples") + ], + targets: [ + .target( + name: "MainApp", + dependencies: [ + .product(name: "MMIO", package: "swift-mmio") + ], + swiftSettings: [ + .enableExperimentalFeature("Embedded"), + .unsafeFlags(["-Xfrontend", "-function-sections"]), + ] + ), + .target(name: "Support"), + + ] +) diff --git a/rpi4b-blink/README.md b/rpi4b-blink/README.md new file mode 100644 index 00000000..b68de6a5 --- /dev/null +++ b/rpi4b-blink/README.md @@ -0,0 +1,24 @@ +# rpi4b-blink + + + +## Requirements + +- A Raspberry Pi 4B board +- An SD Card, with a Raspberry Pi OS installed (this way, we don't need to create the configuration files from scratch). You may backup `kernel8.img` and `config.txt` if you need the Linux install later, since we will change these files. +- LLVM installed (`brew install llvm`) and added to PATH. This is needed to convert the resulted ELF file to binary image format using `llvm-objcopy`. + +## How to build and run this example: + +- Make sure you have a recent nightly Swift toolchain that has Embedded Swift support. +- Build the program, then copy the kernel image to the SD card. +``` console +$ cd rpi4b-blink +$ export TOOLCHAINS='' # Your Swift nightly toolchain identifier +$ make +$ cp kernel8.img /Volumes/bootfs +``` +- If your original OS is not 64-bit, make sure to set `arm_64bit=1` in `config.txt`. +- Place the SD card in your Raspberry Pi 4B, and connect it to power. +- After the boot sequence, the green (ACT) led will start blinking in a regular pattern. + diff --git a/rpi4b-blink/Sources/MainApp/MainApp.swift b/rpi4b-blink/Sources/MainApp/MainApp.swift new file mode 100644 index 00000000..eaef412f --- /dev/null +++ b/rpi4b-blink/Sources/MainApp/MainApp.swift @@ -0,0 +1,82 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift project authors. +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import MMIO + +@Register(bitWidth: 32) +struct GPSET1 { + @ReadWrite(bits: 10..<11, as: Bool.self) + var set: SET +} + +@Register(bitWidth: 32) +struct GPCLR1 { + @ReadWrite(bits: 10..<11, as: Bool.self) + var clear: CLEAR +} + +@Register(bitWidth: 32) +struct GPFSEL4 { + @ReadWrite(bits: 6..<7, as: Bool.self) + var fsel42b1: FSEL42b1 + @ReadWrite(bits: 7..<8, as: Bool.self) + var fsel42b2: FSEL42b2 + @ReadWrite(bits: 8..<9, as: Bool.self) + var fsel42b3: FSEL42b3 +} + +@RegisterBlock +struct GPIO { + @RegisterBlock(offset: 0x200020) + var gpset1: Register + @RegisterBlock(offset: 0x20002c) + var gpclr1: Register + @RegisterBlock(offset: 0x200010) + var gpfsel4: Register +} + +let gpio = GPIO(unsafeAddress: 0xFE00_0000) + +func setLedOutput() { + gpio.gpfsel4.modify { + // setFunction Select 42 (fsel42) to 001 + $0.fsel42b1 = true + $0.fsel42b2 = false + $0.fsel42b3 = false + } +} + +func ledOn() { + gpio.gpset1.modify { + $0.set = true + } +} + +func ledOff() { + gpio.gpclr1.modify { + $0.clear = true + } +} + +@main +struct Main { + + static func main() { + setLedOutput() + + while true { + ledOn() + for _ in 1..<100000 {} // just a delay + ledOff() + for _ in 1..<100000 {} // just a delay + } + } +} diff --git a/rpi4b-blink/Sources/Support/boot.S b/rpi4b-blink/Sources/Support/boot.S new file mode 100644 index 00000000..0a8f8c39 --- /dev/null +++ b/rpi4b-blink/Sources/Support/boot.S @@ -0,0 +1,41 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift project authors. +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +.section ".text.boot" + +.global _start + +_start: + // Check processor ID is zero (executing on main core), else hang + mrs x1, mpidr_el1 + and x1, x1, #3 + cbz x1, 2f + // We're not on the main core, so hang in an infinite wait loop +1: wfe + b 1b +2: // We're on the main core! + + // Set stack to start below our code + ldr x1, =_start + mov sp, x1 + + // Clean the BSS section + ldr x1, =__bss_start // Start address + ldr w2, =__bss_size // Size of the section +3: cbz w2, 4f // Quit loop if zero + str xzr, [x1], #8 + sub w2, w2, #1 + cbnz w2, 3b // Loop if non-zero + + // Jump to Swift! +4: bl main + // Halt if Swift returns + b 1b diff --git a/rpi4b-blink/Sources/Support/include/boot.h b/rpi4b-blink/Sources/Support/include/boot.h new file mode 100644 index 00000000..e69de29b diff --git a/rpi4b-blink/assets/rpi4.png b/rpi4b-blink/assets/rpi4.png new file mode 100644 index 00000000..1df6f1c3 Binary files /dev/null and b/rpi4b-blink/assets/rpi4.png differ diff --git a/rpi4b-blink/link.ld b/rpi4b-blink/link.ld new file mode 100644 index 00000000..dfbf0227 --- /dev/null +++ b/rpi4b-blink/link.ld @@ -0,0 +1,19 @@ +SECTIONS +{ + . = 0x80000; /* Kernel load address for AArch64 */ + .text : { KEEP(*(.text.boot)) *(.text .text.* .gnu.linkonce.t*) } + .rodata : { *(.rodata .rodata.* .gnu.linkonce.r*) } + PROVIDE(_data = .); + .data : { *(.data .data.* .gnu.linkonce.d*) } + .bss (NOLOAD) : { + . = ALIGN(16); + __bss_start = .; + *(.bss .bss.*) + *(COMMON) + __bss_end = .; + } + _end = .; + + /DISCARD/ : { *(.comment) *(.gnu*) *(.note*) *(.eh_frame*) } +} +__bss_size = (__bss_end - __bss_start)>>3; diff --git a/rpi5-blink/Makefile b/rpi5-blink/Makefile new file mode 100644 index 00000000..da37442b --- /dev/null +++ b/rpi5-blink/Makefile @@ -0,0 +1,28 @@ +SWIFT_EXEC ?= $(shell if [ "$(shell uname)" = "Darwin" ]; then xcrun -f swift; else which swift; fi) +CLANG ?= $(shell if [ "$(shell uname)" = "Darwin" ]; then xcrun -f clang; else which clang; fi) +LLVM_OBJCOPY ?= $(shell if [ "$(shell uname)" = "Darwin" ]; then xcrun -f llvm-objcopy; else which llvm-objcopy; fi) + +BUILDROOT := $(shell $(SWIFT_EXEC) build --triple aarch64-none-none-elf -Xswiftc -Xfrontend -Xswiftc -disable-stack-protector --show-bin-path) + +.PHONY: all clean + +all: kernel8.img + +kernel8.img: kernel8.elf + @echo "💾 Converting to binary kernel image with llvm-objcopy..." + $(LLVM_OBJCOPY) -O binary kernel8.elf kernel8.img + @echo "" + @echo "🥳 Done! kernel8.img was saved to this directory." + +kernel8.elf: $(BUILDROOT)/libMainApp.a $(BUILDROOT)/Support.build/boot.S.o link.ld + @echo "🔗 Linking with clang..." + $(CLANG) --target=aarch64-elf -o kernel8.elf $< $^ -fuse-ld=lld -nostdlib -Wl,--unresolved-symbols=ignore-in-object-files -Wl,-T ./link.ld + @echo "" + +$(BUILDROOT)/libMainApp.a $(BUILDROOT)/Support.build/boot.S.o: + @echo "🛠️ Building with Swift Package Manager..." + $(SWIFT_EXEC) build --triple aarch64-none-none-elf -Xswiftc -Xfrontend -Xswiftc -disable-stack-protector + @echo "" + +clean: + rm -rf kernel8.elf kernel8.img .build \ No newline at end of file diff --git a/rpi5-blink/Package.resolved b/rpi5-blink/Package.resolved new file mode 100644 index 00000000..1a6f57c0 --- /dev/null +++ b/rpi5-blink/Package.resolved @@ -0,0 +1,33 @@ +{ + "originHash" : "193ca3f107e2c8dd2da5d091f6259f64b2cbfd6776d1c26bbcfb195b3a0b5045", + "pins" : [ + { + "identity" : "swift-argument-parser", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-argument-parser.git", + "state" : { + "revision" : "41982a3656a71c768319979febd796c6fd111d5c", + "version" : "1.5.0" + } + }, + { + "identity" : "swift-mmio", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-mmio.git", + "state" : { + "branch" : "swift-embedded-examples", + "revision" : "06d96ed4916739f2edafde87f3951b2d2a04df65" + } + }, + { + "identity" : "swift-syntax", + "kind" : "remoteSourceControl", + "location" : "https://github.com/swiftlang/swift-syntax.git", + "state" : { + "revision" : "0687f71944021d616d34d922343dcef086855920", + "version" : "600.0.1" + } + } + ], + "version" : 3 +} diff --git a/rpi5-blink/Package.swift b/rpi5-blink/Package.swift new file mode 100644 index 00000000..a01f42ef --- /dev/null +++ b/rpi5-blink/Package.swift @@ -0,0 +1,36 @@ +// swift-tools-version: 6.1 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "RPi5-Blink", + platforms: [ + .macOS(.v14) + ], + products: [ + .library( + name: "MainApp", + type: .static, + targets: ["MainApp"]) + ], + dependencies: [ + .package( + url: "https://github.com/apple/swift-mmio.git", + branch: "swift-embedded-examples") + ], + targets: [ + .target( + name: "MainApp", + dependencies: [ + .product(name: "MMIO", package: "swift-mmio") + ], + swiftSettings: [ + .enableExperimentalFeature("Embedded"), + .unsafeFlags(["-Xfrontend", "-function-sections"]), + ] + ), + .target(name: "Support"), + + ] +) diff --git a/rpi5-blink/README.md b/rpi5-blink/README.md new file mode 100644 index 00000000..49c200b2 --- /dev/null +++ b/rpi5-blink/README.md @@ -0,0 +1,25 @@ +# rpi5-blink + + + +## Requirements + +- A Raspberry Pi 5 board +- An SD Card, with a Raspberry Pi OS installed (this way, we don't need to create the configuration files from scratch). You may backup `kernel8.img` and `kernel_2712.img` if you need the Linux install later, since we will change these files. +- LLVM installed (`brew install llvm`) and added to PATH. This is needed to convert the resulted ELF file to binary image format using `llvm-objcopy`. + +## How to build and run this example: + +- Make sure you have a recent nightly Swift toolchain that has Embedded Swift support. +- Build the program, then copy the kernel image to the SD card. +``` console +$ cd rpi5-blink +$ export TOOLCHAINS='' # Your Swift nightly toolchain identifier +$ make +$ cp kernel8.img /Volumes/bootfs # Copy kernel image to SD card +$ rm /Volumes/bootfs/kernel_2712.img # Delete this kernel image so our kernel8.img is used +$ # You can also rename our kernel8.img to kernel_2712.img, or set it to anything you want and specify "kernel=[your-img-name]" in config.txt. +``` +- Place the SD card in your Raspberry Pi 5, and connect it to power. +- After the boot sequence, the green (ACT) led will start blinking in a regular pattern. + diff --git a/rpi5-blink/Sources/MainApp/MainApp.swift b/rpi5-blink/Sources/MainApp/MainApp.swift new file mode 100644 index 00000000..7bbae377 --- /dev/null +++ b/rpi5-blink/Sources/MainApp/MainApp.swift @@ -0,0 +1,56 @@ +import MMIO + +@Register(bitWidth: 32) +struct GIOIODIR { + @ReadWrite(bits: 9..<10, as: Bool.self) + var direction: DIRECTION +} + +@Register(bitWidth: 32) +struct GIODATA { + @ReadWrite(bits: 9..<10, as: Bool.self) + var value: VALUE +} + +@RegisterBlock +struct GPIO { + @RegisterBlock(offset: 0x00008) + var gioiodir: Register + @RegisterBlock(offset: 0x00004) + var giodata: Register +} + +let gpio = GPIO(unsafeAddress: 0x10_7d51_7c00) + +func setLedOutput() { + gpio.gioiodir.modify { + $0.direction = false // 0 is output, 1 is input + } +} + +func ledOn() { + gpio.giodata.modify { + $0.value = true // pin on + } +} + +func ledOff() { + gpio.giodata.modify { + $0.value = false // pin off + } +} + +@main +struct Main { + + static func main() { + setLedOutput() + + while true { + ledOn() + for _ in 1..<100000 {} // just a delay + ledOff() + for _ in 1..<100000 {} // just a delay + } + } +} diff --git a/rpi5-blink/Sources/Support/boot.S b/rpi5-blink/Sources/Support/boot.S new file mode 100644 index 00000000..0a8f8c39 --- /dev/null +++ b/rpi5-blink/Sources/Support/boot.S @@ -0,0 +1,41 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift project authors. +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +.section ".text.boot" + +.global _start + +_start: + // Check processor ID is zero (executing on main core), else hang + mrs x1, mpidr_el1 + and x1, x1, #3 + cbz x1, 2f + // We're not on the main core, so hang in an infinite wait loop +1: wfe + b 1b +2: // We're on the main core! + + // Set stack to start below our code + ldr x1, =_start + mov sp, x1 + + // Clean the BSS section + ldr x1, =__bss_start // Start address + ldr w2, =__bss_size // Size of the section +3: cbz w2, 4f // Quit loop if zero + str xzr, [x1], #8 + sub w2, w2, #1 + cbnz w2, 3b // Loop if non-zero + + // Jump to Swift! +4: bl main + // Halt if Swift returns + b 1b diff --git a/rpi5-blink/Sources/Support/include/boot.h b/rpi5-blink/Sources/Support/include/boot.h new file mode 100644 index 00000000..e69de29b diff --git a/rpi5-blink/assets/raspi5.png b/rpi5-blink/assets/raspi5.png new file mode 100644 index 00000000..3fb4451f Binary files /dev/null and b/rpi5-blink/assets/raspi5.png differ diff --git a/rpi5-blink/link.ld b/rpi5-blink/link.ld new file mode 100644 index 00000000..dfbf0227 --- /dev/null +++ b/rpi5-blink/link.ld @@ -0,0 +1,19 @@ +SECTIONS +{ + . = 0x80000; /* Kernel load address for AArch64 */ + .text : { KEEP(*(.text.boot)) *(.text .text.* .gnu.linkonce.t*) } + .rodata : { *(.rodata .rodata.* .gnu.linkonce.r*) } + PROVIDE(_data = .); + .data : { *(.data .data.* .gnu.linkonce.d*) } + .bss (NOLOAD) : { + . = ALIGN(16); + __bss_start = .; + *(.bss .bss.*) + *(COMMON) + __bss_end = .; + } + _end = .; + + /DISCARD/ : { *(.comment) *(.gnu*) *(.note*) *(.eh_frame*) } +} +__bss_size = (__bss_end - __bss_start)>>3;