Skip to content

Modernize rpi-5 build #128

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

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open

Modernize rpi-5 build #128

wants to merge 6 commits into from

Conversation

rauhul
Copy link
Collaborator

@rauhul rauhul commented May 8, 2025

Updates the rpi-5-blink example to link using SwiftPM and use a toolset.json.

@rauhul rauhul requested a review from kubamracek as a code owner May 8, 2025 02:40
@rauhul
Copy link
Collaborator Author

rauhul commented May 8, 2025

@iCMDdev can you verify that this example still works with the build changes?

@iCMDdev
Copy link
Contributor

iCMDdev commented May 8, 2025

Hi, sure! I'll test later today, or in the worst case, tomorrow, and let you know.

@iCMDdev
Copy link
Contributor

iCMDdev commented May 10, 2025

Sorry for the delay. I tried to test it since yesterday but something didn't work out (likely on my end). I'll test again tomorrow and let you know how it goes.

@iCMDdev
Copy link
Contributor

iCMDdev commented May 12, 2025

Unfortunately I think something isn't right on the Pi 5 (LED doesn't blink); I'll try to compare the resulted binaries. Currently the old build fails for me, so I'll have to retry with an older Swift dev build.

Looking at the output of objdump -D Application it seems that the entrypoint / address of main is 00000000000800a0 and I'm not really sure if that's all right or not (looking at the linker script, entrypoint should be 0x80000). As I said, I wanted to compare with the previous working build and I currently don't have it, but I'll try do this asap.

@rauhul
Copy link
Collaborator Author

rauhul commented May 13, 2025

Unfortunately I think something isn't right on the Pi 5 (LED doesn't blink); I'll try to compare the resulted binaries. Currently the old build fails for me, so I'll have to retry with an older Swift dev build.

Looking at the output of objdump -D Application it seems that the entrypoint / address of main is 00000000000800a0 and I'm not really sure if that's all right or not (looking at the linker script, entrypoint should be 0x80000). As I said, I wanted to compare with the previous working build and I currently don't have it, but I'll try do this asap.

Thanks so much for taking a look; would you be interesting in trying to fix/land this change? we're trying to move all the SwiftPM sample to avoid manually linking

@iCMDdev
Copy link
Contributor

iCMDdev commented May 13, 2025

Unfortunately I think something isn't right on the Pi 5 (LED doesn't blink); I'll try to compare the resulted binaries. Currently the old build fails for me, so I'll have to retry with an older Swift dev build.
Looking at the output of objdump -D Application it seems that the entrypoint / address of main is 00000000000800a0 and I'm not really sure if that's all right or not (looking at the linker script, entrypoint should be 0x80000). As I said, I wanted to compare with the previous working build and I currently don't have it, but I'll try do this asap.

Thanks so much for taking a look; would you be interesting in trying to fix/land this change? we're trying to move all the SwiftPM sample to avoid manually linking

Yes, surely! I was actually looking into it right now.

Looking at the old (main branch) resulted build files, I'm pretty sure that indeed, the problem is related to the entrypoint. Here's the old elf file, with entrypoint at 0x80000:

cmd@ ~/Downloads/rpi-5-blink-binary % objdump -D kernel8.elf 

kernel8.elf:	file format elf64-littleaarch64

Disassembly of section .text:

0000000000080000 <_start>:
   80000: d53800a1     	mrs	x1, MPIDR_EL1
   80004: 92400421     	and	x1, x1, #0x3
   80008: b4000061     	cbz	x1, 0x80014 <_start+0x14>
   8000c: d503205f     	wfe
   80010: 17ffffff     	b	0x8000c <_start+0xc>
   80014: 58000161     	ldr	x1, 0x80040 <_start+0x40>
   80018: 9100003f     	mov	sp, x1
   8001c: 58000161     	ldr	x1, 0x80048 <_start+0x48>
   80020: 18000182     	ldr	w2, 0x80050 <_start+0x50>
   80024: 34000082     	cbz	w2, 0x80034 <_start+0x34>
   80028: f800843f     	str	xzr, [x1], #0x8
   8002c: 51000442     	sub	w2, w2, #0x1
   80030: 35ffffa2     	cbnz	w2, 0x80024 <_start+0x24>
   80034: 940003ee     	bl	0x80fec <main>
   80038: 17fffff5     	b	0x8000c <_start+0xc>
   8003c: 00000000     	udf	#0x0
   
   ... (there's more)

And here is the current, new build result:

cmd@ ~/Downloads/rpi-5-blink % objdump -D Application

Application:	file format elf64-littleaarch64

Disassembly of section .swift_modhash:

0000000000080000 <$d>:
   80000: b8 37 7d b5  	.word	0xb57d37b8
   80004: 35 04 4e b2  	.word	0xb24e0435
   80008: 8d 6c 19 ca  	.word	0xca196c8d
   8000c: cf a6 20 4d  	.word	0x4d20a6cf
   
... (more .words)

Disassembly of section .text:

00000000000800a0 <main>:
   800a0: d28f8088     	mov	x8, #0x7c04             // =31748
   800a4: f2afaa28     	movk	x8, #0x7d51, lsl #16
   800a8: f2c00208     	movk	x8, #0x10, lsl #32
   800ac: b9400509     	ldr	w9, [x8, #0x4]
   800b0: 12167929     	and	w9, w9, #0xfffffdff
   800b4: b9000509     	str	w9, [x8, #0x4]
   800b8: b9400109     	ldr	w9, [x8]
   800bc: 32170129     	orr	w9, w9, #0x200
   800c0: b9000109     	str	w9, [x8]
   800c4: b9400109     	ldr	w9, [x8]
   800c8: 12167929     	and	w9, w9, #0xfffffdff
   800cc: b9000109     	str	w9, [x8]
   800d0: 17fffffa     	b	0x800b8 <main+0x18>

So yes, as I suspected, the entrypoint is likely the issue. This is likely caused by the linking step, and I'll look into it. It's a bit strange, since the same linker script is used and this shouldn't have happened.

Also: In the new build, there's no _start symbol in the resulted ELF file, so that might also be part of the culprit.

If you have any ideas why this could happen, I'm open to hearing them!

@rauhul rauhul force-pushed the rpi-swiftpm-link branch from ad1958d to 54fd8ff Compare May 18, 2025 17:28
@rauhul
Copy link
Collaborator Author

rauhul commented May 18, 2025

@iCMDdev I think I fixed those issues!

  1. I forgot to link Support into Application 🤦
  2. I updated the linker map to discard .swift_modhash

@iCMDdev
Copy link
Contributor

iCMDdev commented May 18, 2025

Great! I'll test again shortly.

@iCMDdev
Copy link
Contributor

iCMDdev commented May 18, 2025

I think we're definitely on the right track and these changes are good, but from my testing it stil doesn't blink. Here's what I've noticed:

  • Both builds (old and new) now have the _start function. And it is identical. This is great, the previous updated version did not have it.
  • I noticed the new .text section is significantly shoter, containing the _start and main symbols, while the original build included many other symbols such as MMIO-related ones, or embedded-specific symbols such as _swift_embedded_set_heap_object_metadata_pointer (random example that I picked from the original build). This leads me to believe that a module or dependency issue might be the root cause of the problem. Here's the current .text in the new build:
% objdump -D Application
Disassembly of section .text:

0000000000080000 <_start>:
   80000: d53800a1     	mrs	x1, MPIDR_EL1
   80004: 92400421     	and	x1, x1, #0x3
   80008: b4000061     	cbz	x1, 0x80014 <_start+0x14>
   8000c: d503205f     	wfe
   80010: 17ffffff     	b	0x8000c <_start+0xc>
   80014: 58000161     	ldr	x1, 0x80040 <_start+0x40>
   80018: 9100003f     	mov	sp, x1
   8001c: 58000161     	ldr	x1, 0x80048 <_start+0x48>
   80020: 18000182     	ldr	w2, 0x80050 <_start+0x50>
   80024: 34000082     	cbz	w2, 0x80034 <_start+0x34>
   80028: f800843f     	str	xzr, [x1], #0x8
   8002c: 51000442     	sub	w2, w2, #0x1
   80030: 35ffffa2     	cbnz	w2, 0x80024 <_start+0x24>
   80034: 94000008     	bl	0x80054 <main>
   80038: 17fffff5     	b	0x8000c <_start+0xc>
   8003c: 00000000     	udf	#0x0

0000000000080040 <$d>:
   80040: 00 00 08 00  	.word	0x00080000
   80044: 00 00 00 00  	.word	0x00000000
   80048: 90 00 08 00  	.word	0x00080090
   8004c: 00 00 00 00  	.word	0x00000000
   80050: 00 00 00 00  	.word	0x00000000

0000000000080054 <main>:
   80054: d28f8088     	mov	x8, #0x7c04             // =31748
   80058: f2afaa28     	movk	x8, #0x7d51, lsl #16
   8005c: f2c00208     	movk	x8, #0x10, lsl #32
   80060: b9400509     	ldr	w9, [x8, #0x4]
   80064: 12167929     	and	w9, w9, #0xfffffdff
   80068: b9000509     	str	w9, [x8, #0x4]
   8006c: b9400109     	ldr	w9, [x8]
   80070: 32170129     	orr	w9, w9, #0x200
   80074: b9000109     	str	w9, [x8]
   80078: b9400109     	ldr	w9, [x8]
   8007c: 12167929     	and	w9, w9, #0xfffffdff
   80080: b9000109     	str	w9, [x8]
   80084: 17fffffa     	b	0x8006c <main+0x18>

(That's all of it. No mentions of MMIO, compared to the original build, which had symbols such as <$e4MMIO18ContiguousBitFieldPAAE8bitWidthSivgZ7MainApp7GIODATAV5VALUEO_Ttgq5+0x40>).

  • I assume .swift_modhash are related to module hashes. I'm not sure if they are necessary at runtime; if they are, I think we could relocate this section after .text.

@rauhul
Copy link
Collaborator Author

rauhul commented May 18, 2025

@iCMDdev can you try the old version but in a release build? It would be good to know if that version still blinks. I'd expect the .text section of the old version in release to be similar to the new version.


I do really appreciate the time and help here!

@iCMDdev
Copy link
Contributor

iCMDdev commented May 18, 2025

@iCMDdev can you try the old version but in a release build? It would be good to know if that version still blinks. I'd expect the .text section of the old version in release to be similar to the new version.

I do really appreciate the time and help here!

Sure!

@iCMDdev
Copy link
Contributor

iCMDdev commented May 18, 2025

It looks like indeed, the old build doesn't work either when compiled in release mode (with .swift_modhash removed as well; since building in release also made that section appear at 0x80000 before .text).

While the old build .elf file, compiled in release mode, still includes some extra symbols such as swift_initStackObject, the assembly instrctions within _start and main are nearly, if not identical, so I suppose the old build didn't strip out the unnecessary symbols generated by the standard libraries. Which is fine for the new build to do.

@iCMDdev
Copy link
Contributor

iCMDdev commented May 19, 2025

I think I figured out something by looking at the assembly. I was previously using the following as a simple workaround for a sleep() function:

for _ in 1..<100000 {} 

But that gets optimized in the production assembly:

main:
    // set x8 to the MMIO address = 0x00000010_7d51_7c04
    mov	x8, #0x7c04
    movk	x8, #0x7d51, lsl #16
    movk	x8, #0x10, lsl #32

    // x8 is now set to GPIO base address + 4 = GIODATA

    // x8 + 4 =  GPIOIODIR (GPIO direction register)
    ldr	w9, [x8, #0x4]      // load current value of GIOIODIR
    and	w9, w9, #0xfffffdff // clear bit 9, which is for GPIO9 (value 0 = output)
    str	w9, [x8, #0x4]      // store result back to GIOIODIR
    
    // x8 + 0 = GPIOIODATA (GPIO data register)
    // set GPIO9 (onboard green ACT LED) high
    ldr	w9, [x8]            // load current value of GIODATA
<main+0x18>:
    orr	w9, w9, #0x200      // set bit 9, which is for GPIO9 (value 1 = high)
    str	w9, [x8]            // store result back to GIODATA

    // NOTICE: There's no delay here!!

    // x8 + 0 = GPIOIODATA (GPIO data register)
    // set GPIO9 (onboard green ACT LED) low
    ldr	w9, [x8]            // load current value of GIODATA
    and	w9, w9, #0xfffffdff // clear bit 9, which is for GPIO9 (value 0 = low)
    str	w9, [x8]            // store result back to GIODATA
    b	0x8006c <main+0x18> // loop back; set GPIO9 high again

And, to be honest, I forgot to rewrite it in a more graceful manner. So, @rauhul, do you have any tips on how to write such a sleep() function in a better way (and, perhaps, keep it slightly simple - I guess we shouldn't use timers, etc.)?

TL;DR I suppose the code is actually running but it blinks really fast, without a delay; unfortunately I can't find my SWD debugger to attach to my Pi and confirm it that way (edit: reading this, I guess I could just turn the LED off without turning it back on and confirm it like that, but I'll try the hack mentioned below).

@iCMDdev
Copy link
Contributor

iCMDdev commented May 19, 2025

I've seen that the STM32 example defines a nop() function in the Bridging Header, which is then used in Swift like this:

for _ in 0..<10_000 * milliseconds {
    nop()
}

So I assume that would work.

@rauhul
Copy link
Collaborator Author

rauhul commented May 19, 2025

@iCMDdev our hack solution in the other examples is to use inline asm from a c header to expose nop as a function you can call in loop.

@iCMDdev
Copy link
Contributor

iCMDdev commented May 19, 2025

@iCMDdev our hack solution in the other examples is to use inline asm from a c header to expose nop as a function you can call in loop.

Yeah, just noticed that (also sent my comment above right before seeing this). I'll try to test that.

@iCMDdev
Copy link
Contributor

iCMDdev commented May 19, 2025

@rauhul Great news: I confirmed it works!!! I made the follwing changes (for the Pi 5 example, Pi 4 should be similar):

// Support.h
#pragma once

static inline __attribute((always_inline)) void nop() {
    asm volatile("nop");
}
// Application.swift
import MMIO
import Support // for nop()

@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<GIOIODIR>
  @RegisterBlock(offset: 0x00004)
  var giodata: Register<GIODATA>
}

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 Application {
  static func main() {
    setLedOutput()
    while true {
      ledOn()
      for _ in 1..<100000 { nop() } // added nop and increased delay
      ledOff()
      for _ in 1..<100000 { nop() } // added nop and increased delay
    }
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants