Serial Port Device

The serial port is a byte oriented bus model, it provides the ability to send individual bytes to a receiver. TEMU is delivered with several serial port terminals, the most important ones are the Console and the ConsoleUI plugins. These provide a non-interactive serial port terminal that prints anything received to stdout (this is the one you have been using so far), and an interactive version respectively. The interactive serial port terminal in the ConsolUI plug-in can be used if Qt4 is installed on your system, it provides a console application with a degree of VT100 emulation. This console will send data back to TEMU in case you type in it.

The rest of this exercise focus on the creating of a device communicating via the serial port. The device will when receiving data, print the character using the TEMU logging functions, and will respond automatically with a character. The response will be executed from a stack posted event handler.

Create a new file with a model, we can call it MyTTY. To communicate with the serial port we need two fields in the device struct (in addition to the temu_Object field):

  int64_t eventId;
  temu_SerialIfaceRef uart; // Defined in temu-c/Bus/Serial.h

The constructor does ofcourse needs to publish the event:

  tty->eventId = temu_eventPublish("tty.event", tty, evfunc);

The event function will send a byte to the other end of the serial port:

  MyTTY *tty = (MyTTY*)ev->Obj;
  tty->uart.Iface->write(tty->uart.Obj, 'x');

To implement the serial port interface you need to provide two functions to the interface, firstly a receive function, and secondly a (clear to send) cts function. CTS is experimental, so you do not need to fill it in at the moment.

static void
receive(void *obj, uint8_t data)
{
  MyTTY *tty = (MyTTY*)obj;
  temu_logInfo(obj, "received character '%c'", (char)data);
  temu_eventPostStack(tty->super.TimeSource, tty->eventId, teSE_Cpu);
}
static void
cts(void *obj)
{
}
static temu_SerialIface SerialIface = {
  receive,
  cts,
};

The device need to be registered in the plug-in initialisation function:

TEMU_PLUGIN_INIT
{
  temu_Class *c = temu_registerClass("MyTTY", create, dispose);
  temu_addProperty(c, "uart", offsetof(MyTTY, uart), teTY_IfaceRef, 1,
    NULL, NULL, "Target serial port");
  temu_addInterface(c, "UART_A", TEMU_SERIAL_IFACE_TYPE,
    &SerialIface, 0, "serial port interface");
}

Then compile the model and load it in TEMU. A problem now is that the LEON3 and the UT700 scripts only come with one APBUART instance. This is used by the normal serial port and is connected to the console (which is what has let you see the output of the RTEMS printf/printk calls). So, we cannot use this in case we want to print the received data in RTEMS. So we will add an extra UART to the system:

temu> object-create class=ApbUart name=apbuart1
temu> object-prop-write prop=apbuart1.config.infiniteUartSpeed val=1
temu> object-prop-write prop=apbuart1.config.fifoSize val=1
temu> memory-map memspace=mem0 addr=0x80001100 length=256 object=apbuart1
temu> connect a=cpu0.devices b=apbuart1:DeviceIface
temu> connect a=apbuart1.irqCtrl b=irqMp0:IrqIface
temu> connect-timesource obj=apbuart1 ts=cpu0
temu> connect a=apbctrl0.slaves b=apbuart1:ApbIface

As the extra APBUART is created and mapped into the system, we can now create the serial port device you have implemented:

temu> object-create class=MyTTY name=tty
temu> connect-timesource obj=tty ts=cpu0
temu> connect a=tty.uart b=apbuart1:UartIface
temu> connect a=apbuart1.tx b=tty:UART_A

Note that we connect firstly tty.uart to apbuart1:UartIface and secondly apbuar1.tx to tty:UART_A, that is as the connection is bidirectional.

Now we can create a small RTEMS program with the following init function:

rtems_task
Init(rtems_task_argument ignored)
{
  *(volatile unsigned*)(0x80001108) = 0x3; // Enable RX and TX
  *(volatile unsigned*)(0x80001100) = 'y'; // Put byte in datareg
  // Loop until data is in RX
  while ((*(volatile unsigned*)(0x80001104) & 1) == 0) ;
  int data = *(volatile unsigned*)(0x80001100);
  printk("got byte: %c\n", data);
  exit( 0 );
}

Build it with sparc-rtems-gcc and mkprom2 as usual and load this into the emulator,now when running you should see the logging message from the model and the output on apbuart0 in the console.

As you probably noticed above, you need to connect a bidirectional bus connector twice. However, this is error prone and easy to forget, if you delete the second connect, the emulator will crash as your serial device does not check whether it is calling a NULL function. There are two ways to avoid this, firstly is to run the objsys-check-sanity command, after all devices have been created and connected, that will let you know that there is an unconnected interface reference in the tty object. The second method is to add a port.

A port is an association within a TEMU class that establish a relationship between an interface reference property and an interface. The connect command can then establish a reverse link automatically. In the command:

temu> connect a=tty.uart b=apbuart1:UartIface
temu> connect a=apbuart1.tx b=tty:UART_A

If there is a port associating tty.uart and tty:UART_A, and there is a second port that associates apbuart1:UartIface and apbuart1.tx, then we will automatically get a bidirectional connection. In the plug-in initialisation function add the following after the uart interface reference property is registered AND after the UART_A interface is registered:

temu_addPort(c, "uart", "UART_A", "Serial port");

Now you only need to execute one connect command to connect the APBUART with your serial port device (note that you can use either of the two connect commands).