You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The entry points to this crate's API are currently mostly extension traits: For a peripheral that this crate provides an API for, an extension trait is defined and implemented. Extension traits are implemented for the peripheral proxys from the nrf52 crate. An extension trait defines a method like constrain or split, which consumes the raw peripheral proxy and returns the HAL API for that peripheral.
That style of API is popular among HAL APIs (I think mostly because most HAL crates start out by copying other HAL crates), but I believe it is flawed. The critical problem is, that the HAL can't control the initial hardware state, making it impossible to give certain guarantees without significant runtime overhead. If we knew for a fact that no one had been messing with the hardware before initializing the HAL API, we could give much stronger safety guarantees, without the additional runtime overhead.
So far this is a rather general argument, but there is one concrete problem that would be weird to solve with the extension trait style: On the nRF52, several groups of peripherals share the same address space, and only one peripheral in each group can be used at a time (see nRF52832 Product Specification, section 15.2).
If we controlled the initial hardware state, we could statically guarantee that the user can enable only one of the peripherals in a given address space at a time. With the the current architecture, I can only think of solutions that are weird (enabling one peripheral in the group consumes the others) or less efficient (check hardware status at runtime.
I propose the following architecture:
We add a new struct, Peripherals, which wraps nrf52::Peripherals and nrf52::CorePeripherals.
Peripherals has two methods that can be used to get an instance, take and steal.
Under the hood, take and steal use the take and steal methods of nrf52::Peripherals/nrf52::CorePeripherals to provide the same guarantees that those methods provide.
Each nRF52 peripheral is available as a field of Peripherals. For peripherals that don't have a HAL API implemented yet, the peripheral proxy from nrf52 is provided directly.
Each peripheral API provides a free method that returns the peripheral proxy from nrf52. This allows the user to fall back to the raw register API, if the HAL API doesn't fulfill their needs.
This allows us to solve the problem of shared address spaces cleanly: We add singleton structs that represent the shared address spaces to Peripherals. Those are consumed by the HAL APIs of the shared peripherals when enabling them.
Summary: By following this architecture, we know that the user hasn't modified the hardware at the point when the HAL API is initialized (unless they have used unsafe means to do so, at which point all bets are off). This allows us to provide safety guarantees that we otherwise couldn't provide easily, or only with additional runtime overhead.
Further reading: I've been using this technique in lpc82x-hal for a while now, and I'm very happy with it. One major problem it solves is pin function assignment. lpc82x-hal statically guarantees that conflicting pin functions can't be assigned to a single pin. Without control over the initial hardware conditions, this could not be achieved without checking every single function assignment register (nRF52 has the same problem, by the way).
The text was updated successfully, but these errors were encountered:
This issue was first opened in the old nrf52-hal repository. Original discussion: jamesmunns/nrf52-hal#6
The entry points to this crate's API are currently mostly extension traits: For a peripheral that this crate provides an API for, an extension trait is defined and implemented. Extension traits are implemented for the peripheral proxys from the
nrf52
crate. An extension trait defines a method likeconstrain
orsplit
, which consumes the raw peripheral proxy and returns the HAL API for that peripheral.That style of API is popular among HAL APIs (I think mostly because most HAL crates start out by copying other HAL crates), but I believe it is flawed. The critical problem is, that the HAL can't control the initial hardware state, making it impossible to give certain guarantees without significant runtime overhead. If we knew for a fact that no one had been messing with the hardware before initializing the HAL API, we could give much stronger safety guarantees, without the additional runtime overhead.
So far this is a rather general argument, but there is one concrete problem that would be weird to solve with the extension trait style: On the nRF52, several groups of peripherals share the same address space, and only one peripheral in each group can be used at a time (see nRF52832 Product Specification, section 15.2).
If we controlled the initial hardware state, we could statically guarantee that the user can enable only one of the peripherals in a given address space at a time. With the the current architecture, I can only think of solutions that are weird (enabling one peripheral in the group consumes the others) or less efficient (check hardware status at runtime.
I propose the following architecture:
Peripherals
, which wrapsnrf52::Peripherals
andnrf52::CorePeripherals
.Peripherals
has two methods that can be used to get an instance,take
andsteal
.take
andsteal
use thetake
andsteal
methods ofnrf52::Peripherals
/nrf52::CorePeripherals
to provide the same guarantees that those methods provide.Peripherals
. For peripherals that don't have a HAL API implemented yet, the peripheral proxy fromnrf52
is provided directly.free
method that returns the peripheral proxy fromnrf52
. This allows the user to fall back to the raw register API, if the HAL API doesn't fulfill their needs.Peripherals
. Those are consumed by the HAL APIs of the shared peripherals when enabling them.Summary: By following this architecture, we know that the user hasn't modified the hardware at the point when the HAL API is initialized (unless they have used
unsafe
means to do so, at which point all bets are off). This allows us to provide safety guarantees that we otherwise couldn't provide easily, or only with additional runtime overhead.Further reading: I've been using this technique in
lpc82x-hal
for a while now, and I'm very happy with it. One major problem it solves is pin function assignment.lpc82x-hal
statically guarantees that conflicting pin functions can't be assigned to a single pin. Without control over the initial hardware conditions, this could not be achieved without checking every single function assignment register (nRF52 has the same problem, by the way).The text was updated successfully, but these errors were encountered: