Threadx-ARM EXCEPTION HANDLING

Introduction

An exception is an asynchronous event or error condition that disrupts the normal flow
of thread processing. Usually, an exception must be handled immediately, and then control
is returned to thread processing. There are three exception categories in the ARM
architecture, as follows:
• Exceptions resulting from the direct effect of executing an instruction
• Exceptions resulting as a side effect of executing an instruction
• Exceptions resulting from external interrupts, unrelated to instruction execution
When an exception arises, ARM attempts to complete the current instruction, temporarily
halts instruction processing, handles the exception, and then continues to process
instructions.
The processor handles an exception by performing the following sequence of actions.

  1. Save the current value of CPSR into the SPSR of the new operating mode for later
    return.
  2. Change to the operating mode corresponding to the exception.
  3. Modify the CPSR of the new operating mode. Clear the T (Thumb) bit (bit 5) in
    preparation for execution in ARM 32-bit mode. If an IRQ interrupt is present, set
    the I-bit (bit 7) to disable further IRQ interrupts. If an FIQ interrupt is present, set
    the F-bit (bit 6) and the I-bit (bit 7) to disable further FIQ interrupts.
  4. Save the current PC (program counter—address of the next instruction) in register
    r14 of the new operating mode.
  5. Change the PC to the appropriate exception vector as illustrated in Figure 13.1,
    which is where the application software interrupt handling starts.
    ARM has a simple exception and interrupt handling architecture. There are seven
    interrupt vectors, starting at address 0x00000000. Each vector is one 32-bit word and
    contains an actual ARM instruction. Typically, most applications place a load-to-pc
    instruction at each of the seven exception vector locations. Figure 13.1 shows the sevenentry
    ARM vector table.
    在这里插入图片描述
    Note that address 0x00000014 does not appear in the above vector table. This location
    was used in earlier ARM processors to handle address exceptions when a 26-bit
    address space was used. This location is not used in current ARM processors, and is
    reserved for future expansion.
    Some implementations of the ARM architecture add additional IRQ vectors so that each
    interrupt source can have a separate vector. This scheme has the advantage that the interrupt
    handling software no longer has to determine which interrupt source caused the interrupt.
    ATMEL is an ARM licensee that provides these additional IRQ handling capabilities.

ThreadX Implementation of ARM Exception Handling

ThreadX is a popular RTOS for embedded designs using the ARM processor. ThreadX
complements the ARM processor because both are extremely simple to use and are very
powerful.

Reset Vector Initialization
ThreadX initialization on the ARM processor is straightforward. The reset vector at address
0x00000000 contains an instruction that loads the PC with the address of the compiler’s initialization routine. Figure 13.2 contains an example of a typical ThreadX vector table,
with the reset vector pointing to the entry function __main of the ARM compiler tools.
Figure 13.2 ThreadX vector table.

EXPORT __vectors
__vectors
LDR pc,=__main					; Reset goes to startup function
LDR pc,=__tx_undefined			; Undefined handler
LDR pc,=__tx_swi_interrupt		; Software interrupt handler
LDR pc,=__tx_prefetch_handler	; Prefetch exception handler
LDR pc,=__tx_abort_handler		; Abort exception handler
LDR pc,=__tx_reserved_handler	; Reserved exception handler
LDR pc,=__tx_irq_handler		; IRQ interrupt handler
LDR pc,=__tx_fiq_handler		; FIQ interrupt handler

There are several different ways to initialize the vector table. You can set it up by
loading it directly to address 0 with a JTAG debug device. Alternatively, your system
may copy the vector table to address 0 after address 0 has been mapped to RAM instead
of flash memory.
The vector table is typically located in the ThreadX low-level initialization file tx_
ill.s and may be modified according to application needs. For example, in many applications,
the reset vector will actually point to some low-level application code that is
responsible for preparing the hardware memory for execution. This code can also copy
itself from flash memory to RAM if that is necessary for the application. Note that
ThreadX can execute in place out of flash memory or in RAM. Once finished, the application
low-level code must jump to the same location specified in the original reset vector.
For example, suppose a low-level initialization routine called __my_low_level_init
is required to execute before anything else in the system. The application would have to
change the reset vector to point to this routine:
LDR pc,=__my_low_level_init
At the end of __my_low_level_init, the code would have to either branch (jump) or
load PC to call the original compiler startup code, as illustrated in Figure 13.3.
Figure 13.3 ThreadX low-level initialization.

