CS452 - Real-Time Programming - Winter 2017
Lecture 12 - AwaitEvent, Clock Server
Pubilc Service Annoucements
-
Due date for kernel 3: 6 February, 2017.
-
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.
-
They are wired together (daisy-chained) so that all 64
interrupts assert a single IRQ output which is connected to
the IRQ input of the CPU.
Base addresses
-
VIC1:
0x800B0000
-
VIC2:
0x800C0000
Basic Operation
VIC powers up with
- all vectored interrupts disabled.
- all interrupts giving IRQ
- all interrupts masked
Most likely you will change only the last of them.
Procedure
Initialization
-
leave protection off, unless you are in doubt about your
partner's ethics or competence.
-
enable in VICxIntEnable only when you are ready to handle the
interrupt
On an interrupt, the kernel
- reads the VICxIRQStatus register,
- chooses which interrupt to handle and
- clears the interrupt source in the device.
For debugging
- Use VICxSoftInt and VICxSoftIntClear to turn interrupt sources off and
on in software
Hardware Definitions
Registers for Basic Operation
Register Name |
Offset |
R/W |
Description |
VICxIRQStatus |
0x00 |
RO |
One bit for each interrupt source
1 if interrupt is asserted and enabled
|
VICxFIQStatus |
0x04 |
RO |
As above for FIQ |
VICxRawIntr |
0x08 |
RO |
As above but not masked |
VICxIntSelect |
0x0c |
R/W |
0: IRQ, 1: FIQ |
VICxIntEnable |
0x10 |
R/W |
0: Masked, 1: Enabled |
VICxIntEnClear |
0x14 |
WO |
Clears bits in VICxIntEnable |
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
-
Create user tasks with interrupts enabled.
-
Enable interrupt in device
- Enable interrupt in ICU
-
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
-
Remember that you started in IRQ mode.
-
Switch to svc mode.
-
Do the context switch.
-
Look at the active interrupt sources; decide which one you
will service; turn it off.
-
Find which task is blocked waiting for the interrupt.
-
Set up its return value, and make it ready.
Tasks waiting on interrupts are normally high priority, and
usually run right away.
If a second interrupt occurs immediately
-
The CPU checks AND( IRQ, NOT( IRQ disabled bit set in CPSR ) )
before each instruction fetch.
-
If it is asserted an IRQ exception is taken in place of next
instruction fetch.
-
Possibly, zero instructions of the active task are executed.
-
Make sure that this case works
- Context switch into kernel
- Turn off interrupt in device
-
Do you need to turn off the interrupt in the ICU?
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.
-
A hardware interrupt occurs.
-
The registers of the interrupted task are pushed onto its
stack.
-
The lr_IRQ overwrites the lr on the stack.
-
When the interrupted task is next scheduled, registers r0-r14
are restored in system mode.
-
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 )
-
eventType
identifies the event on which a user
program wishes to wait.
-
In selecting the argument for AwaitEvent, the application
programmer is required to know more about the hardware
than is optimal. For us that's not a problem because we
combine application and system programmer. When they are
split apart, however, the application programmer must
know aspects of the hardware that operating systems
normally hide.
-
Might something like autoconf, establishing an extra level
of indirection, be a solution?
-
The return value contains any volatile data picked up by the
server, and/or error codes.
More About AwaitEvent
Argument
-
Somewhere there is a list of event types
- Application programmer knows the list
- Kernel can respond to each event type on the list
-
This is not very portable
- The list would normally be the union of all types occurring on all
hardware
- This is the Windows problem
Processing in the kernel
- Initialization
- Kernel creates first user task
- Kernel always has
IRQ masked
- Kernel initializes
ICU
- For each device
- Kernel initializes hardware
- Kernel turns on interrupt(s) in the device
- Procedure
- Kernel activates first user task
active = schedule( ) // first user task
nextReq = activate( active );
-
The first user bootstraps the user program into existence
using
Create
;
-
Then in response to an interrupt the kernel
- identifies interrupt source
-
identifies the task waiting on the interrupt, which we
call a Notifier
- acquires volatile data
- re-enables interrupt in the device
- re-enables interrupt in the CPU during task activation (eg,
movs
)
- puts volatile data into
AwaitEvent
's return value
- Makes Notifier ready
- Notifier
- collects and packages data
Send
s to server
- Eventually Server
Reply
s to Notifier
- acts on the data
- Advantage
- Clean consistent user code
- Disadvantage
- Kernel has to know a lot about the hardware.
- Hardware knowledge split between Notifier and kernel
-
In the future the kernel may need to do too much.
Clock Server, Task Structure
What does the clock server do?
-
Increments the time every time the timer counts down.
-
Gives out the time.
-
Accepts delay requests; queues delayed tasks; readies delayed tasks
when their delay period is complete.
How is AwaitEvent Used?
AwaitEvent is used to update the time.
-
The event is the timer counting down to zero.
-
When AwaitEvent returns the time is increased by one tick.
-
When the time is increased by a tick?
Generalizing
- There should (almost) always be a task blocked on AwaitEvent for every
interrupt type. Why?
- A server cannot call AwaitEvent. Why?
- We call the task that calls AwaitEvent a Notifier. Why?
- Code for a typical Notifier
struct delivery {
int type;
int data;
}
void notifier( ) {
struct delivery request;
// Initialization, probably including device
// Synchronization
FOREVER {
request.data = AwaitEvent( evtType );
request.type = NOTIFIER;
Send( server, &request, ... );
}
}
- Code for a typical server
void server( ) {
struct delivery request;
// create notifier
// other initialization
// synchronization
FOREVER {
Receive( &requester, &request, ... );
switch ( request.type ) {
case NOTIFIER:
Reply( notifier );
data = request.data;
// 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?
- Idle task
- lowest priority
- diagnose system: at least % of time idle should be known.
- search for ETI
- HALT
- turns off CPU clock
- save power (battery)
- provided two ways
- through System Controller Co-processor
- through the TS-7200 clock controller
- IRQ path is asynchronous, so it works when the clock is off
Clock Server
Primitives
int Time( )
- Clock server starts at zero when it initializes
- Unit of time is tick
int Delay( int ticks )
- Note error returns
- You might want to add an error for negative arguments
- ticks is usually calculated, and a negative value is an early
warning of falling behind.
int DelayUntil( int ticks )
- Can be constructed from the above two primitives.
Pseudo-implementation
void clock( ) {
// Create Notifier and send any initialization data
// Initialize self
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:
- You need a common request type, or possibly a union.
- You should notice a typical server pattern.
- Notifier updates data
- Client who can be serviced now is serviced
- Client who needs service in the future is suspended
- List of suspended tasks is checked regularly
It's normal to sort the list of suspended tasks. Why?
Return to: