hackndev / zinc

The bare metal stack for rust
zinc.rs
Apache License 2.0
1k stars 100 forks source link

Idea for static configuration #207

Open bgamari opened 10 years ago

bgamari commented 10 years ago

An alternative to platformtree

Imagine there existed a trait,

trait CompileTime {
    pub fn to_compile_time(tt: TokenTree) -> Self;
}

and a syntax extension providing an attribute (let's call it compile_time) which given a type definition derived an appropriate instance. For example,

#[compile_time]
enum Port { PortA, PortB }

#[compile_time]
struct Pin {
    pub block: Port,
    pub pin_n: uint,
}

#[compile_time]
struct SpiParams {
    pub speed: uint,
    pub phase: Level,
    pub polarity: Level,
    pub clk: Pin
    pub mosi: Pin
    pub miso: Pin
}

#[compile_time]
enum Level {High, Low}

Finally, we'll want a function,

fn to_compile_time<T: CompileTime>(tt: TokenTree) -> T

At this point we have something that could be used for compile-time specification of configuration. The nice thing about this is that we've punted all of the details of parsing to a reusable component (compile_time). In fact, with CTFE this component may almost disappear entirely. Moreover, we would benefit from any tooling support the user might have due to the use of standard Rust syntax.

All the device bindings need to worry about is a typical Rust struct. For instance you might have a syntax extension which handles peripheral instantiation, e.g.

#[use(SPI0)]
const Spi0 = SpiParams {
      speed: 100000,
      phase: High,
      polarity: Low,
      clk: Pin(PortA, 1),
      mosi: Pin(PortA, 2),
      miso: Pin(PortA, 3),
}

where peripheral bindings would register with use with an interface like,

pub fn register<Config: CompileTime>(instantiate: |Config| -> Result<TokenTree>);

Dealing with dependencies

One issue brought up by @farcaller is that of dependencies. Currently Platform Tree deals with this by encoding them in its tree structure.

This obviously won't work in the above proposal. In the case of simple dependencies we can encode these as fields as we did with the Pins above. But what if we have an open universe of potential dependents?

Say we have an I2C device (say, for instance, an ADC). Its configuration might look like this,

#[compile_time]
struct I2cAdc {
    addr: I2cAddress,
    resolution: AdcResolution,
    bus: &I2cBus,
}

Here we have encoded the device's dependency on its bus as a reference. The binding would then be free to call something like needs(self.bus) to ensure that its bus is initialized before it attempts to initialize itself.

farcaller commented 10 years ago

This replaces the parts of PT that do node -> instance conversion alongside with appropriate validations. It doesn't seem to handle the dependencies, right? And you'll still have the suffering of passing down any typed arguments down to task entry points (the #[zinc_task] thing)

bgamari commented 10 years ago

Correct, this focuses on the parsing side of the problem. I haven't pondered how the resulting configuration might be passed to the user at runtime. I haven't had much experience in this area but at first glance your approach of generating structs and passing them to user tasks seems quite workable. What has been the trouble with this approach in your experience?