EXPORT __my_low_level_init
__my_low_level_init
;
; Application low-level code here!
;
B __main

Compiler Initialization
Shortly after the ARM processor executes the reset vector, the system executes the C compiler’s
initialization code. In this example, the name of the compiler’s entry point is __main.
The C compiler initialization is responsible for setting up all application data areas, including
initialized and uninitialized global C variables. The C run-time library is also set up
here, including the traditional heap memory area. If some of the application code was written
in C++, the initialization code instantiates all global C++ objects. Once the run-time
environment is completely set up, the code calls the application’s “main” entry point.

ThreadX Initialization
The ThreadX initialization typically occurs from inside the application’s main function.
Figure 13.4 shows a typical application main function that uses ThreadX. It is important
to note that tx_kernel_enter does not return to main. Hence, any code after tx_
kernel_enter will never be executed.
Figure 13.4 Typical application main function that uses ThreadX.

/* Define main entry point. */
void main()
{
/* Enter the ThreadX kernel. */
tx_kernel_enter();
}

When ThreadX is entered via tx_kernel_enter, it performs a variety of actions in
preparation for multithreading on the ARM processor. The first action is to call ThreadX’s
internal low-level initialization function _tx_initialize_low_level. This function sets
up stack pointers in the IRQ, FIQ, and SYS modes. This function also ensures that the vector
table is properly initialized at address 0. Typically, _tx_initialize_low_level also
sets up the periodic timer interrupt. When the low-level initialization returns, ThreadX
initializes all its system components, which includes creating a system timer thread for the
management of ThreadX application timers.
After basic ThreadX initialization is complete, ThreadX calls the application’s
ThreadX initialization routine, tx_application_define. This is where the application
can define its initial ThreadX system objects, including threads, queues, semaphores,
mutexes, event flags, timers, and memory pools. After tx_application_define
returns, the complete system has been initialized and is ready to go. ThreadX starts
scheduling threads by calling its scheduler, _tx_thread_schedule.

Thread Scheduling
ThreadX scheduling occurs within a small loop inside _tx_thread_schedule. ThreadX
maintains a pointer that always points to the next thread to schedule. This pointer name
is _tx_thread_execute_ptr; this pointer is set to NULL when a thread suspends. If all
threads are suspended, it stays NULL until an ISR executes and makes a thread ready.
While this pointer is NULL, ThreadX waits in a tight loop until it changes as a result of
an interrupt event that results in resuming a thread. While this pointer is not NULL, it
points to the TX_THREAD structure associated with the thread to be executed.
Scheduling a thread is straightforward. ThreadX updates several system variables to
indicate the thread is executing, recovers the thread’s saved context, and transfers control
back to the thread.

