CS452 - Real-Time Programming - Winter 2018

Lecture 12 - AwaitEvent, Clock Server

Pubilc Service Annoucements

  1. Due date for kernel 3: 5 February, 2018.
  2. Data from kernel 2.

The Hardware in the Trains Lab

32-bit Timer

Interrupt Control Unit (ICU)

The actual device is the ARM PL190, and there are two of them in the SoC. In the Cirrus documentation they are called VIC1 and VIC2.

Base addresses

Basic Operation

VIC powers up with

Most likely you will change only the last of them.

Procedure

Initialization

  1. leave protection off, unless you are in doubt about your partner's ethics or competence.
  2. enable in VICxIntEnable only when you are ready to handle the interrupt

On an interrupt, the kernel

  1. reads the VICxIRQStatus register,
  2. chooses which interrupt to handle and
  3. clears the interrupt source in the device.

For debugging

  1. Use VICxSoftInt and VICxSoftIntClear to turn interrupt sources off and on in software.

Hardware Definitions

The first three lines of the table below are the part of the ICU that are used when your kernel is debugged. The remainder, except the last line, are useful during debugging.

The fast interrupt is not used in our kernels. It's used when there is so little to do in response to an interrupt that you don't need to perform a complete context switch.

Hardware priority is something that is widely used when it's worth saving a few instructions. If you select "vectored interrupts" then you give the interrupt a priority and an address. The address is the entry point for that particular interrupt. There is a register in the ICU containing the address of the highest priority asserted interrupt, and the kernel uses it directly.

Registers for Basic Operation
Register Name Offset R/W Description
VICxIntEnable 0x10 R/W 0: Masked, 1: Enabled
VICxIntEnClear 0x14 WO Clears bits in VICxIntEnable
VICxIRQStatus 0x00 RO One bit for each interrupt source
1 if interrupt is asserted and enabled
VICxRawIntr 0x08 RO As above but not masked
VICxIntSelect 0x0c R/W 0: IRQ, 1: FIQ
Set this to 0x0.
VICxSoftInt 0x18 R/W Asserts interrupt from software
VICxSoftIntClear 0x1c WO Clears interrupt from software
VICxProtection 0x20 R/W Bit 0 enables protection from user mode access
VICxVectAddr 0x30 R/W Enables priority hardware

See documentation.


Non-vectored Operation

Initialization

  1. Create user tasks with interrupts enabled.
  2. Enable interrupt in device
  3. Enable interrupt in ICU
  4. Enable interrupt in CPU, by MOVS whenever a task is activated.

Normal operation

When a hardware interrupt occurs you find yourself in IRQ mode. Next, you

  1. Remember that you started in IRQ mode, probably by storing something in a location only IRG mode can access.
  2. Switch to svc mode.
  3. Save user state (complete save!).
  4. Switch to svc mode.
  5. Install kernel state. Only at this point can you access kernel variables.
  6. Switch to IRQ state. You still have the kernel frame pointer.
  7. Put "something" into a kernel variable.
  8. Switch to svc mode.
  9. Look at the active interrupt sources; decide which one you will service; turn it off.
  10. Find which task is blocked waiting for the interrupt.
  11. Set up its return value, and make it ready.
Steps 1-8 are given in detail so that you can see the main pitfalls. They are not the only way to accompish what you want, and surely not the most efficient.

Tasks waiting on interrupts are normally high priority, and usually run right away.

If a second interrupt occurs immediately

  1. The CPU checks AND( IRQ, NOT( IRQ disabled bit set in CPSR ) ) before each instruction fetch.
  2. If it is asserted an IRQ exception is taken in place of next instruction fetch.
  3. Context switch into kernel
  4. Turn off interrupt in device

Last gasp of the hardware context switch

I have alluded several times to the IP bug, which occurs when you fail to save IP. Very infrequently a hardware interrupt occurs between two instructions

    mov	ip,sp
    stm sp!,{fp,ip,lr}
  
in every function preamble. The result is an activated task with another task's stack. In a running train program this bug occurs every several minutes.

There is a dual bug, the LR bug, in which one task's data gets another task's instructions. Here is a sequence that produces it.

  1. A hardware interrupt occurs.
  2. The registers of the interrupted task are pushed onto its stack.
  3. The lr_IRQ overwrites the lr on the stack.
  4. When the interrupted task is next scheduled, registers r0-r14 are restored in system mode.
  5. Then, movs pc, lr restarts execution of the interrupted task in scv mode.
This almost always works, but very infrequently it crashes. Why?

AwaitEvent

The final Kernel Primitive

... except for Destroy

int AwaitEvent( int eventType )

More About AwaitEvent

Argument

  1. Somewhere there is a list of event types
  2. This is not very portable

Processing in the kernel

Clock Server, Task Structure

What does the clock server do?

How is AwaitEvent Used?

AwaitEvent is used to update the time.

Generalizing
  1. There should (almost) always be a task blocked on AwaitEvent for every interrupt type. Why?
  2. A server cannot call AwaitEvent. Why?
  3. We call the task that calls AwaitEvent a Notifier. Why?
  4. Code for a typical Notifier
      struct delivery {
          int type;
          int data;
      }
      
      void notifier( ) {
          struct delivery request;
          // Initialization, probably including device
          // Synchronization
          request.type = NOTIFIER;
          FOREVER
          {
              request.data = AwaitEvent( evtType );
              Send( server, &request, ... );
          }
      }
  5. Code for a typical server
      void server( ) {
          struct delivery request;
          // create notifier
          // other initialization
          // synchronization
          FOREVER {
              Receive( &requester, &request, ... );
              switch ( request.type ) {
              case NOTIFIER:
                  data = request.data;
                  Reply( notifier );
                  // use data
              case CLIENT:
                  ...
              }
          }
      }

HALT versus an Idle Task

What do you do when there are no tasks ready to run? Some tasks are probably blocked on AwaitEvent, and will run as soon as an interrupt occurs. What do you do?


Clock Server

Primitives

int Time( )
int Delay( int ticks )
int DelayUntil( int ticks )

Pseudo-implementation

void clock( ) {
    // Create Notifier and send  any initialization data.
    // Initialize self, including setting time to zero.
    FOREVER {
        Receive( &requester, &request, ... );
        switch ( request.type ) {
        case NOTIFIER:
            Reply( notifier, ... )
            // update time and check for terminated delays
        case TIME_REQUEST:
            Reply( requester, time,... )
        case DELAY_REQUEST: 
            // Add requester to list of suspended tasks
        }
        // Reply to any timed-out tasks
    }
}

Comments:

  1. You need a common request type, or possibly a union.
  2. You should notice a typical server pattern.

It's normal to sort the list of suspended tasks. Why?


Return to: