Expand description

Peripheral Management

Most peripherals are implemented as memory mapped I/O (MMIO). Intrinsically, this means that accessing a peripheral requires dereferencing a raw pointer that points to the peripheral’s memory.

Generally, Tock peripherals are modeled by two structures, such as:

/// The MMIO Structure.
#[repr(C)]
#[allow(dead_code)]
pub struct PeripheralRegisters {
    control: VolatileCell<u32>,
    interrupt_mask: VolatileCell<u32>,
}

/// The Tock object that holds all information for this peripheral.
pub struct PeripheralHardware<'a> {
    mmio_address: StaticRef<PeripheralRegisters>,
    clock: &'a ChipSpecificPeripheralClock,
}

The first structure mirrors the MMIO specification. The second structure holds a pointer to the actual address in memory. It also holds other information for the peripheral. Kernel traits will be implemented for this peripheral hardware structure. As the peripheral cannot derefence the raw MMIO pointer safely, Tock provides the PeripheralManager interface:

impl hil::uart::Configure for PeripheralHardware {
    fn configure(&self, params: hil::uart::Parameters) -> Result<(), ErrorCode> {
        let peripheral = &PeripheralManager::new(self);
        peripheral.registers.control.set(0x0);
        //         ^^^^^^^^^-- This is type &PeripheralRegisters
        Ok(())
    }
}

Each peripheral must tell the kernel where its registers live in memory:

/// Teaching the kernel how to create PeripheralRegisters.
use kernel::platform::chip::NoClockControl;
impl PeripheralManagement<NoClockControl> for PeripheralHardware {
    type RegisterType = PeripheralRegisters;

    fn get_registers(&self) -> &PeripheralRegisters {
        &*self.mmio_address
    }
}

Note, this example kept the mmio_address in the PeripheralHardware structure, which is useful when there are multiple copies of the same peripheral (e.g. multiple UARTs). For single-instance peripherals, it’s fine to simply return the address directly from get_registers.

Peripheral Clocks

To facilitate low-power operation, PeripheralManager captures the peripheral’s clock upon instantiation. The intention is to exploit Ownership Based Resource Management to capture peripheral power state.

To enable this, peripherals must inform the kernel which clock they use, and when the clock should be enabled and disabled. Implementations of the before/after_mmio_access methods must take care to not access hardware without enabling clocks if needed if they use hardware for bookkeeping.

use kernel::utilities::peripheral_management::PeripheralManagement;
use kernel::utilities::StaticRef;
use kernel::platform::chip::ClockInterface;
// A dummy clock for this example.
// Real peripherals that do not have clocks should use NoClockControl from this module.
struct ExampleClock {};
impl ClockInterface for ExampleClock {
    fn is_enabled(&self) -> bool { true }
    fn enable(&self) { }
    fn disable(&self) { }
}

// Dummy hardware for this example.
struct SpiRegisters {};
struct SpiHw<'a> {
    mmio_address: StaticRef<SpiRegisters>,
    clock: &'a ExampleClock,
    busy: bool,
};

/// Teaching the kernel which clock controls SpiHw.
impl<'a> PeripheralManagement<ExampleClock> for SpiHw<'a> {
    type RegisterType = SpiRegisters;

    fn get_registers(&self) -> &SpiRegisters { &*self.mmio_address }

    fn get_clock(&self) -> &ExampleClock { self.clock }

    fn before_peripheral_access(&self, clock: &ExampleClock, _registers: &SpiRegisters) {
        clock.enable();
    }

    fn after_peripheral_access(&self, clock: &ExampleClock, _registers: &SpiRegisters) {
        if !self.busy {
            clock.disable();
        }
    }
}

Structs

  • Structures encapsulating peripheral hardware (those implementing the PeripheralManagement trait) should instantiate an instance of this method to access memory mapped registers.

Traits