Recovering Thread Context
Recovering a thread’s context is straightforward. The thread’s context resides on the
thread’s stack and is available to the scheduler when it schedules the thread. The contents
of a thread’s context depends on how the thread last gave up control of the processor. If
the thread made a ThreadX service call that caused it to suspend, or that caused a higherpriority
thread to resume, the saved thread’s context is small and is called a “solicited”
context. Alternatively, if the thread was interrupted and preempted by a higher-priority
thread via an ISR, the saved thread’s context contains the entire visible register set and is
called an “interrupt” context.
A solicited thread context is smaller because of the implementation of the C language
for the ARM architecture. The C language implementation divides the ARM register set
into scratch registers and preserved registers. As the name implies, scratch registers are not
preserved across function calls. Conversely, the contents of preserved registers are guaranteed
to be the same after a function call returns as they were before the function was
called. In the ARM architecture, registers r1 through r11 and r14 (LR) are considered preserved
registers. Because the thread suspension call is itself a C function call, ThreadX can
optimize context saving for threads that suspend by calling a ThreadX service. The minimal
ThreadX solicited context is illustrated in Figure 13.5.
Figure 13.5 Minimal solicited context.
在这里插入图片描述
As Figure 13.5 illustrates, the solicited thread context is extremely small and can
reside in 48 bytes of stack space. Thus, saving and recovering the solicited thread context
is extremely fast.
An interrupt context is required if the thread was interrupted and the corresponding
ISR processing caused a higher-priority thread to be resumed. In this situation, the
thread context must include all the visible registers. The interrupt thread context2 is
shown in Figure 13.6.
在这里插入图片描述
Figure 13.7 contains the ARM code fragment that restores both solicited and interrupt
thread contexts in a ThreadX ARM application. Recall from Chapter 5 that some ARM
instructions can be conditionally executed. If the stack type is 0, indicating a solicited stack
frame, the MSRNE and LDMNEIA instructions do not execute because of the “NE” and
“NEIA” conditional execution modifiers3 on the these instructions, respectively. Thus, if
the stack type is 0, the interrupt stack context is not restored; instead, a solicited thread-context
is restored.
在这里插入图片描述
There are two different places in this code example where execution can return to the
caller. If the LDMNEIA instruction is executed (stack type == 1, or interrupt), it causes an
immediate return to the point of interrupt because it modifies the PC register. Thus, if the
stack type indicates an interrupt stack frame, none of the code following the LDMNEIA
instruction is executed. The other place where execution can return to the caller is through
either the “BX LR” instruction or the “MOV PC, LR” instruction. (Only one of these
instructions will be present in the code because they are conditionally included at compiletime.
) If the LDMNEIA instruction is not executed (stack type == solicited, or 0), execution
falls through to the LDMIA instruction, which sets up the lr register with the return
address to be used in the “BX LR” or “MOV PC, LR” instruction.
As the above figure shows, recovering an interrupt thread’s context requires only four
instructions.
The first return method in Figure 13.7 recovers every processor resource for the
thread, and the second method recovers only the resources presumed to be saved across
function calls. The key point is that what the RTOS must save when a thread makes a
function call (ThreadX API call, actually) is much less than what it must save when a
thread is interrupted.

Saving Thread Context
The saving of a thread’s context occurs from within several locations inside the ThreadX
RTOS. If an application makes a ThreadX service call that causes a higher-priority thread
to preempt the calling thread, the ThreadX service call will call a routine named tx
thread_system_return to create a solicited thread context on the thread’s stack (in the
format shown in Figure 13.5) and return to the ThreadX scheduler. Alternatively, if
ThreadX detects that a higher-priority thread became ready during the application’s ISR
(by application calls to ThreadX services), ThreadX creates an interrupt thread context
on the interrupted thread’s stack and returns control to the ThreadX scheduler.
Note that ThreadX also creates an interrupt thread context when each thread is created.
When ThreadX schedules the thread for the first time, the interrupt thread context
contains an interrupt return address that points to the first instruction of the thread.

ThreadX Interrupt Handling
ThreadX provides basic handling for all ARM program exceptions and interrupts. The
ThreadX program exception handlers are small spin loops that enable the developer to
easily set a breakpoint and detect immediately when a program exception occurs. These
small handlers are located in the low-level initialization code in the file tx_ill.s.
ThreadX offers full management of the IRQ and FIQ interrupts, which are described in
the following sections.

IRQ Interrupt Handling
ThreadX provides full management of ARM’s IRQ interrupts. As described before, the
IRQ interrupt processing starts at address 0x18, which typically contains the instruction:
LDR pc,=__tx_irq_handler ; IRQ interrupt handler
This instruction sets the PC to the address of __tx_irq_handler, the ThreadX IRQ
handler. Figure 13.8 contains an example of a basic ThreadX IRQ handler.
Figure 13.8 Example of a ThreadX IRQ handler.

