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
RFC: New clocking API #429
Comments
@vccggorski and @vcchtjader, as discussed on Matrix, I already have a nearly-complete Before digging in, I would recommend you understand what I've started to call the I've read your proposal in a little bit more detail, and I have to say that you hit the nail on the head in every count.
For the I tried to add some comments just now. I hope the are helpful. After a few minutes, I realized that I could spend hours writing comments for everything, so I decided to stop for now. Honestly, that's why I haven't worked on it in a while. There's just so much documentation to write. But I think that documentation is really important if we want this to be accessible to people. Have a look and let me know what you think. Maybe we can collaborate and use this as a starting point. @jbeaurivage and I collaborated quite a bit on the |
I forgot to include the link. Also, here is a good example of the entire module in action. It's extensively commented, so I think it should be easy to follow. This is an entirely real use case. It's almost exactly my actual clocking structure for my work project. Also, FYI, the compile-time locking trait ( |
@vccggorski, we missed celebrating the one year anniversary of this PR! 😢 We'll get there eventually. 🥳 |
You're right, damn :/ I have to focus and allocate some time for it, argh. |
Add a new API for the `clock` module on `thumbv7em` targets Add a new `clock` module API that enforces correctness by construction. It is impossible to create an invalid clock tree without using `unsafe`. Specifically, use typed tokens and clocks to guarantee memory safety and act as proof that a clock is correctly configured, and use type-level numbers to count consumer clocks at compile-time and restrict a given clock's API while it is in use. This commit comes after two years of work, starting with #272, then with #429 and culminating with PR #450. Co-authored-by: Gabriel Górski <gabriel.gorski@volvocars.com> Co-authored-by: Henrik Tjäder <henrik.tjader@volvocars.com>
Closed with #450 |
Add a new API for the `clock` module on `thumbv7em` targets Add a new `clock` module API that enforces correctness by construction. It is impossible to create an invalid clock tree without using `unsafe`. Specifically, use typed tokens and clocks to guarantee memory safety and act as proof that a clock is correctly configured, and use type-level numbers to count consumer clocks at compile-time and restrict a given clock's API while it is in use. This commit comes after two years of work, starting with #272, then with #429 and culminating with PR #450. Co-authored-by: Bradley Harden <bradleyharden@gmail.com> Co-authored-by: Gabriel Górski <gabriel.gorski@volvocars.com> Co-authored-by: Henrik Tjäder <henrik.tjader@volvocars.com>
Summary
New clocking API allowing to build a typesafe chain of dependent clock sources, generic clock generators and peripheral channels.
Motivation
Current clocking API assumes the following workflow:
This API has the following limitations.
GenericClockController
constructor configures and provides an opinionated set of clock sources and generic clock generators that cannot be changed afterwards. Following clock configuration is set forthumbv7em
.GCLK0
(Generic Clock Generator 0) is driven byDFLL48M
(48MHz) throughGCLK5
(divider: 24 -> 2MHz) throughPCh1
(Peripheral Channel 1) throughDPLL0
(multiplier: 60 -> 120MHz)GCLK1
is driven either byXOSC32K
(use_external_32kosc) orOSCULP32K
(use_internal_32kosc)Clock Source - GCLK
pair, it is impossible to change it later.GCLK - PeripheralChannel
pair, it is impossible to change it later.Main clock locked to 120MHz by HAL, even though being acceptable in basic scenarios, is a serious design flaw that severely diminishes flexibility of a HAL and might either encourage users to hack unsafe solutions on top of existing abstractions or discourage them from using the HAL altogether.
Having these points in mind and also the fact that current clocking API implementation would benefit from refactoring anyway, striving for improvement seems to be fully motivated and justified.
Detailed design
Introduction
ATSAMD clocking system assumes 3 types of major building blocks:
ClkSrc
)GClk
)PCh
)Properties of ATSAMD clocking system:
GClk
s depend onClkSrc
s in aN:1
relationshipPCh
s depend onGClk
s in aM:1
relationshipClkSrc
s can depend on somePCh
sSpecifically:
thumbv7em
-based MCUsPCh:PCh0
can serve as a reference clock provider forClkSrc:DFLL48M
PCh:PCh1
can serve as a reference clock provider forClkSrc:DPLL0
PCh:PCh2
can serve as a reference clock provider forClkSrc:DPLL1
PCh:PCh3
? Take a look at #unresolved-questionsthumbv6m
-based MCUsPCh:PCh0
(onthumbv6m
Peripheral Channels are called GCLK Multiplexers) can serve as a reference forClkSrc:DFLL48M
ClkSrc
can depend on some otherClkSrc
sSpecifically
thumbv7em
-based MCUsClkSrc:XOSC32K
can serve as a clock source forClkSrc:DPLL{0, 1}
ClkSrc:XOSC{0, 1}
can serve as a clock source forClkSrc:DPLL{0, 1}
Mapping HW model to Rust
Updated: 2021-06-08
In order to model one-to-many type of relationship between dependencies a few additional concepts/abstractions were introduced.
Enabled
type and its helper types/traitsEnabled
type wrapper represents a clocking component in its enabled state while also holding an information about current amount of dependencies (usually0
upon construction). This amount of dependencies is embedded into the type via second generic parameter leveragingtypenum::{UInt, UTerm}
types.Via specialized implementation blocks for this type (like for
Enabled<Gclk<G, H, U0>
) it is possible to introduce special behaviour; e.g.fn disable
will only exist for clocking component havingU0
current users.SourceMarker
trait and its subtraitsThis marker trait unifies family of more specific traits. These ones are essential during a construction
fn ::{new, enable}
and deconstructionfn ::{free, disable}
of clocking components as they provide information to the constructed/deconstructed type what its source is (shown in the example later) and which variant of source (associated constant) is applicable while performing a HW register write.These are implemented by marker types corresponding to existing clocking abstractions e.g.:
Source
trait and its subtraitsThis trait represents a source of clocking signal while subtraits its more specialized flavours (source of signal for Dpll, Pclk, Gclk, etc.).
These are implemented by corresponding specialized types of
Enabled
e.g.:Regardless of how complicated it might seem to look, it can be roughly understood as: Enabled
Dpll
peripheral can be a source of signal for any Gclk.*Token
typesUnfortunately,
PAC::Peripherals
granularity is too low for them to be useful when spliting clocking system into such small, semi-independent pieces. In order to solve this problem, we consume PAC at the very beginning and return a set of tokens that internally use appropriateRegisterBlock
directly, retrieved from a raw pointer . It is safe because register regions managed by different tokens do not overlap. Tokens cannot be created by a user; they are provided during initialization and do not expose any public API. Memory accesses are read/write-synchronized (Chapter 13.3; p. 138).Source/SourceMarker
traits usage in an APIThis is a slightly simplified example of how more less every clocking component that relies on one-to-many depenedency relationships is implemented
Gclk::new
consumes, upon construction, aGclkSource
provided to it and returns it with a type ofEnabled<_, N++>
(as mentioned previously, specializations ofEnabled
implementSource
based traits). Analogically,Gclk::free
consumes aGclkSource
passed in and returns it with a new type ofEnabled<_, N-->
. By design it is impossible to go below0
, because there is always less or equal amount of users than a counter value.Note: amount of users might be less than a value of a counter in case of special types like
Gclk<Gen0>
which always has an implicit single user -- synchronous clocking domain. Minimal amount of users for it is1
, making it impossible to disable and therefore consistent with its documented HW characteristics.Version of this chapter from 2021-04-16
These properties are going to be reflected in Rust by traits (representing abstract concepts of
ClkSrc
,GClk
andPCh
) and structs implementing these traits (representing concrete HW instances of these concepts likeClkSrc:XOsc0
,GClk:GClk0
orPCh:PCh0
).Because of this one-to-many type of relationships between dependencies and dependees, typical for Rust approach of taking ownership over the dependency while dependee is being constructed is not sufficient.
To solve this, token-based approach might be used. Dependee, upon construction, consumes a token acquired from a dependency incrementing dependency's internal counter. Token can be returned back to the dependency causing its internal counter value to decrement. Dependency cannot be deconstructed as long as its internal counter is above 0.
release(self)
methods returnself
withinResult::Err
variant, allowing to recover after a failed release operation.This token-based approach will be used by all structs implementing aforementioned traits (
ClkSrc
,GClk
,PCh
).Usage from the HAL perspective
Updated: 2021-06-08
HAL peripheral X that is associated by HW design to peripheral channel Y
PCh:PChY
Pclk<Y, _>
will consume a token acquired fromPCh:PChY
Pclk<Y, _>
.Thus, clocking tree that is relevant for HAL peripheral X is locked and has to be released step by step. It is worth noting, that only that part of clocking tree is locked so the granularity is quite fine.
V1 clocking API compatibility
New: 2021-06-08
In order to support v1 clocking compatible peripherals, every
clock::v1::*Clock
object implements aFrom<Pclk<_, _>>
trait. Therefore, it is feasible to use existing peripheral implementations with new clocking API.Default state
Updated: 2021-06-08
fn crate::clock::v2::retrieve_clocks
returns a tuple of following typewhich is supposed represent a default state of clocking system after reset (Chapter 13.7; p. 141).
Version of this chapter from 2021-04-16
Idea is to have a stateless function returning `GCLK0` + `DFLL48M` instances populated with adequate tokens that represent default clocking configuration of the HW ([Chapter 13.7; p. 141][v7datasheet]). `GCLK0` and `DFLL48M` should not have public constructors. Moreover, `GCLK0` should have an _exchange_ method for managing `ClkSrc` instead of the release. This is because `GCLK0` is essential for MCU to function, as it drives MCLK and therefore the CPU.// TODO Show an example of
GClk0<T: ClkSrcToken>::exchange<U: ClkSrcToken>(clk_src_token: U) -> GClk0<U>
// TODO: Fix further chapters.
Access to PAC
Instead of having single
ClockController
-like object managing the clock tree, new API assumes usage of smaller types that cannot be misused or misconstructed.To achieve this, one needs to be able to split PAC components according to the granularity of clocking API components. Here, idea is to have a
split
function (similar to one available withinGpioExt
trait) that would output a tuple of structs wrapping PAC registers within.Handling of MCLK
It is possible that some of these structs will need to share registers among each other. For example,
PACPeripherals::MCLK
ownsahbmask
andapb{a, b, c, d}mask
registers that are managing synchronous clock domain for a variety of peripherals. As long as we make sure that the implementation of these structs is correct (meaning eg. no overlapping bitmask operations), it should be fine.As an entry point for a discussion we are proposing to split
PACPeripherals::MCLK
into structs that correspond to would-bePCh
types and make themPCh
s' dependencies. Even thoughPCh
s are part of the asynchronous clocking domain, they are more less corresponding to the existing peripherals. It'd probably make sense to have it initialized there, asPCh
s are going to be HAL peripheral dependencies anyway.thumbv7em vs thumbv6m
General idea revolves around generalizing PAC access as early as possible. Eg. for
PACPeripherals::{MCLK, PM}
(MCLK
forthumbv7em
;PM
forthumbv6m
) having asplit()
implementation forPM
that would split it into power management related registers and athumbv7em
'sMCLK
like struct.// TODO get into details
Embedded-time
ATSAMD seems to be heading towards
embedded-time
, as evident from Issue #333.This proposal does not conflict with this path forward as it is agnostic to what stores the notion of time.
How We Teach This
Documentation should be updated along with a migration guide from the older abstraction. It would also be beneficial for users to have some reference examples for common clocking scenarios.
Drawbacks
Why should we not do this?
It is a major breaking change.
Alternatives
What other designs have been considered? What is the impact of not doing this?
Retaining the similar but subtly different clocking abstractions for
thumbv7em
andthumbv6m
, with the drawbacks as discussed above.Unresolved questions
What parts of the design are still TBD?
NOTE: These questions are mostly related to
thumbv7em
.genctrl::SRC_A
: PAC defined clock sources, what areGCLKIN
andGCLKGEN1
(in a diagram on a page 142GCLK1
seems to be able to become an input for otherGCLK
s even though it is not apparent on a diagram from page 136)?RTC
clock source), CLK_WDT_OSC (WDT
clock source) and CLK_ULP32K (EIC
clock source)?USB
peripheral andDFLL48M
. Can it be incorporated to the typesafe hierachy in a coherent manner (page 136)?PCh3
specifically used for (what is the GCLK_DPLLn_32K signal)?Hypothesis:
DPLL0
can rely either on a reference clock fromPCh1
(GCLK_DPLL0) orPCh3
(GCLK_DPLL0_32K)DPLL1
can rely either on a reference clock fromPCh2
(GCLK_DPLL1) orPCh3
(GCLK_DPLL1_32K)References
If there's a reference to an external document with a specified page, it is implicit reference to
SAM D5x/E5x Family Data Sheet
(DS60001507F
) in a context ofthumbv7em
based MCUs.For
thumbv6m
-based MCU, it is a reference toSAM D21/DA1 Family Data Sheet
(DS40001882F
).The text was updated successfully, but these errors were encountered: