Bokeh

Making sense of the mess in my head

Creating a Swift type for button input on nRF52 - Part 2

Introduction

In the last post, Creating a Swift type for button input on nRF52 - Part 1, we worked on code to abstract interacting with a button in Swift code. We had a working prototype, but things fell apart as soon as we cleaned up the code and encapsulated it in a struct.

Investigating the issue

Let’s see if there’s valuable information in the documentation.

/**
 * @brief GPIO callback structure
 *
 * Used to register a callback in the driver instance callback list.
 * As many callbacks as needed can be added as long as each of them
 * are unique pointers of struct gpio_callback.
 * Beware such structure should not be allocated on stack.
 *
 * Note: To help setting it, see gpio_init_callback() below
 */
struct gpio_callback {
	/** This is meant to be used in the driver and the user should not
	 * mess with it (see drivers/gpio/gpio_utils.h)
	 */
	sys_snode_t node;

	/** Actual callback function being called when relevant. */
	gpio_callback_handler_t handler;

	/** A mask of pins the callback is interested in, if 0 the callback
	 * will never be called. Such pin_mask can be modified whenever
	 * necessary by the owner, and thus will affect the handler being
	 * called or not. The selected pins must be configured to trigger
	 * an interrupt.
	 */
	gpio_port_pins_t pin_mask;
};

The documentation for the definition of the gpio_callback gives an interesting piece of information.
Can you spot it?

Beware such structure should not be allocated on stack.

Let’s look at our Swift code again

struct Button {
  var pin_cb_data = gpio_callback()
...

Here we’re creating a gpio_callback as a stored property of a Swift struct. That struct being a value type, it will be allocated on the stack, which the above comment explicitly advises against.
An easy fix is to make the Button a class, which as a reference type, will be allocated on the heap.

Info

The idea that value types are always created on the stack and reference types on the heap is not entirely true. I have not yet been able to find an exact rule but the approximation works for now and for this particular case.

A safer way (which would also allow to keep our Button as a struct) is to explicitly allocate the gpio_callback structure on the heap. This however opens a can of worms that is too much for this article. But it’s a topic we’ll revisit later and in great details.

Beyond printing a message

The code is now working again, but all it does is print a message when the button is pressed. Let’s move to something slightly more interesting and, re-using code from our first example (Controlling an LED using Embedded Swift on nRF52), make the button toggle an LED.

We’ll use a simplified version of the Led code, omitting the error handling and not throwing any errors, as to make things more readable.

We can then update the closure passed to the button so it toggles the LED when the button is pressed.

struct Main {
  static func main() {
    let led = Led(gpio: &led0)

    let myButton = Button(gpio: &button) { _, _, _ in
      led.toggle()
    }
...

But this makes the build fail

…/EmbeddedSwift-nRF52-Examples/Button-and-LED/Main.swift:52:42: error: a C function pointer cannot be formed from a closure that captures context
 50 |     let led = Led(gpio: &led0)
 51 | 
 52 |     let myButton = Button(gpio: &button) { _, _, _ in
    |                                          `- error: a C function pointer cannot be formed from a closure that captures context
 53 |       led.toggle()
 54 |     }

As the error message indicates, the closure cannot capture references to variables from its context.
We have two main possibilities to fix this:

  • make the variable that we want to reference globally available
  • somehow pass the reference to the C function

Let’s explore these 2 options in more details.

Making our led variable globally available

There are multiple ways to achieve this.

Global variable

The first and easiest way is to just make it a global variable for the module.

Moving the let led = Led(gpio: &led0) declaration outside of the Main struct makes it available from within the closure.

The code would look something like this

let led = Led(gpio: &led0)

@main
struct Main {
  static func main() {
    let myButton = Button(gpio: &button) { _, _, _ in
      led.toggle()
    }
...

This builds and works fine, and is OK for simple cases but it’s definitely not the best code organisation and does not scale very well.

Static variable on a type

Instead, we can define a static constant property on the existing Led type

struct Led {
  static let firstLed = Led(gpio: &led0)
...

and refer to it from the closure

let myButton = Button(gpio: &button) { _, _, _ in
  Led.firstLed.toggle()
}

If you don’t want to modify the Led type and keep it generic (for instance because it’s coming from an external package), you can create a dedicated type (an enum in this case) to hold the static constants.

enum Leds {
  static let firstLed = Led(gpio: &led0)
}
let myButton = Button(gpio: &button) { _, _, _ in
  Leds.firstLed.toggle()
}

Application logic singleton

In all the above examples, we’ve used different strategies to allow global access to a single definition of the Led constant.

If we would have multiple elements to expose, or we would want to encapsulate those elements behind some more advanced logic, we can create a class to host that logic and make it a singleton.
The singleton pattern ensures that the class would only have one instance and provides a global access point to that instance.

The code could look something like this.

class AppLogic {
    static let shared = AppLogic()
    private init() { }

    let firstLed = Led(gpio: &led0)

    func toggleLed() {
      firstLed.toggle()
    }
}

With the button closure using the logic exposed by the singleton.

let myButton = Button(gpio: &button) { _, _, _ in
  AppState.shared.toggleLed()
}

Info

In the above implementation of the singleton, the AppLogic instance is only created when the shared static property is first accessed. And so is the initialisation of our Led instance. This means this only happens on the first button press.
If this is not acceptable for some reason, you could always read the shared property in your main function to trigger the instantiation earlier.

Passing the reference to the C function

Exploring the possibilities

The general idea is that the required information comes via the parameter(s) of the C function (closure) that’s being called. As we’re implementing a callback whose prototype is defined by the Zephyr SDK, we cannot add the parameter ourself.
But is there already something in the API that we can use? Let’s take another look at the callback prototype.

/**
 * @typedef gpio_callback_handler_t
 * @brief Define the application callback handler function signature
 *
 * @param port Device struct for the GPIO device.
 * @param cb Original struct gpio_callback owning this handler
 * @param pins Mask of pins that triggers the callback handler
 *
 * Note: cb pointer can be used to retrieve private data through
 * CONTAINER_OF() if original struct gpio_callback is stored in
 * another private structure.
 */
typedef void (*gpio_callback_handler_t)(const struct device *port,
					struct gpio_callback *cb,
					gpio_port_pins_t pins);

Do you see the note in the documentation ? The API has foreseen a way to pass private data to this handler. Let’s try to figure out how that works.

Searching the Zephyr documentation for CONTAINER_OF, we find some information in Zephyr API Documentation: Utility Functions. The basic idea is that given an element of a struct, we’re able to get back to the containing struct.

Let’s look at the example from the documentation

 struct foo {
    int bar;
 };

 struct foo my_foo;
 int *ptr = &my_foo.bar;

 struct foo *container = CONTAINER_OF(ptr, struct foo, bar);

Here we see that, knowing the address of bar, an element of my_foo, the CONTAINER_OF macro will return the address of that my_foo struct.

If the struct foo would contain a struct gpio_callback instead of an int, that would read

 struct foo {
    struct gpio_callback cb;
 };

 struct foo my_foo;
 int *ptr = &my_foo.cb;

 struct foo *container = CONTAINER_OF(ptr, struct foo, cb);

We’re getting to a containing structure from the gpio_callback element, which is one of the parameters we receive in our GPIO handler function.

Understanding CONTAINER_OF

Looking at the definition of CONTAINER_OF, we see it’s a “complex” macro

#define CONTAINER_OF(ptr, type, field)                               \
	({                                                           \
		CONTAINER_OF_VALIDATE(ptr, type, field)              \
		((type *)(((char *)(ptr)) - offsetof(type, field))); \
	})

and we’ve seen in the previous post that Swift C Interop only supports simple macros.

Instead of trying to see how we could call this macro from Swift, let’s understand what it does and re-implement the same behaviour using Swift native constructs.

Setting aside some validation, the macro implements some basic pointer arithmetics. Considering the containing struct is represented by a contiguous block of memory, knowing the address of one of its property and the offset of that property from the start, by subtracting that offset from the address we get back the starting address of the memory block i.e. the address of the containing structure.

Let’s extend our foo structure with some int property, create a variable of that type and see how the maths work

struct foo {
   int bar;
   struct gpio_callback cb;
};

struct foo my_foo;
int *ptr = &my_foo.cb;

Let’s pretend my_foo is at address 0x00001000. The addresses of the structure look like

Address Element
0x00001000 bar
0x00001004 cb

ptr is the address of cb and would have value 0x00001004. offsetof(struct foo, cb) would return 4. Subtracting that from ptr gives 0x00001000, which is indeed the address of my_foo.

Pointer arithmetics in Swift

Swift provides several types for Manual Memory Management, you can spot them by their name following the Unsafe…Pointer pattern.
Of the different flavours available, UnsafeRawPointer is one of the simplest, providing untyped access to raw bytes in memory. It corresponds to a void * pointer in C.
Raw pointers support basic arithmetic operations at the byte level. When you add to or subtract from a raw pointer, the result is a new raw pointer offset by that number of bytes.
Using UnsafeRawPointerwill allow us to implement the above subtraction from our CONTAINER_OF formula.

What about the offsetof() call ? This is an ANSI C macro that evaluates to the offset (in bytes) of a given member within a struct or union type.
It happens that Swift provides the same functionality with the offset(of:) function of the MemoryLayout type.

Finally, once done with the pointer arithmetic, we would like to “cast” the result to the type of our container structure. We can achieve that using the assumingMemoryBound(to:) on our UnsafeRawPointer, that will return us an UnsafePointer typed to the requested type.

Putting it all together

In our project, we wanted the Led to be available with the button handler closure. We thus create a container structure, that in addition to the original callback structure, contains our Led.

struct ExtendedCallback {
  var callback: gpio_callback
  var led: Led
}

We then add, on that structure, a function to perform the pointer arithmetic, and return, given a pointer to the callback element, the ExtendedCallback.

struct ExtendedCallback {
  var callback: gpio_callback
  var led: Led

  static func containerFrom(callback ptr: UnsafePointer<gpio_callback>) -> ExtendedCallback {
    let containerPtr = UnsafeRawPointer(ptr) - MemoryLayout.offset(of: \Self.callback)!
    return containerPtr.assumingMemoryBound(to: Self.self).pointee
  }
}

With that in place, we can now access the Led from within the button closure.

let myButton = Button(gpio: &button) { _, callback, _ in
  ExtendedCallback.containerFrom(callback: callback!).led.toggle()
}

Of course, we need to properly initialise this property. We edit our Button class so that, the pin_cb_data property is initialized with an ExtendedCallback, encapsulating both our initial gpio_callbackand our Led.

var pin_cb_data = ExtendedCallback(callback: gpio_callback(), led: Led(gpio: &led0))

Within our initializer, we need to adapt the calls that are taking the gpio_callback as parameters, as we still need to pass that original structure and not the container.

gpio_init_callback(&pin_cb_data.callback, self.btnHandler, bit(gpio.pointee.pin))

gpio_add_callback(gpio.pointee.port, &pin_cb_data.callback)

With that in place, we can build the project and test that indeed, the LED toggles as the user pushes the button.

We’ve been able to use this strategy here because the API was designed in such a way that some object accessible to us during initialization, was propagated to the callback. This will not always be the case and you’ll need to adapt based on the specific use case.

Making it generic

There are 2 shortcomings to the above code:

  • we have hardcoded in the ExtendedCallback structure that the extra information is a Led
  • that Led is instantiated as part of the Button code

Let’s use generics to get rid of those limitations.

Info

Embedded Swift supports generics but uses specialisation i.e. the generic type is known at compile-time at the caller site and the compiler creates a specialized instantiation of the generic type that is no longer generic.

Generic instantiation at run-time is not supported, as that would require access to type metadata, which does not exist in Embedded Swift.

We start by making the extra parameter generic

struct ExtendedCallback<T> {
  var callback: gpio_callback
  var context: T
...

We also need to modify the Button class in accordance

class Button<T> {
  let gpio: UnsafePointer<gpio_dt_spec>
  let btnHandler: GpioCallbackHandler
  var pin_cb_data: ExtendedCallback<T>

  init(gpio: UnsafePointer<gpio_dt_spec>, context: T, btnHandler: GpioCallbackHandler) {
    self.gpio = gpio
    self.btnHandler = btnHandler
    self.pin_cb_data = ExtendedCallback(callback: gpio_callback(), context: context)
...

We make the class generic, the pin_cb_data is now the generic version of ExtendedCallback and that property is initialized in the initializer, receiving the context as a parameter.

The closure needs a slight update, as ExtendedCallback must be specialized for us to call its the static function.

let myButton = Button<Led>(gpio: &button, context: Led(gpio: &led0)) { _, callback, _ in
  ExtendedCallback<Led>.containerFrom(callback: callback!).context.toggle()
}

Conclusion

In this post, we fixed the issue we encountered last time when encapsulating our logic within a Button type.
We then encountered a limitation of closures used as C function pointers and explored several strategies on how the closure can access information defined outside of its scope.

With these core concepts covered in this post and the previous two, we have some building blocks to start tackling more complex and interesting projects.
Stay tuned as we’ll cover that in a future post.