|
CHERIoT RTOS
A compartmentalised RTOS for CHERIoT hardware
|

Topics | |
| Convenience macros | |
Classes | |
| class | Bitpack< StorageParam > |
| The Bitpack structure itself, templated on the underlying Storage type. More... | |
| struct | BitpackDerived< B > |
| It is occasionally useful to derive one bitpack from another. More... | |
Macros | |
| #define | BITPACK_DECL_ANNOTATION |
Bitpack-s are a way to handle situations where many flags and/or fields of arbitrary widths are packed into an underlying integer (such as a uint32_t and referred to as a Bitpack's Storage type) at arbitrary bit alignments. This is similar to C and C++'s bitfields, but more portable, more versatile, and with a type-centric interface. They encapsulate the bitwise AND, OR, and shift operations required to get at spans of bits within a word. They are meant to be particularly useful for handling memory mapped I/O (MMIO) registers for hardware drivers.
Those readers interested in formal details are invited to see A More Formal Perspective.
Bitpack-s support the following features:
To define a bitpack, begin by defining a class (or struct) that inherits from the Bitpack template given below, specifying the underlying Storage type as the template parameter. It usually suffices to pick one of the uintNN_t cstdint types. For reasons of C++, one often wants a bit of syntax at the top of these definitions, which is encapsulated in the BITPACK_USUAL_PREFIX macro; add that at the top of your derived class. We then need to specify fields of the bitpack in question.
For each field, this means, ultimately, defining a call to the Bitpack::member method that users of the bitpack can call to access that field. While it is perfectly sensible to do so directly, this file also offers a large number of utilities and affordances which capture common occurrences. We will need to specify the type of the field as seen from C++, though in many cases one will simultaneously define the field and its type. Further, one will need to specify the FieldInfo for the field: this is a structure holding, in order, the minimum and maximum (both inclusive) bit positions occupied by the field in the underlying word, and a (default false) flag to indicate that the field is constant; many of our macros will simply let us write the values of these properties in order as arguments.
At the lowest level of assistance, the BITPACK_MEMBER_ADD macro encapsulates the C++ syntax for a method wrapping a call to member. It takes the name of the method to define, the type of the field, and the values to use for the FieldInfo.
For example, BITPACK_MEMBER_ADD(ticks, uint8_t, 3, 9); defines a method ticks() that gives uint8_t-flavored access to a field spanning bits 3 to 9 of the bitpack's underlying word. Callers of this method get a Proxy of the bitpack that can be used to get, set, or alter (read-modify-write) that field and will see the field's value as a uint8_t.
Most of the time, though, just as defining a bitpack creates a new C++ type, one can think of a field within a bitpack as having a unique type within its containing bitpack. As such, most of our definition utility macros serve to define a field while simultaneously defining a type which is both used as the type of values of the field and as a name for the field. Behind the scenes, there is some C++ machinery to provide a Bitpack::member<FieldType> template method that can look up a field's FieldInfo from its type; our macros encapsulate that machinery.
Often, fields take on enumerated values, for which C++ enum class types are usually reasonably convenient representations. The BITPACK_MEMBER_ADD_ENUM macro encapsulates the syntax for defining an enum class (with given underlying type) and an associated FieldInfo.
For example, this block of code...
BITPACK_MEMBER_ADD_ENUM(Drivers, uint8_t, 12, 13)
{
None = 0,
UpOnly = 1,
DownOnly = 2,
Both = 3,
};
defines an enum class Drivers within the bitpack containing it (multiple bitpack definitions may each have their own Drivers type within, but each such may have only one). This enumeration has an underlying type of uint8_t and four defined values: Drivers::None, Drivers::UpOnly, &c. The numeric values of the enumerators are used as values of the field within the bitpack. At the same time, the macro will have defined a FieldInfo calling for a non-constant, two-bit field at positions 12 and 13. and associated this with the Drivers type, so that a call to member<Drivers>() within the bitpack will return a Proxy for that field which works with Drivers-typed values.
It is often useful to have single-bit fields with custom names for the 0/false and 1/true values. This is provided by the BITPACK_MEMBER_ADD_ENUM_BOOL macro; some popular options for names are provided as wrappers thereof.
Sometimes fields are numeric rather than enumerative, such as clock dividers or event counters. Bitpack-s have a Numeric<Base> template class to facilitate creating bespoke C++ types for such fields, and the BITPACK_MEMBER_ADD_NUMERIC macro encapsulates the syntactic clutter of its use.
For example, BITPACK_MEMBER_ADD_NUMERIC(Ticks, uint16_t, 14, 25, true); defines a type Ticks, which wraps a uint16_t, within the bitpack containing it. Like with BITPACK_MEMBER_ADD_ENUM, this also defines a FieldInfo and associates it with the Ticks type, so that member<Ticks>() within the bitpack will return a proxy of this field. The true at the end sets the FieldInfo isConst flag and so will inhibit attempts to use the member() field proxy to change its value.
Having defined a bitpack and its fields and internal machinery, one can now make use of it! Most often, bitpacks are used directly by external code; that is, the proxies of its fields are public rather than being used by methods of the bitpack class itself (though, of course, that also works). As such, C++'s somewhat inflexible syntax rears its head on occasion, and bitpacks offer macros in an attempt to compensate.
As a consequence of defining types (of fields) within other types (their contianing bitpack class), the former are in scope outside the latter only when qualified with the name of the latter. Having to always write out the full MyBitpackClass::TheFieldType would be exhausting and distracting. Conveniently, with a bitpack value in hand, decltype gives us a generic way to refer to its type and can be used to qualify the names of contained types. The BITPACK_MEMBER_DECLTYPE(b, T) macro gets the Proxy of the T-typed field within the bitpack value b without needing to spell out the latter's type. If the type of b is dependent (for example, is auto or is or involves a template argument), use BITPACK_MEMBER_DEPENDENT(b, T) instead. See Type-qualifying member accessor macros .
Bitpack::Field::Proxy-s offer six core methods:
Atop this core, there are many convenience macros for working with bitpacks and field proxies.
Because Bitpack-s encourage the use of types and named values defined within derived classes, code using Bitpack-s often needs to use qualified names or wrap values in type constructors (in addition to qualifying the field type with the bitpack's type when constructing the field proxy as in the BITPACK_MEMBER_ macros above). The first two families of macros atop proxies help with these cases.
Each of these families have further wrappers around their center macros:
The family of Type-directed proxy operator is somewhat dual: rather than using the field type to in some way influence the meaning of a value, this family uses the type of the value to find the appropriate field and a proxy thereof within a bitpack value. This can be convenient when a field value is already available. The centerpiece of this family is the BITPACK_OPERATE_VALUE(b, operator, value) macro, which relates with operator a proxy of the field in the bitpack value b whose type is that of value and value itself. The BITPACK_OPERATE_{DECLTYPE,DEPENDENT}(b, operator, value) wrappers qualify value with the type of the bitpack value b. As above, there are also _WITH specializations of the _OPERATE forms.
There are other macros likely of more niche interest; see...
More formally, Bitpack-s are a kind of aggregate structure within a numeric word and offer lens-like, typed access to contiguous spans of bits therein, offering a view of bits as a product of typed fields, occuping a disjoint, contiguous span of bits within that word.
The static information about a field is represented by a specialization of the Bitpack::Field<Type, Info> template. Type is the exposed C++ type of the field in question, and Info is a (necessarily constexpr) value of the FieldInfo type, which holds the bit indicies of the field's span within the Storage type. FieldInfo also has an isConst flag, used to specify that the field is not mutable from software even if the larger bitpack is non-constant.
Our lenses, the centerpiece of the whole thing, are expressed as "proxy" objects, instances of specializations of the Bitpack::Field<Type, Info>::Proxy<DerivedBitpack, RefType> class, which wrap references to the underlying Storage-typed word in a Bitpack class (or a derived class). The type of such references, RefType, inherits any const and/or volatile qualification of the user's handle to the bitpack, and are additionally const qualified if the associated FieldInfo has isConst asserted.
| #define BITPACK_DECL_ANNOTATION |
Definition at line 303 of file bitpack.hh.