EXPORT __tx_irq_handler
EXPORT __tx_irq_processing_return
__tx_irq_handler
/* Jump to context save to save system context. */
B _tx_thread_context_save
__tx_irq_processing_return
/* Application ISR call(s) go here! */
BL application_irq_handler
/* Jump to context restore to restore system context. */
B _tx_thread_context_restore	

After _tx_thread_context_save returns, execution is still in the IRQ mode. The
CPSR, point of interrupt, and all C scratch registers are available for use. At this point,
IRQ interrupts are still disabled. As illustrated in Figure 13.8, the application’s IRQ handler
is called between the ThreadX context save and context restore calls. If there is only
one IRQ interrupt source, application_irq_handler can simply process that interrupt
and return. If there are multiple IRQ interrupt sources, application_irq_handler
must examine the implementation-specific interrupt status register and dispatch to the
appropriate handling routine.

FIQ Interrupt Handling
ThreadX also provides full management of the ARM’s FIQ interrupts; the process is
quite similar to the IRQ processing described previously. The FIQ interrupt processing
starts at address 0x1C, which typically contains the instruction:
LDR pc,=__tx_fiq_handler ; FIQ interrupt handler
This instruction sets the PC to the address of __tx_fiq_handler, the ThreadX FIQ
handler. Figure 13.9 contains an example of a basic ThreadX FIQ handler:
Figure 13.9 Example of a ThreadX FIQ Handler.

EXPORT __tx_fiq_handler
EXPORT __tx_fiq_processing_return
__tx_fiq_handler
/* Jump to context save to save system context. */
B _tx_thread_fiq_context_save
__tx_fiq_processing_return
/* Application ISR FIQ call(s) go here! */
BL application_fiq_handler
/* Jump to context restore to restore system context. */
B _tx_thread_fiq_context_restore

After _tx_thread_fiq_context_save returns, execution is still in the FIQ mode.
The CPSR, point of interrupt, and all C scratch registers are available for use. At this
point, FIQ and IRQ interrupts are still disabled. As shown in Figure 13.9, the application
FIQ handler is called between the ThreadX context save and restore calls. If there is
only one FIQ interrupt source, application_fiq_handler can process that interrupt
and return. If there are multiple FIQ interrupt sources, application_fiq_handler
must examine the implementation-specific interrupt status register and dispatch to the
appropriate handling routine.
Note that ThreadX FIQ interrupt management is conditionally defined. If you want
your application to handle all FIQ interrupts without ThreadX support, simply build
ThreadX without defining TX_ENABLE_FIQ_SUPPORT.

Internal Interrupt Processing
ThreadX interrupt processing is tailored to the ARM architecture. There are several
optimizations and additional interrupt handling features in ThreadX that are not found
in other commercial RTOSes. We discuss some of these features below as we describe
how ThreadX processes interrupts.

Idle System
Unlike other RTOSes that require a background thread to be continuously running,
ThreadX implements its idle loop as a simple three-instruction sequence in assembly
code. These three instructions are designed to wait for the next thread to be ready for
scheduling. There are several advantages to this approach, including not wasting the
memory resources associated with having an idle thread—including the thread’s Control
Block, stack, and instruction area. Note that all the threads in the system still require
resources. However, with this idle loop approach, ThreadX need not force the application
to maintain a dummy thread that executes when the system is idle. A dummy thread
would require a TCB and a stack, and would eliminate an optimization in the interrupt
handling because the thread’s context would always need to be saved. The other advantage
involves interrupt processing. If ThreadX detects that an interrupt has occurred
when the system is idle (in the scheduling loop), no context (registers) need to be saved
or restored. When the interrupt processing is complete, a simple restart of the scheduling
loop will suffice.

