Raising Interrupts

A common need for MMIO models is to raise interrupts. The means for doing this depend on the type of interrupt interface that is used. For example, the signal interface can be used to raise and lower interrupts for devices that supports this, but more common is to use the direct interrupt interface as this offers some advantages as interrupts may be numbered.

The interrupt interface is defined in temu-c/Models/IrqController.h. There are two interfaces defined there, the temu_IrqCtrlIface and the temu_IrqClientIface. The client interface is for implementing dedicated interrupt controllers that needs to be notified about interrupt acknowledgement, while the controller interface is the way in which a device need to interface to an existing interrupt controller. Most users will be interested in the controller interface only.

Add the field:

temu_IrqCtrlIfaceRef irq;

To your model struct and register this field in the plug-in initialisation function:

temu_addProperty(c, "irq", offsetof(MyModel, irq),
  teTY_IfaceRef, 1,
  NULL, NULL, "interrupt controller");

To raise and lower interrupts, we need to insert code for this somewhere in the model. We can do this in the event handler (or in the register read/write handlers):

MyModel *model = (MyModel*)ev->Obj;
model->irq.Iface->raiseInterrupt(model->irq.Obj, 14);

The code above will raise and lower an interrupt in the connected interrupt controller (note that if no NULL checks are done, then this will result in a crash if you have not connected the interface).

The LEON3 and UT700 model comes with the IrqMp interrupt controller, in both cases it is named: irqMp0. It is easy to use the connect command to attach your MMIO model to the IRQ controller:

temu> connect a=foo.irq b=irqMp0:IrqIface

The connect command will create a link from the interface reference property named irq registered with the temu_addProperty()` call to the named interface "IrqIface" implemented by the IrqMp class.

Note that if you need to figure out what interfaces a model implements, either look in the device manual or use the class-info command.

Now we need some software to handle the interrupts. The straight forward method is to use RTEMS for this. We can work from the rtems-hello.c example you used earlier in the tutorial, and rework the Init()-function to ensure that we explicitly install an interrupt handler and to trigger the interrupt by writing to the register that was added in your device model:

rtems_isr
handleIRQ(rtems_vector_number vector)
{
  // Note, printf is interrupt driven and will hang
  // hang in ISRs unless you reenable IRQs here
  // thus we use printk which is polling.
  printk("External interrupt received with vector 0x%x\n",
         vector);
  }
}
rtems_task
Init(rtems_task_argument ignored)
{
  rtems_isr_entry oldHandle;
  rtems_status_code status = rtems_interrupt_catch(handleIRQ, 0x1e, &oldHandle);
  *(volatile unsigned*)(0x80000240) |= (1 << 14); // IRQ 14
  *(volatile unsigned*)(0x80001000) = 42;
  exit( 0 );
}