Friedrich-Alexander-Universität UnivisSearch FAU-Logo
Techn. Fakultšt Willkommen am Department Informatik FAU-Logo
Logo I4
Department of Computer Science 4
  Hello World: First Steps
  Raw Memory Access
  Inter-Domain Comm.
  Service Protection
  CiAO backend
  core module API

Raw Memory Access
  Memory objects
  Memory-mapped objects
Dept. of Computer Science  >  CS 4  >  Research  >  KESO  >  Documentation  >  Raw Memory Access
Raw Memory Access: Programming device drivers

This howto presents KESO's mechanisms for accessing memory mapped device registers from Java code. This is mostly needed for writing device drivers. This guide does not cover devices that require special CPU instructions for being programmed. Such accesses require direct usage of the KNI (Keso Native Interface), unless an API is already available in KESO for your target platform.

KESO provides two mechanisms for accessing raw memory locations: Memory objects and Memory mapped objects. Memory objects are basically equivalent to RawMemoryAccess as specified by the Real-time Specification for Java (RTSJ), although the API of KESO's Memory class is slightly different. Memory-mapped Objects allow the layout of a memory region to be specified by a Java class definition, similar as one would do using a structure definition in the C programming language.

For both abstractions, the raw memory area can for safety reasons only be accessed using primitive data types. It is not possible to read or write reference values to such a memory area. In KESO, the abstractions of Memory objects and memory-mapped objects are also used for accessing special memory areas that are managed by the runtime system. To distinguish from this type of raw memory areas, you may sometimes also encounter the term Device Memory when speaking of memory areas that are not managed by the runtime, such as memory-mapped device registers that reside at fixed addresses in the address space.

Example: AVR GPIO-Ports

We will present the device memory abstractions at the example of a simple device driver for the GPIO ports of an 8-bit AVR processor. For a better understanding, we briefly explain the core registers used to configure one of the AVR's GPIO ports. Each of the AVR's GPIO ports consists of 8 pins that can independantly be used as input or output pins. The core operations on each port are done through three 8-bit registers, that are mapped into the address space and can be accessed using normal memory access operations:

  • Data direction register DDRA, address 0x1a
    This register is used to configure the direction (input or output) for each of the port's pins. Setting bit X in this register configures pin X as output, clearing it configures the pin as input.
  • Input pins DDRA, address 0x19
    This read-only register can be used to determine the level of a pin. It is normally used to determine the state of input pins.
  • Data register DDRA, address 0x1b
    For output pins, writing to this register sets the level of the pin. Setting bit X in this register sets a high level at pin X, clearing the bit a low level. For input pins, setting bit X in this register activates the pull-up resistor for pin X.
In our example, we will use a single of these Ports, PortA. The register names for this port all have the suffix A appended.

address space example

The figure shows the memory areas of interest for the example. Normally, a Java application can only access the managed memories of its domain, i.e. the stack of the current thread, the static fields of the domain and objects on the heap of the domain. The three port configuration registers that we need to access in order to write a device driver for PortA are outside these managed area, hence there is no way for our Java code to gather access to these registers without using special mechanisms provided by the Java runtime.

Memory Objects

Memory objects themselves are regular objects that reside on the heap of a domain or in immortal memory. These objects however internally reference a memory region outside the regular managed memory areas of the domain. Such a raw memory region is defined by its start address in memory and its size. For our example, we need access to the memory region at address 0x19 of size 3 (bytes).

The Memory object in addition provides methods for reading and writing primitive values of varying sizes to the referenced area, and convenience methods for directly performing certain bit operations on a value within the referenced area. Details are available in the API documentation of the Memory class. All accesses to the memory area require an explicit offset into the area. Similar to Java array accesses, these offsets need to be bounds checked for safety reasons.

Creating a new Memory object for accessing a region of device memory

To create a new Memory object that provides access to a device memory region you need to use the methods of the MemoryService class provided for this purpose, allocStaticDeviceMemory() or allocDynamicDeviceMemory(), both of which are provided the start address and the size of the desired memory area as parameters. The static variant creates a Memory object in immortal memory for that call site of the allocStaticDeviceMemory(). Subsequent invocations at the same call site will always return the same Memory object, with the referenced memory region adjusted to the most recently provided parameters. The second variant will dynamically allocate the Memory object on the heap of the current domain. If the first variant fulfills your needs, it should be preferred as it is more efficient.

Driver using Memory objects

You should now know enough to write a driver class for the above GPIO port. The driver class could look as follows:

import keso.core.*; public final class PortA { // base address private static final int BASE=0x19; // offsets private static final int PIN =0; private static final int DDR =1; private static final int PORT=2; private static final Memory regs = MemoryService.allocStaticDeviceMemory(BASE, 3); public static void setMode(int pin, boolean isOutput) { if(isOutput) { regs.or8(DDR, (1<<pin)); } else { regs.and8(DDR, ~(1<<pin)); writePin(pin, true); // activate pull-up resistor } } public static void writePin(int pin, boolean level) { if(level) regs.or8(PORT, (1<<pin)); else regs.and8(PORT, ~(1<<pin)); } public static boolean readPin(int pin) { return (regs.get8(PIN) & (1<<pin)) != 0; } }

In the example, the driver class is purely static. Since AVRs contain multiple equivalent such ports that merely defer in the start address of the registers, a real-world example would probably be non-static and allow instantiation using varying base addresses.

Memory-Mapped Objects

Memory objects are inconvenient to use in many simple cases and require runtime bounds checks. While dynamically computed offsets can make Memory objects a flexible and powerful mechanism for more complex scenarios, this flexibility is not needed in many simple cases such as our AVR port example.

KESO provides a second abstraction called memory-mapped objects for accessing raw memory regions. This mechanism allows the layout of a memory region to be defined using a standard Java class definition.

Defining a Class used as template for creating memory-mapped objects

Defining a class to be used for creating memory-mapped objects is basically equivalent to defining a regular Java class. The class may contain regular (not mapped) fields and methods, but in addition it contains mapped fields.

When defining such a class, the class needs to implement the MemoryMappedObject marker interface. To describe the layout of the raw memory area that should be accessed, one simply defines fields in the class using the special memory types from package keso.core, all subclasses of keso.core.MT carrying the prefix MT_. The chosen memory type determines the size occupied by the respective field in the memory area, the position of the field definition in the class relative to other mapped field definitions determines the offset. An example for such a memory type is MT_U8, which represents an unsigned byte value. The use of special types rather than Java's standard types for primitive data fields allows the class to also contain regular heap fields.

The operations provided by the memory types are similar to that provided by Memory objects, however, the size of the target value is defined by the memory type, thus there is only one variant of each operation. In addition, the offsets of the values are implicitely given by the position of the field in the class definition, therefore it is not required as a parameter to these methods and no runtime bound check is needed.

Creating a new memory-mapped object

Memory-mapped objects need to be created using services of the MemoryService. You can either directly create a new memory-mapped object using a base address for the mapping, or you can use an existing Memory object as the basis for this mapping. In the latter case, a bound check will be performed when creating the mapping to ensure that the Memory object references an area of sufficient size.

To directly create a memory-mapped object, use the mapStaticDeviceMemory() service, for creating the object on the basis of an existing Memory object, use the mapMemoryToStaticObject() service.

Driver using Memory-mapped objects

Back to our example, a driver using memory-mapped object could look as follows:

package driver.avr; import keso.core.*; public final class AVRPort implements MemoryMappedObject { private MT_U8 PIN; // offset 0 private MT_U8 DDR; // offset 1 private MT_U8 PORT; // offset 2 public void setMode(int pin, boolean isOutput) { if(isOutput) { DDR.setBit(pin); } else { DDR.clearBit(pin); writePin(pin, true); // activate pull-up resistor } } public void writePin(int pin, boolean level) { if(level) PORT.setBit(pin); else PORT.clearBit(pin); } public boolean readPin(int pin) { return PIN.isBitSet(pin); } // provide a mapped object for using PortA at address 0x19 public static AVRPort getPortA() { return (AVRPort) MemoryService.mapStaticDeviceMemory(0x19,"driver/avr/AVRPort"); } }

As described above, the class needs to implement the MemoryMappedObject marker interface. The class contains three mapped fields, PIN, DDR, and PORT, in the order that they appear in the address space. The methods provided are the same as in the Memory object variant, except that we now need to use instance methods since we need to access the mapped fields of a memory-mapped instance of the class. The methods show that accessing the registers is now more convenient, since we can directly access them using their field name. Since memory types appear to be a non-primitive data type on the Java language level, all accesses look like methods calls on the mapped field - in the generated C code, these will be downcompiled to direct accesses.

Finally, the last method provides a mapped instance of the class mapped to the base address of PortA's registers.

  Imprint   Privacy Last modified: 2011-01-18 13:05   MS