Differences between CAmkES 2 and 3

Rich Types for Settings

Previously all attribute settings were treated as strings by templates. Settings are now interpreted as an appropriate python type.

// list
foo.bar = [0x60, 0x64];

// boolean
moo.cow = true;

// string
baz.qux = "hello";

// dict
crazy.stuff = { "key" : ["polymorphic", 42, "list",
                 {"hello":true} ] };

// appropriate numeric type 
arithmetic.expressions = 1 << (2 ** 2) == 3 ? -2 : 0x9;

Arbitrary ids are no longer allowed as setting values:

a.b = c; // error

Parametrised Buf Type

The Buf dataport type can now be optionally parametrised by the size (in bytes) of the dataport. The syntax for this is Buf(size). If left unspecified, the default size is 4096 bytes.

It is an error to connect dataport interfaces of different sizes.

E.g.

component Foo {
    dataport Buf(8192) dp;
}

component Bar {
    dataport Buf(8192) dp;
}

assembly {
    composition {
        component Foo foo;
        component Bar bar;
        connection seL4SharedData conn(from foo.dp, to bar.dp);
    }
}

Asynch Connector Renamed

The seL4Asynch connector has been renamed to seL4Notification.

Non-Volatile Dataports

Previously, c symbols used to access dataports had volatile pointer types. Users were encouraged to use volatile pointers to prevent the compiler optimizing away, or re-ordering, accesses to shared memory.

This is no longer the case. The c symbols referring to dataports are regular (ie. non-volatile) pointer types, and programmers are required to explicitly “acquire” and “release” dataports to prevent harmful re-ordering (at both compile time and runtime). The motivations for this change are: * Treating dataports as always volatile prevents the compiler from re-ordering any accesses to them, even in cases where it won’t affect the correctness of programs. Using non-volatile dataports allows the compiler to make better optimizations. * Preventing compiler reordering (ie. with volatile) is not sufficient to make shared memory coherent in a multicore environment. Changes made to shared memory by one core may become visible to other cores in a different order. * Using functions from the standard library on dataports (e.g. passing a string in a dataport to strlen) requires casting from a volatile pointer to a regular pointer - an undefined operation in c.

For a dataport interface foo, a component has access to foo_acquire() and foo_release() functions (they may instead be macros). Call foo_acquire() between multiple reads from a dataport, where the correct behaviour of the program depends on the contents of the dataport possibly changing between reads. Call foo_release() between multiple writes to a dataport, where the correct behaviour of the program depends on writes preceding the foo_release() in the program code being performed strictly before the writes following it.

Many-to-Many Connections

There is new syntax for connections with multiple from/to sides. The following fragments are equivalent (except for connection names):

connection seL4RPCCall foo(from a.x, to c.z);
connection seL4RPCCall bar(from b.y, to c.z);
connection seL4RPCCall foobar(from a.x, from b.y, to c.z);

Both syntaxes are supported by CAmkES 3.

Hardware Component Configuration Attributes

The attributes for configuring hardware components have changed. Below is a CAmkES 2 spec, followed by the equivalent CAmkES 3 spec. These changes are not backwards compatible.

These component definitions are the same in CAmkES 2 and 3:

component Device {
  hardware;
  dataport Buf registers;
  emits Interrupt interrupt;
  provides IOPort port;
}

component Driver {
    dataport Buf registers; consumes Interrupt interrupt; uses IOPort
    port;
}

The composition section of the spec is the same for CAmkES 2 and 3:

assembly {
  composition {
    component Device device;
    component Driver driver;

    connection seL4HardwareMMIO mmio(from driver.registers, to device.registers);
    connection seL4HardwareInterrupt interrupt(from device.interrupt, to driver.interrupt);
    connection seL4HardwareIOPort ioport(from driver.port, to device.port);
  }
  configuration {
    // see below
  }

}

CAmkES 2 configuration:

configuration {
  device.registers_attributes = "0x12345000:0x1000"; // string in format "paddr:size"
  device.interrupt_attributes = 27;                  // irq number
  device.port_attributes = "0x40:0x40";              // string in format "start_port:end_port"
}

CAmkES 3 configuration:

configuration {
  device.registers_paddr = 0x12345000;  // separate attribute for paddr and size
  device.registers_size = 0x1000;
  device.interrupt_irq_number = 27;     // attribute name has changed
  device.port_attributes = "0x40:0x40"; // unchanged
}

Interrupt API

In CAmkES 2, interrupts were abstracted as CAmkES events, emitted from a hardware component. For a component with an interface foo connected to an interrupt, components could call foo_wait(), foo_poll(), and foo_reg_callback(), as with a regular event.

In CAmkES 3, interrupts are still abstracted as events in the ADL (CAmkES spec). Component implementations however, use a different interface for interacting with interrupts than with regular event interfaces. More specifically, a component with an interface foo connected with the seL4HardwareInterrupt connection has access to foo_acknowledge() which acknowledges the associated interrupt to the kernel. In addition, the component implementation must provide a definition of a function void foo_handler(void). The standard event methods (foo_wait(), foo_poll(), and foo_reg_callback()) are not implemented for interrupts.

The user-provided function foo_handler() will be called by a dedicated interrupt-handling thread (one thread per interface connected with seL4HardwareInterrupt). Unlike callbacks registered with *_reg_callback, interrupt handlers do not need to be explicitly registered, and do not become unregistered after calling.

Hierarchical Components

The syntax for defining hierarchical components has changed in CAmkES 3. CAmkES 2 had special connectors used to export an interface of a sub-component:

component Serial {

  // interface of this component 
  provides UartIface serial;
 
  composition {
 
    // internal components
    component UartDevice uart_device;
    component UartDriver uart_driver;

    // internal connection
    connection seL4HardwareMMIO conn(from uart_device.regs, to uart_driver.regs);
  
    // export interface of driver component as interface of this component
    connection ExportRPC exp(from uart_driver.uart, to serial);
 
  }
}

CAmkES 3 introduces special syntax for exposing interfaces of sub-components:

component Serial {

  // interface of this component
  provides UartIface serial;
 
  composition {
 
    // internal components
    component UartDevice uart_device;
    component UartDriver uart_driver;

    // internal connection
    connection seL4HardwareMMIO conn(from uart_device.regs, to uart_driver.regs);

    // export interface of driver component as interface of this component
    export uart_driver.uart -> serial;
 
  }
}