Saving Solicited Thread Contexts
If an interrupt does occur during a thread’s execution, ThreadX initially saves only the
thread’s scratch registers via the _tx_thread_context_save routine. Assuming the ISR
contains C calls, the compiler will preserve the non-scratch registers during the ISR processing.
If the ISR processing does not make a higher-priority thread ready, the minimal
context saved (r0-r3, r10, r12, SPSR, and r14) is recovered, followed by an interrupt
return to the interrupted thread.

Saving Interrupt Thread Contexts
If an application interrupt service routine makes a higher-priority thread ready, ThreadX
builds an interrupt thread context stack frame (see Figure 13.6) and returns to the
thread scheduler.

Nested Interrupt Handling
ThreadX supports nested interrupts on the ARM architecture. Simple FIQ nesting on
top of IRQ interrupts is inherently supported. ThreadX conditionally supports nested
IRQ and/or FIQ interrupts. The reason nested interrupts are conditionally supported is
due to an interesting aspect of the ARM interrupt architecture that requires additional
code and overhead to support. If an application does not need nested interrupt support,
this complexity is avoided by default.
As mentioned before, the point of interrupt is stored in register r14 (LR) of the responsible
interrupt mode. If function calls are subsequently made from the interrupt mode, the
r14 (LR) register is then used for storing the function call return address. ThreadX handles
this case by saving the point of interrupt to the thread’s stack early in the tx_thread
context_save routine. However, if the application also wishes to re-enable interrupts
while running in an interrupt mode, the function call return addresses r14 (LR) can be corrupted.
Consider the scenario where the processor is running an application ISR in an
interrupt mode (either IRQ or FIQ), has enabled the same interrupt, and has also made at
least one function call. At this point, the return address of the last function call is stored in
r14 (LR). If the same interrupt occurs, the processor immediately stores the point of interrupt
in r14 (LR), which effectively destroys its contents—the last function call return
address from the previous interrupt processing. This doesn’t happen in non-nested interrupts
because the r14 (LR) of the mode interrupted is not the same register as the r14 (LR)
of the interrupt mode. Basically, the ARM architecture is not designed for enabling interrupts
inside of ISRs running in either the IRQ or FIQ interrupt mode.
In order to support nested interrupts, ThreadX provides the application with the
ability to execute its interrupt handling in System Mode (SYS) rather than one of the
interrupt modes. This solves the problem because now there is a separate r14 (LR) for
the ISR’s function calls and the point of interrupt. The function call return addresses are
stored in SYS’s r14 (LR) while the point of interrupt is stored in the interrupt mode
(IRQ or FIQ) r14 (LR).
To enable ThreadX nested interrupt support, the application must build the ThreadX
library (and tx_ill.s) with TX_ENABLE_IRQ_NESTING and/or TX_ENABLE_FIQ_
NESTING defined. Figure 13.10 contains the calls that must be made between the ThreadX
context save/restore calls in the application interrupt handler to enable and disable nesting
for IRQ interrupt handlers.
Figure 13.10 Enable and disable nesting for IRQ interrupt handlers.

BL _tx_thread_irq_nesting_start
/* Application ISR call(s) go here! IRQ interrupts may also
be enabled here or inside the application interrupt
handler. */
BL application_irq_handler
BL _tx_thread_irq_nesting_end

Figure 13.11 contains the calls that must be made between the ThreadX context
save/restore calls for FIQ interrupt handlers (assuming TX_ENABLE_FIQ_SUPPORT is
also defined).
Figure 13.11 Enable and disable nesting for FIQ interrupt handlers.

BL _tx_thread_fiq_nesting_start
/* Application ISR call(s) go here! FIQ interrupts may
also be enabled here or inside the application interrupt
handler. */
BL application_fiq_handler
BL _tx_thread_fiq_nesting_end

The nesting start service switches to the SYS mode of the ARM. However, it does not
re-enable interrupts. This is left to the application to do. Conversely, the nesting end service
switches back to the original interrupt mode. Interrupts must not be enabled once
the nesting end routine returns.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值