Embedded Operating Systems

Similar documents
The ARM instruction set

Operating System. Hanyang University. Hyunmin Yoon Operating System Hanyang University

ECE 598 Advanced Operating Systems Lecture 11

ECE 598 Advanced Operating Systems Lecture 7

Lecture 10 Exceptions and Interrupts. How are exceptions generated?

Input/Output. Jin-Soo Kim Computer Systems Laboratory Sungkyunkwan University

ECE 598 Advanced Operating Systems Lecture 8

ECE 598 Advanced Operating Systems Lecture 8

embos Real Time Operating System CPU & Compiler specifics for ARM core with ARM RealView Developer Suite 3.0 Document Rev. 1

Systems Architecture The ARM Processor

real-time kernel documentation

Returning from an Exception. ARM Exception(Interrupt) Processing. Exception Vector Table (Assembly code) Exception Handlers (and Vectors) in C code

VE7104/INTRODUCTION TO EMBEDDED CONTROLLERS UNIT III ARM BASED MICROCONTROLLERS

Multiple data transfer instructions

18-349: Introduction to Embedded Real-Time Systems

Project 2: Vectored Interrupts (4%)

Interrupt/Timer/DMA 1

October, Saeid Nooshabadi. Overview COMP 3221

Hercules ARM Cortex -R4 System Architecture. Processor Overview

and ARM Processors Application Note AN-1014 Rev. E

Chapter 4. Enhancing ARM7 architecture by embedding RTOS

Multitasking on Cortex-M(0) class MCU A deepdive into the Chromium-EC scheduler

Systems Architecture The Stack and Subroutines

AN10254 Philips ARM LPC microcontroller family

ARM Architecture (1A) Young Won Lim 3/20/18

Support for high-level languages

ARM Assembly Language

Design and Implementation Interrupt Mechanism

Migrating ARM7 Code to a Cortex-M3 MCU By Todd Hixon, Atmel

Cooperative Multitasking

CISC RISC. Compiler. Compiler. Processor. Processor

William Stallings Computer Organization and Architecture 8 th Edition. Chapter 12 Processor Structure and Function

Boot Loader of Creator S3C4510 張大緯 CSIE, NCKU

Module 8: Atmega32 Stack & Subroutine. Stack Pointer Subroutine Call function

Free-RTOS Implementation

Embedded Systems Ch 12B ARM Assembly Language

ARM968E-S. Technical Reference Manual. Revision: r0p1. Copyright 2004, 2006 ARM Limited. All rights reserved. ARM DDI 0311D

ARM Assembly Programming

Introduction to the ARM Processor Using Intel FPGA Toolchain. 1 Introduction. For Quartus Prime 16.1

ARM Processors ARM ISA. ARM 1 in 1985 By 2001, more than 1 billion ARM processors shipped Widely used in many successful 32-bit embedded systems

ARM Instruction Set Architecture. Jin-Soo Kim Computer Systems Laboratory Sungkyunkwan University

ARM ARCHITECTURE. Contents at a glance:

Outline. ARM Introduction & Instruction Set Architecture. ARM History. ARM s visible registers

IAR PowerPac RTOS for ARM Cores

The ARM Instruction Set

Programming the ARM. Computer Design 2002, Lecture 4. Robert Mullins

ARM Assembly Programming

DEVICE DRIVERS AND INTERRUPTS SERVICE MECHANISM

Stack Frames. September 2, Indiana University. Geoffrey Brown, Bryce Himebaugh 2015 September 2, / 15

ARM System Design. Aim: to introduce. ARM-based embedded system design the ARM and Thumb instruction sets. the ARM software development toolkit

Context Switching & Task Scheduling

ARM Architecture and Assembly Programming Intro

introduction to interrupts

Embedded Seminar in Shenzhen

Part 7. Stacks. Stack. Stack. Examples of Stacks. Stack Operation: Push. Piles of Data. The Stack

18-349: Embedded Real-Time Systems Lecture 2: ARM Architecture

Introduction to the ARM Processor Using Altera Toolchain. 1 Introduction. For Quartus II 14.0

Processor Structure and Function

+ Overview. Projects: Developing an OS Kernel for x86. ! Handling Intel Processor Exceptions: the Interrupt Descriptor Table (IDT)

acret Ameya Centre for Robotics & Embedded Technology Syllabus for Diploma in Embedded Systems (Total Eight Modules-4 Months -320 Hrs.

ARM Embedded Systems: ARM Design philosophy, Embedded System Hardware, Embedded System Software

Grundlagen Microcontroller Interrupts. Günther Gridling Bettina Weiss

ARM-7 ADDRESSING MODES INSTRUCTION SET

Putting it All Together

OPERATING SYSTEM PROJECT: SOS

18-349: Introduction to Embedded Real- Time Systems Lecture 3: ARM ASM

ARM Programmer s Model

ZYNQ/ARM Interrupt Controller

ECE 598 Advanced Operating Systems Lecture 11

ARM Interrupts. EE383: Introduction to Embedded Systems University of Kentucky. James E. Lumpp

ARM Cortex-M4 Architecture and Instruction Set 4: The Stack and subroutines

Black Box Debugging of Embedded Systems

AC OB S. Multi-threaded FW framework (OS) for embedded ARM systems Torsten Jaekel, June 2014

Overview COMP Microprocessors and Embedded Systems. Lectures 18 : Pointers & Arrays in C/ Assembly

William Stallings Computer Organization and Architecture 10 th Edition Pearson Education, Inc., Hoboken, NJ. All rights reserved.

Arm Assembly Language programming. 7. Non-user modes

Modes and Levels. Registers. Registers. Cortex-M3 programming

Stacks and Subroutines

ARM Assembly Programming II

CPUs. Input and output. Supervisor mode, exceptions, traps. Co-processors. Computers as Components 4e 2016 Marilyn Wolf

From last time. What is the effect on R0 (in terms of an equivalent multiplication) of each of the following ARM instructions? ADD R0, R0, R0,LSL #3

Interrupt-Driven Input/Output

Wireless Sensor Networks (WSN)

ARM Assembly Language. Programming

Operating System. Hanyang University. Hyunmin Yoon Operating System Hanyang University

Hi Hsiao-Lung Chan, Ph.D. Dept Electrical Engineering Chang Gung University, Taiwan

Sneha Rajguru & Prajwal Panchmahalkar

MNEMONIC OPERATION ADDRESS / OPERAND MODES FLAGS SET WITH S suffix ADC

Processes. Dr. Yingwu Zhu

15CS44: MICROPROCESSORS AND MICROCONTROLLERS. QUESTION BANK with SOLUTIONS MODULE-4

Implementing Procedure Calls

Process Context & Interrupts. New process can mess up information in old process. (i.e. what if they both use the same register?)

ARM Advanced Interrupt Controller

Chapter 09. Programming in Assembly

12. Interrupts and Programmable Multilevel Interrupt Controller

Interrupts and Low Power Features

LDR R0,=0x L: LDREX R1, [R0] ORR R1, #4 STR R1, [R0] (5) Part a) Why does the 9S12 code not have a critical section?

Description of the Simulator

Operating Systems. Objective. Background

Precept 2: Non-preemptive Scheduler. COS 318: Fall 2018

Transcription:

Embedded Operating Systems Condensed version of Embedded Operating Systems course. Or how to write a TinyOS Part 2 Context Switching John Hatch

Covered in Part One ARM registers and modes ARM calling standard Exception types Mode switching on exception Vector table Context saving and restoring Adjusting the return address for exceptions Data abort handler example

Switching Tasks What s needed Switching between tasks requires: Setting up stacks for each task Tracking stack pointers for each task Priming the stacks with an initial context Saving the full context Saving the task s stack pointer Switching tasks by switching stack pointers Restoring the full context for next task Using the timer to drive context switches

Some Important Points Each mode has its own stack pointer So each mode can have own stack Which we set up in the reset handler Which we can use as temporary scratch pad This will be important for interrupt handling and context switching It will give us a place to temporarily safe registers so that we can use them as variables. Which we will need to shuffle data between modes

Simplifications No heap memory Mode stacks are allocated in the linker definition file and assigned in the reset handler Tasks never return. Implemented as endless loops. Stacks are allocated as stack globals Each timer interrupt will cause a task switch.

Simple Task Switching Overview Reset Startup Initialize mode stacks Jump to main Main Allocate stacks Initialize stacks Set current task Start timer Call SwitchToFirst() Switch to first task Pop context Task One Switch Tasks Save current context Call scheduler Pop new context Task Two Switch Tasks Save current context Call scheduler Pop new context Timer Interrupt Timer Interrupt Using the timer to initiate the task switch. Every time the timer fires we ll switch tasks. Task One Switch Tasks Save current context Call scheduler Pop new context Timer Interrupt Task Two

Hardware Interrupts to CPU Exceptions Hardware Interrupt Sources WDT SWI ARM_CORE0 ARM_CORE1 TIMER0 TIMER1 UART0 UART1 PWM0_1 I2C0 SPI0 SSP0 SSP1 PLL RTC EINT0 EINT1 EINT2 EINT3 ADC0 I2C1 BOD EMAC USB CAN MCI GPDMA TIMER2 TIMER3 UART2 UART3 I2C2 I2S Vectored Interrupt Controller CPU Exceptions IRQ FIQ ARM 7 CPU All hardware interrupts go through the VIC VIC handles 32 interrupt sources VIC is programmable Interrupts can fire either an IRQ or FIQ exception Each sources has a vector that can be programmed with the address of an interrupt handler. When the interrupt fires the IRQ handler gets the function address from the VIC and calls it.

Defining a Task // // TaskOne counts up from 0. // Never exits. // void TaskOne(void) { int count = 0; while(1) { printstring("task one: "); print_uint32(count++); printstring("\n"); } } // delay for(int i=0; i<10000; i++); A task is simply a function that does not return. No where for it to return to since it starts the call chain for its context. Each task has its own stack A stack is simply an array of bytes allocated before the task runs. When the task starts the SP register must point to the highest free address in the stack.

Allocating a stack // Allocate a stack int stackone[stacksize]; // Calculate the beginning of the stack. int taskonesp = stackone + STACKSIZE - 1; A simply way to allocate the stack is simply to define a global array. The stack pointer is initialized to the highest memory location in that array. Each task needs its own stack. The task will use its stack to store locals and make function calls. The stack will also be used to save the task s context when there is a task switch.

Tracking the running task // The value of the current task's stack pointer int currentsp; // The ID of the current task int currenttaskid; // Array of stack pointers. One for each task. int Tasks[NUMTASKS]; A simple way to track which task is running is to have two global variables. currentsp points to the stack of the current running task. currenttaskid is the id of the current running task. The globals make it easy to share information between the C code and the assembly code.

Saving a Task s Full Context When the timer interrupts we need to save the current context on to the current task s stack. We must save the full context, that is all the registers and the value in the SPSR. There are no instructions for saving SPSR directly to memory. The value has to be copied to a register first. The SPSR is the Interrupt Mode s SPSR. We need to get the value over into the Supervisor Mode. If we switch modes then we won t be able to see the Interrupt Mode s SPSR register. So we need to save the SPSR value in a register before switching modes. Be can t use any registers until their values have been saved. But we want SPSR to be the first thing saved so we ll need to juggle a little. We can do this be temporarily saving some registers to the Interrupt mode s stack. That will give use some registers to play with.

How Store Multiple and Load Multiple work To push all the register on to the stack we use the Store Multiple instruction. STMFD SP!, {R0-R12, R14, R15} It always writes the values from the register from lowest to highest to the address pointed to by SP. That means the lowest register is at the lowest memory location, the highest register ends up at the highest memory location

How Store Multiple saves to the stack Stack Before the Call Stack After the Call High memory Stack grows down in memory 0xFF 0xFF 0xFF 0xFF SP before STMFD SP!, {R0-R12, R14, R15} High memory 0xFF 0xFF 0xFF 0xFF R15 R14 R12 R11 R10 R9 R8 R7 R6 R5 R4 R3 R2 R1 R0 SP After Low memory Low memory

Full Context on the Stack This is how we want the task s stack to look after we save the task s full context. It has all the information we need to restore the task later. To get this we first push R0-12, LR, PC to the stack. Then we push the value saved in the Interrupt mode s SPSR. That value was the task s CPSR at the time of the interrupt. No R13(SP) R15 PC R14 LR R12 R11 R10 R9 R8 R7 R6 R5 R4 R3 R2 R1 R0 CPSR Address where the interrupt occurred Values that were in the registers when the interrupt occurred The task s status at the time of the interrupt

Steps for a context switch Save some registers to the IRQ stack so that we can use the registers as variable Remember where we save the original values Save the SPRS, LR and SP for later in the freed registers Switch to the Supervisor mode Push registers to the task s stack Retrieve the values saved on the IRQ stack and save them to the task s stack Save the stack pointer to a global Call the scheduler who will save the value in the global, pick the next task, and set the global with the next task s stack pointer. Pop the next task s context of its stack

First Step to Saving Context 2 R0 R1 R2 R3 R4 R5 R6 R7 R8 R9 R10 R12 R13_IRQ SP R14_IRQ LR R15 PC CPSR SPSR_IRQ 3 1 SP = SP + 12 IRQ Stack R2 R1 R0 Task Stack 1. Save R0, R1, R2 to the IRQ stack so we can use the registers as variables. The IRQ stack is temporary storage 2. Save SPSR, LR, and SP to R0, R1, R2 SP points to where we saved the original values of R0, R1, and R2 on the IRQ stack. We ll need to retrieve the values later so we have to remember where they are saved. 3. Reset IRQ mode s SP We saved SP to R2 so we don t need it anymore We need to make sure the IRQ SP is ready for the next time by moving it back to where it was. Since the stack grow down in memory we need to add 12 bytes to set it back to where it was. 12 bytes is the size of 3 four byte registers.

Second step to saving context 1 Switch to Supervisor Mode R0 R1 R2 R3 R4 R5 R6 R7 R8 R9 R10 R12 R13_SVC SP R14_SVC LR R15 PC CPSR 2 4 3 IRQ Stack R2 R1 R0 Task Stack Task PC Task LR R12 R11 R10 R9 R8 R7 R6 R5 R4 R3 1. Switch to Supervisor Mode 2. Save R1 which has the task s PC value to the stack. 3. Save R14 which has the task s LR to the stack. 4. Save R3-R12 to the stack SPSR_SVC

Third Step to Saving Task Context 4 currentsp 5 Call scheduler R0 R1 R2 R3 R4 R5 R6 R7 R8 R9 R10 R12 R13_SVC SP R14_SVC LR R15 PC CPSR SPSR_SVC 3 2 1 IRQ Stack R2 R1 R0 Task Stack Task PC Task LR R12 R11 R10 R9 R8 R7 R6 R5 R4 R3 R2 R1 R0 Task CPSR 1. Pop the three values saved on the IRQ stack into R3-R5. R2 points to the right position on the IRQ stack. Use R2 as the stack pointer. 2. Save values in R3-R5 to the task s stack. They were the original R0-R2 values. 3. Save value in R0 to the task s stack. It is the task s CPSR at the moment it was interrupted. 4. Save SP to the global variable currentsp. The task s full context is saved to its stack. SP points to the task s context. The context s location is all we need to remember to restore the task later. 5. Call the scheduler.

Restoring a Task 1 currentsp R0 R1 R2 R3 R4 R5 R6 R7 R8 R9 R10 R12 R13_SVC SP R14_SVC LR R15 PC CPSR SPSR_SVC 2 3 Task Stack Task PC Task LR R12 R11 R10 R9 R8 R7 R6 R5 R4 R3 R2 R1 R0 Task CPSR 1. Copy currentsp to R13 Sets SP to point to the next task s context. 2. Pop the first value off the stack and copy it to the SPRS The first value is the task s CPSR. 3. Pop the context using the load multiple and the ^ flag. Single instruction loads all the registers and copies the SPSR to the CPSR. The task switch is complete with everything restored correctly.

Observations The code popping the context and restoring the task assumes there is already a context. So how do we start? The easiest thing to do is to first prime each stack with an initial context. The initial context should have the same structure as saved context. The only values that matter are the PC and CPSR. The PC should be the address of the task function The CPSR should be set to the mode we want task to run in. In this case it is the Supervisor mode. Having a initial context means we don t need to special case running a task for the first time. To start task switching we call a simple function that just pops the first context.

Main starts everything int main(void) { inithardware(); } // Setup initial stacks for tasks Tasks[0] = initialize_stack(stackone, (void*)taskone); Tasks[1] = initialize_stack(stacktwo, (void*)tasktwo); // Set current to first task currenttaskid = 0; currentsp = Tasks[currentTaskID]; initialize_timer_tick(); // Pop the first context to start task running. switch_to_current(); // Never gets here. printstring("main completed.\n"); while(1); return 0; Initializes two stacks. One for each task. Sets the currenttaskid and currentsp to the first task. Turns on the hardware timer to interrupt every 100 milliseconds. Starts multitasking Calls switch_to_current which pops the context pointed to by currentsp. The call never returns since it switches execution over to the tasks.

Priming the Stack for Task Switching int initialize_stack(int* stack, void* task_address) { // Calculate the top of the stack. int *stkptr = (int*)(stack + STACKSIZE - 1); *(stkptr) = (int)task_address; // PC *(--stkptr) = (int)0x0e0e0e0e; // R14 (LR) *(--stkptr) = (int)0x0c0c0c0c; // R12 *(--stkptr) = (int)0x0b0b0b0b; // R11 *(--stkptr) = (int)0x0a0a0a0a; // R10 *(--stkptr) = (int)0x09090909; // R9 *(--stkptr) = (int)0x08080808; // R8 *(--stkptr) = (int)0x07070707; // R7 *(--stkptr) = (int)0x06060606; // R6 *(--stkptr) = (int)0x05050505; // R5 *(--stkptr) = (int)0x04040404; // R4 *(--stkptr) = (int)0x03030303; // R3 *(--stkptr) = (int)0x02020202; // R2 *(--stkptr) = (int)0x01010101; // R1 *(--stkptr) = (int)0x00000000; // R0 *(--stkptr) = ARM_SVC_MODE_ARM; // Initial CPSR The stack grows down in memory so we want to start by calculating the top of the stack. Then fill in the initial data starting at the top and working down. The important values are the task_address and the initial CPSR. LR or return address doesn't matter since the task is a infinite loop and should not exit. Number values are simply markers that can help in debugging. } return (int)(stkptr);

Starting the first task /* * Switch to current task without saving previous context. * Used to run the first task the first time. * * Pops the initial context setup up by the initialize_stack function. * That means this function will act like a return to the task. * */ switch_to_current: /* Switch to the current task's stack. */ ldr r0, =currentsp /* Load r0 with the address for currentsp */ ldr sp, [r0] /* Load sp with the value stored at the address */ /* sp now points to the task's stack. */ /* Set SPSR to the CPSR for the task. */ ldmfd sp!, {r0} /* Pop the first value from the stack into r0. */ /* That value was the CPSR for the task. */ msr spsr_cxsf, r0 /* Load SPSR with that CPSR value in r0. */ /* Run task. */ ldmfd sp!, {r0-r12, lr, pc}^ /* Pop the rest of the stack setting regs and pc for the task */ /* Acts like a call the task. */

Timer Interrupt Timer is set to interrupt every 100 milliseconds. The timer interrupt causes an IRQ exception. The CPU switches the IRQ mode and jumps to the IRQ vector. The IRQ vector is set to the address of the context switching function called SwitchingIRQHandler. The handler will save the current context, call the scheduler, then restore the context selected by the scheduler.

Context Switcher SwitchingIRQHandler: /* Current mode: IRQ mode with IRQ stack */ sub lr, lr, #4 /* LR offset to return from this exception: -4. */ stmfd sp!, {r0-r2} /* Push working registers r0-r3. */ mrs r0, spsr /* Save task's cpsr (stored as the IRQ's spsr) to r0 */ mov r1, lr /* Save lr which is the task's pc to r1 */ mov r2, sp /* Save exception's stack pointer to r2, used to retrieve the data off of the IRQ stack. */ add sp, sp, #(3 * 4) /* Reset IRQ's stack back to where it was so it will be ready next time. */ msr cpsr_c, #(INT_DISABLED MODE_SVC) /* Change to SVC mode with interrupts disabled. */ /* Current mode: SVC mode with SVC stack. This is the stack of interrupted task. */ stmfd sp!, {r1} /* Push task's PC */ stmfd sp!, {lr} /* Push task's LR */ stmfd sp!, {r3-r12} /* Push task's R4-R12 */ ldmfd r2!, {r3-r5} /* Move r0-r3 from exception stack to task's stack. First load r3-r5 with r0-r2 */ stmfd sp!, {r3-r5} /* then by pushing them onto the task's stack */ stmfd sp!, {r0} /* Push task's CPSR, which was the IRQ mode's SPSR to the task stack. */ mov r0, sp /* Save task's stack pointer for call to switcher */ bl scheduler /* Call the task scheduler which sets currentsp and resets the timer. */ ldr r0, =currentsp /* Set sp to the new task's sp by reading the value stored in currentsp. */ ldr sp, [r0] ldmfd sp!, {r0} /* Pop task's CPSR and restore it to spsr. */ msr spsr_cxsf, r0 /* spsr will be moved to cpsr when we pop the context with ldmfd */ ldmfd sp!, {r0-r12, lr, pc}^ /* Pop task's context. Restores regs and cpsr which reenables interrupts. */

Simple Scheduler // Simple round robin scheduler. // // Saves currentsp, increments the task ID // Set current SP to the next task. // void scheduler() { // Reset timer WRITEREG32(T0IR, 0xFF); } Tasks[currentTaskID++] = currentsp; if (currenttaskid > NUMTASKS) { currenttaskid = 0; } currentsp = Tasks[currentTaskID]; All the scheduler needs to do to track each task is to save each task s stack pointer. The stack pointer points to where the task s context is saved. The scheduler uses an array to store the stack pointers. The task id is the index to where its data is saved. To switch task all the scheduler needs to do is set the global currentsp. The interrupt routine will restore the context pointed to by currentsp. The scheduler returns back to the interrupt handler.

Summary Tasks are functions that don t return. Each task has a stack. The stack allocated statically and is global. The stack is primed with an initial context containing the task s address. The timer is turned on. The timer causes IRQ interrupts The IRQ interrupts are handled by a context switching function The function saves the current context, calls the scheduler, and then restores the context selected by the scheduler. The scheduler saves the pointer to the context to an array and retrieves the next task s stack pointer. The switching function restores the new context.

Next Steps Quantums Quantums are the period of time a task gets to run before being switched. Current setup switches task on each interrupt. Better to count interrupts then switch. Allows clock to run at a different rate then switching Timer can run at 1 milliseconds and switches only every 100 interrupts Then the quantum is 100 milliseconds. Delays Add a functions that puts the task on a wait list for a set time. Task does not run while on wait. Delay count is decreased every interrupt. When delay count is zero the task is placed back on the run list. Events Semaphores are used to synchronize tasks, they are essential in a multi-tasking OS. Semaphores are acquired or released. Tasks that acquire the semaphore can run. Tasks are blocked if they can t get the semaphore. By acquiring and releasing semaphores tasks can access shared resources without corrupting data.