# Operating System Capstone - Lab3
## Basic Exercise 1 - Exception - 30%
#### Task1: Switch from EL2 to EL1 .

We need to save the state after setting system variables(e.g. stack pointer)
> in start.S file
```asm=
... //set _dtb_ptr
bl from_el2_to_el1 ---> change el2 -> el1
... //set_stack_pointer ---> set el1 stack pointer
... //clear bss
bl main // jump to C code
```
> from_el2_to_el1 function code
```asm=
from_el2_to_el1:
mov x0, (1 << 31) // EL1 uses aarch64
msr hcr_el2, x0
//0x3c5 = 0b1111000101
mov x0, 0x3c5 // EL1h (SPSel = 1) with interrupt disabled
msr spsr_el2, x0
msr elr_el2, lr
eret // return to EL1
```
- `hcr_el2 `(Hypervisor Configuration Register): This register controls various aspects of the hypervisor's configuration and operation when the processor is in EL2. One important bit in this register is E2H (bit 32), which determines the execution state (AArch64 or AArch32) when the processor is executing at EL1. By setting the E2H bit to 1, we configure the processor to use AArch64 mode at EL1. This is crucial because different execution states have different instruction sets, register sets, and system registers.


- `spsr_el2` (Saved Program Status Register): This register holds the saved processor state when an exception is taken to EL2. It is used to restore the processor state when returning from an exception using the eret instruction. The spsr_el2 register contains important information about the processor state, such as the current mode (EL1h, EL1t, etc.), interrupt status (enabled or disabled), and other status flags. By configuring spsr_el2 with the appropriate values, we set the desired processor state at EL1 after the eret instruction. In this case, we want to set EL1 to EL1h mode with interrupts disabled.
- In this case, we set spsr_el2 = 0b1111000101
- M[4:0] bits (bits 0-4) to 0b10101 (0x15), which corresponds to EL1h mode (with stack pointer sp1 selected).


>t: Indicates use of the SP0 Stack Pointer.
> h: Indicates use of the SPx Stack Pointer.
- A bit (bit 8) to 1, which disables the SError (System Error) interrupts.
- I bit (bit 7) to 1, which disables the IRQ (Interrupt Request) interrupts.
- F bit (bit 6) to 1, which disables FIQ (Fast Interrupt Request) interrupts.

- `elr_el2` (Exception Link Register): This register holds the return address after an exception return when the processor is at EL2. When executing the `eret` instruction, the processor uses the value stored in `elr_el2` as the address to resume execution. By setting the `elr_el2` register to the value in the link register (lr), we tell the processor to resume execution at the address stored in lr after returning to EL1.
- `eret`: The Exception Return (ERET) instruction is used to return from an exception. When executed, it switches the exception level (in this case, from EL2 to EL1) and resumes execution at the address specified in the `elr_el2` register. The processor state at EL1 is determined by the `spsr_el2` register, which we configured earlier.
#### Task2: Add a command that can load a user program in the initramfs. Then, use eret to jump to the start address.(EL1->EL0)
Goal: load user programs and execute them in EL0 by eret.
1. load(exec) the file to the target(jump address) first
```cpp=
char* target = (char*) 0x20000;
for(unsigned i = 0; i<f[targetfile_num].file_size; i++){
*target++ = f[targetfile_num].file_content_head[i];
}
```
2. Move to EL0 then execute the file
- set spsr_el1 to 0x3c0
```asm=
msr spsr_el1, 0x3c0;
```
> SPSR_EL1, Saved Program Status Register (EL1)
> The SPSR_EL1 characteristics are:
>
> Purpose
> Holds the saved process state when an exception is taken to EL1.
0x3c0 - 0b0011'1100'0000

- set elr_el1 to the program’s start address.
```asm=
msr elr_el1, 0x20000;
```
`elr_el1` (Exception Link Register): This register holds the return address after an exception return when the processor is at EL1. When executing the eret instruction, the processor uses the value stored in `elr_el1` as the address to resume execution. By setting the `elr_el1` register to the value in the link register (lr), we tell the processor to resume execution at the address stored in lr after returning to EL0.
- set the user program’s stack pointer to a proper position by setting sp_el0.
```cpp=
unsigned long sp = (unsigned long) simple_malloc(4096);
asm volatile("msr sp_el0,%0" :: "r"(sp));
```
- issue eret to return to the user code.
> Overall code

#### Task3: Set the vector table and implement the exception handler.
We need to have a file execute in EL0(task2), and trigger exception.
We are going to do
1. make a new file for exception
2. execute the file(code in task2, in EL0)
3. set the vector table to implement the exception handler(in EL1)
---
1. Make a new file for exception
- one.S -> one.o -> one.img
```asm=
.section ".text"
.global _start
_start:
svc 0x1337
1:
nop
b 1b
```
3. Set the vector table to implement the exception handler(in EL1)
```asm=
set_exception_vector_table:
adr x0, exception_vector_table
msr vbar_el1, x0
```
> VBAR_EL1, Vector Base Address Register (EL1)
> The VBAR_EL1 Purpose: Holds the vector base address for any exception that is taken to EL1.
```asm=
exception_handler:
bl except_handler_c
.align 11 // vector table should be aligned to 0x800
.global exception_vector_table
exception_vector_table:
b exception_handler // branch to a handler function.
.align 7 // entry size is 0x80, .align will pad 0
b exception_handler
.align 7
b exception_handler
.align 7
b exception_handler
.align 7
b exception_handler
.align 7
b exception_handler
.align 7
b exception_handler
.align 7
b exception_handler
.align 7
b exception_handler
.align 7
b exception_handler
.align 7
b exception_handler
.align 7
b exception_handler
.align 7
b exception_handler
.align 7
b exception_handler
.align 7
b exception_handler
.align 7
b exception_handler
.align 7
```
4. Do the except handle
```cpp=
void except_handler_c() {
uart_send_string("In Exception handle\n");
//read spsr_el1
unsigned long long spsr_el1 = 0;
asm volatile("mrs %0, spsr_el1":"=r"(spsr_el1));
uart_send_string("spsr_el1: ");
uart_hex(spsr_el1);
uart_send_string("\n");
//read elr_el1
unsigned long long elr_el1 = 0;
asm volatile("mrs %0, elr_el1":"=r"(elr_el1));
uart_send_string("elr_el1: ");
uart_hex(elr_el1);
uart_send_string("\n");
//esr_el1
unsigned long long esr_el1 = 0;
asm volatile("mrs %0, esr_el1":"=r"(esr_el1));
uart_hex(esr_el1);
uart_send_string("\n");
//ec
unsigned ec = (esr_el1 >> 26) & 0x3F; //0x3F = 0b111111(6)
uart_send_string("ec: ");
uart_hex(ec);
uart_send_string("\n");
while(1){
}
}
```



Because we use `svc` to trigger exception, we will get ec=0x15(0b010101)
#### Task4: Context Saving
We may find that the above user program behaves unexpectedly. That’s because the user program and the exception handler share the same general purpose registers bank. We need to save them before entering the kernel’s function. Otherwise, it may be corrupted.
```asm=
// save general registers to stack
.macro save_all
sub sp, sp, 32 * 8
stp x0, x1, [sp ,16 * 0]
stp x2, x3, [sp ,16 * 1]
stp x4, x5, [sp ,16 * 2]
stp x6, x7, [sp ,16 * 3]
stp x8, x9, [sp ,16 * 4]
stp x10, x11, [sp ,16 * 5]
stp x12, x13, [sp ,16 * 6]
stp x14, x15, [sp ,16 * 7]
stp x16, x17, [sp ,16 * 8]
stp x18, x19, [sp ,16 * 9]
stp x20, x21, [sp ,16 * 10]
stp x22, x23, [sp ,16 * 11]
stp x24, x25, [sp ,16 * 12]
stp x26, x27, [sp ,16 * 13]
stp x28, x29, [sp ,16 * 14]
str x30, [sp, 16 * 15]
.endm
// load general registers from stack
.macro load_all
ldp x0, x1, [sp ,16 * 0]
ldp x2, x3, [sp ,16 * 1]
ldp x4, x5, [sp ,16 * 2]
ldp x6, x7, [sp ,16 * 3]
ldp x8, x9, [sp ,16 * 4]
ldp x10, x11, [sp ,16 * 5]
ldp x12, x13, [sp ,16 * 6]
ldp x14, x15, [sp ,16 * 7]
ldp x16, x17, [sp ,16 * 8]
ldp x18, x19, [sp ,16 * 9]
ldp x20, x21, [sp ,16 * 10]
ldp x22, x23, [sp ,16 * 11]
ldp x24, x25, [sp ,16 * 12]
ldp x26, x27, [sp ,16 * 13]
ldp x28, x29, [sp ,16 * 14]
ldr x30, [sp, 16 * 15]
add sp, sp, 32 * 8
.endm
exception_handler:
save_all
bl exception_entry
load_all
eret
```
Screenshot of the result

## Basic Exercise 2 - Interrupt - 10%
> Enable the core timer’s interrupt. The interrupt handler should print the seconds after booting and set the next timeout to 2 seconds later.
#### Task1: IRQ interrupt unmask
> start.S
```asm=
from_el2_to_el1:
mov x0,#(1<<31)
msr hcr_el2,x0
> mov x0,#0x345 // 0x3c5 = 0b11'1'1000101-> 0x345 = 0b11'0'1000101
msr spsr_el2,x0
msr elr_el2,lr
eret
```

#### Task2: Enable the core timer’s interrupt.
#define CORE0_TIMER_IRQ_CTRL 0x40000040
```asm=
core_timer_enable:
mov x0, 1
msr cntp_ctl_el0, x0 // enable timer
mrs x0, cntfrq_el0
msr cntp_tval_el0, x0 // set expired time
mov x0, 2
ldr x1, =CORE0_TIMER_IRQ_CTRL
str w0, [x1] // unmask timer interrupt
```


> - cntpct_el0: The timer’s current count.
> - cntp_cval_el0: A compared timer count. If cntpct_el0 >= cntp_cval_el0, interrupt the CPU core.
> - cntp_tval_el0: (cntp_cval_el0 - cntpct_el0). We can use it to set an expired timer after the current timer count.
#### Task3: handle interrupt

```asm=
irq_exception_handler:
save_all
bl irq_except_handler_c
load_all
eret:
.align 11 // vector table should be aligned to 0x800
.global exception_vector_table
exception_vector_table:
b exception_handler // branch to a handler function.
.align 7 // entry size is 0x80, .align will pad 0
b exception_handler
.align 7
b exception_handler
.align 7
b exception_handler
.align 7
b exception_handler
.align 7
b irq_exception_handler
.align 7
b exception_handler
.align 7
b exception_handler
.align 7
b exception_handler
.align 7
b irq_exception_handler
.align 7
b exception_handler
.align 7
b exception_handler
.align 7
b exception_handler
.align 7
b exception_handler
.align 7
b exception_handler
.align 7
b exception_handler
.align 7
```
```cpp=
void irq_except_handler_c() {
uart_send_string("In timer interruption\n");
unsigned long long cntpct_el0 = 0;//The register count secs with frequency
asm volatile("mrs %0,cntpct_el0":"=r"(cntpct_el0));
unsigned long long cntfrq_el0 = 0;//The base frequency
asm volatile("mrs %0,cntfrq_el0":"=r"(cntfrq_el0));
unsigned long long sec = cntpct_el0 / cntfrq_el0;
uart_send_string("sec:");
uart_hex(sec);
uart_send_string("\n");
unsigned long long wait = cntfrq_el0 * 2;// wait 2 seconds
asm volatile ("msr cntp_tval_el0, %0"::"r"(wait));//set new timer
// fulfill the requirement set the next timeout to 2 seconds later.
}
```
## Basic Exercise 3 - Rpi3’s Peripheral Interrupt - 30%
#### Task1: Enable mini UART’s Interrupt.
To enable mini UART’s interrupt, you need to
1. set AUX_MU_IER_REG(0x3f215044) and
2. the second level interrupt controller’s Enable IRQs1(0x3f00b210)’s bit29.
```cpp=
#define RX_INTERRUPT_BIT 0x01
#define TX_INTERRUPT_BIT 0x02
#define AUXINIT_BIT_POSTION 1<<29
void uart_enable_interrupt() {
// Enable RX and TX interrupt for mini UART
uint32_t ier = mmio_read(AUX_MU_IER);
//unmask only Receive interrupt
ier |= RX_INTERRUPT_BIT;
// unmask both receive and transmit interrupt
//ier |= (RX_INTERRUPT_BIT | TX_INTERRUPT_BIT);
mmio_write(AUX_MU_IER, ier);
// Enable the mini UART interrupt in the second-level interrupt controller
uint32_t enable_irqs1 = (uint32_t) ENABLE_IRQS_1;// ENABLE_IRQS_1 defined in irq.h
enable_irqs1 |= AUXINIT_BIT_POSTION; // Set bit29
mmio_write(ENABLE_IRQS_1, enable_irqs1);
}
```
[spec/p.12](https://cs140e.sergio.bz/docs/BCM2837-ARM-Peripherals.pdf)

[spec/p.112](https://cs140e.sergio.bz/docs/BCM2837-ARM-Peripherals.pdf)

IRQs_1 take charge of 0-31 bits --> we need to enable this
IRQs_2 take charge of 32-63 bits

#### Task2: Determine the Interrupt Source
#### Task3: Asynchronous Read and Write
Without waiting until printing, we use a read buffer and write buffer to store the content.
```cpp=
#define UART_BUFFER_SIZE 1024
extern char uart_read_buffer[UART_BUFFER_SIZE];
extern char uart_write_buffer[UART_BUFFER_SIZE];
extern int uart_read_index;
extern int uart_read_head;
extern int uart_write_index;
extern int uart_write_head;
```
```cpp=
void uart_irq_handler(){
uint32_t iir = mmio_read(AUX_MU_IIR);
---> in interrupt <---
// Check if it is a receive interrupt
if ((iir & 0x06) == 0x04) {
---> in receive interrupt <---
// Read data(8 bytes) and store it in the read buffer
char data = mmio_read(AUX_MU_IO) & 0xff;
uart_read_buffer[uart_read_index++] = data;
if (uart_read_index >= UART_BUFFER_SIZE) {
uart_read_index = 0;
}
// Enqueue the received data into the write buffer
uart_write_buffer[uart_write_index++] = data;
if (uart_write_index >= UART_BUFFER_SIZE) {
uart_write_index = 0;
}
// Enable tx interrupt
---> enable transmit interrupt <---
mmio_write(AUX_MU_IER, mmio_read(AUX_MU_IER) | 0x2);
}
// Check if it is a transmit interrupt
if ((iir & 0x06) == 0x02) {
---> in transmit interrupt <---
// Send data from the write buffer
if (uart_write_head != uart_write_index) {
mmio_write(AUX_MU_IO, uart_write_buffer[uart_write_head++]);
if (uart_write_index >= UART_BUFFER_SIZE) {
uart_write_index = 0;
}
} else {
// Disable tx interrupt when there is no data left to send
---> disable transmit interrupt <---
mmio_write(AUX_MU_IER, mmio_read(AUX_MU_IER) & ~0x2);
if(uart_read_buffer[uart_read_index-1] == '\r'){
uart_read_buffer[uart_read_index-1] = '\0';
parse_command(uart_read_buffer);
uart_read_index = 0;
uart_write_index = 0;
uart_write_head = 0;
}
}
}
}
}
```
See the result below
(the situation is based on uart_enable both receive and transmit interrupt in the beginning.)
--> that's why we will start going into transmit interrupt when we run the code without input any character.

#### Task4: (Optional)Aysnc uart send/write
Replace `uart_get_char` with `uart_async_read`
Replace `uart_send_string` with `uart_async_send`
Replace `uart_send_char` with `uart_async_write`
```cpp=
int uart_async_read(char *buffer) {
if (uart_read_head == uart_read_index) {
// no characters available
return 0;
} else {
buffer[0] = uart_read_buffer[uart_read_head++];
if (uart_read_head >= uart_buffer_size) {
uart_read_head = 0;
}
return 1;
}
}
void uart_async_write(const char *buffer, int length) {
for (int i = 0; i < length; i++) {
uart_write_buffer[uart_write_head++] = buffer[i];
if (uart_write_head >= uart_buffer_size) {
uart_write_head = 0;
}
}
// trigger tx interrupt
mmio_write(aux_mu_ier, mmio_read(aux_mu_ier) | 0x2);
}
void uart_async_send(const char *str) {
int length = utils_strlen(str);
uart_async_write(str, length);
}
```
## Advanced Exercise 1 - Timer Multiplexing - 20%
We want to set diffenent timer, so we implement a double linked list to store each timer. When the timer ticks, it lead to timer interrupt and we handle the interrupt with interrupt handler.
In `timer.h`
```cpp=
#include <stdint.h>
#include <stddef.h>
typedef void (*timer_callback)(void *data);
typedef struct timer {
struct timer *prev; // previous timer in the list
struct timer *next; // next timer in the list
timer_callback callback; // the function to call when the timer expires
void *data; // data to be passed to the callback
uint64_t expiry; // the time at which the timer will expire
} timer_t;
extern timer_t *timer_head; // head of the timer list
void setTimeout(char *message,uint64_t seconds);
```
in `timer.c`
Goal: we want to have a `settimeout` API
settimeout -> create timer -> add timer(to queue)
Result: when the time's up we print the message, so the callback function is an easy print function.
```cpp=
void print_message(void *data) {
char *message = data;
uint64_t current_time, cntfrq;
asm volatile("mrs %0, cntpct_el0" : "=r"(current_time));
asm volatile("mrs %0, cntfrq_el0" : "=r"(cntfrq));
uint64_t seconds = current_time / cntfrq;
uart_send_string("Timeout message: ");
uart_send_string(message);
uart_send_string(" occurs at ");
uart_hex(seconds);
uart_send_string("\n");
}
```
settimeout
```cpp=
void setTimeout(char *message,uint64_t seconds) {
//strdup implement in utils.c
char *message_copy = utils_strdup(message);
if(!message_copy){
return;
}
if (!timer_head) {
//enable core_timer_interrupt
unsigned int value = 2;
unsigned int* address = (unsigned int*) CORE0_TIMER_IRQ_CTRL;
*address = value;
}
create_timer(print_message,message_copy,seconds);
}
```
create_timer
```cpp=
void create_timer(timer_callback callback, void* data, uint64_t after) {
//Allocate memory for the timer
timer_t* timer = simple_malloc(sizeof(timer_t));
if(!timer) {
return;
}
//Set the callback and data
timer->callback = callback;
timer->data = data;
//Calculate the expiry time
uint64_t current_time, cntfrq;
asm volatile("mrs %0, cntpct_el0":"=r"(current_time));
asm volatile("mrs %0, cntfrq_el0":"=r"(cntfrq));
timer->expiry = current_time + after * cntfrq;
//Add the time to the list
add_timer(timer);
}
```
add_timer
```cpp=
void add_timer(timer_t *new_timer) {
timer_t *current = timer_head;
// Disable interrupts to protect the critical section
asm volatile("msr DAIFSet, 0xf");
// Special case: the list is empty or the new timer is the earliest
if (!timer_head || new_timer->expiry < timer_head->expiry) {
new_timer->next = timer_head;
new_timer->prev = NULL;
if (timer_head) {
timer_head->prev = new_timer;
}
timer_head = new_timer;
// Reprogram the hardware timer
asm volatile ("msr cntp_cval_el0, %0"::"r"(new_timer->expiry));
asm volatile("msr cntp_ctl_el0,%0"::"r"(1));
// Enable interrupts
asm volatile("msr DAIFClr, 0xf");
return;
}
// Find the correct position in the list
while (current->next && current->next->expiry < new_timer->expiry) {
current = current->next;
}
// Insert the new timer
new_timer->next = current->next;
new_timer->prev = current;
if (current->next) {
current->next->prev = new_timer;
}
current->next = new_timer;
// Enable interrupts
asm volatile("msr DAIFClr, 0xf");
}
```
After setting the timeout, when it occurs time out interrupt, we have a timeout interrupt handler to handle the interruption.
```cpp=
#define CNTPSIRQ_BIT_POSITION 0x02
void irq_except_handler_c() {
asm volatile("msr DAIFSet, 0xf"); // Disable interrupts
uint32_t irq_pending1 = mmio_read(IRQ_PENDING_1);
uint32_t core0_interrupt_source = mmio_read(CORE0_INTERRUPT_SOURCE);
uint32_t iir = mmio_read(AUX_MU_IIR);
if (core0_interrupt_source & CNTPSIRQ_BIT_POSITION) {
//disable core 0 timer
unsigned int* address = (unsigned int*) CORE0_TIMER_IRQ_CTRL;
*address = 0;
timer_irq_handler();
}
```
[spec/P.13](https://datasheets.raspberrypi.com/bcm2836/bcm2836-peripherals.pdf)
- CNTPSIRQ: This is the "Counter Physical Secure IRQ" status. This would be used if you are operating in a secure mode and want to handle a timer interrupt.
- CNTPNSIRQ: This is the "Counter Physical Non-Secure IRQ" status. This would be used if you are operating in a non-secure mode and want to handle a timer interrupt.
- CNTHPIRQ: This is the "Counter Hypervisor IRQ" status. This would be used if you are operating in hypervisor mode and want to handle a timer interrupt.
We choose to check the CNTPNSIRQ register likely because our system is operating in non-secure mode.
> How to check if I am in secure mode or not?
>The CurrentEL register holds the current Exception level (EL) in bits [3:2]. If the system is in secure state, then the current EL would be EL3 or EL1 (there's no EL2 in secure state). If the system is in non-secure state, then the current EL could be EL2, EL1, or EL0.
and we are now in el1.

in `timer_irq_handler`
```cpp=
void timer_irq_handler() {
//enable core_0_timer
unsigned int* address = (unsigned int*) CORE0_TIMER_IRQ_CTRL;
*address = 2;
asm volatile("msr cntp_ctl_el0,%0"::"r"(0));
// Disable interrupts to protect critical section
asm volatile("msr DAIFSet, 0xf");
uint64_t current_time;
asm volatile("mrs %0, cntpct_el0":"=r"(current_time));
while(timer_head && timer_head->expiry <= current_time) {
timer_t *timer = timer_head;
//Execute the callback
timer->callback(timer->data);
// Remove timer from the list
timer_head = timer->next;
if (timer_head) {
timer_head->prev = NULL;
}
//free timer
// Reprogram the hardware timer if there are still timers left
if(timer_head) {
asm volatile("msr cntp_cval_el0, %0"::"r"(timer_head->expiry));
asm volatile("msr cntp_ctl_el0,%0"::"r"(1));
} else {
//no timer then mask timer interrupt
asm volatile("msr cntp_ctl_el0,%0"::"r"(0));
}
//enable interrupt
asm volatile("msr DAIFClr,0xf");
}
}
```
## Advanced Exercise 2 - Concurrent I/O Devices Handling 20%
We now put `uart_receive_interrupt`, `uart_transmit_interrupt`, and `timer_interrupt` as tasks. We use double linked list to store each tasks and get priority to each task. Then execute the task with priority.
in `tasklist.h`
```cpp=
#include <stddef.h>
#include <stdint.h>
typedef void (*task_callback)();
typedef struct task {
struct task *prev;
struct task *next;
task_callback callback;
uint64_t priority;
} task_t;
void execute_tasks();
void create_task(task_callback callback,uint64_t priority);
void enqueue_task(task_t *new_task);
extern task_t *task_head;
```
in `tasklist.c`
create_task -> enqueue_task -> execute_task
create the task list with callback and prority, where callback is each interrupt_handler
```cpp=
void create_task(task_callback callback, uint64_t priority) {
task_t* task = simple_malloc(sizeof(task_t));
if(!task) {
return;
}
task->callback = callback;
task->priority = priority;
enqueue_task(task);
}
```
```cpp=
void enqueue_task(task_t *new_task) {
// Disable interrupts to protect the critical section
asm volatile("msr DAIFSet, 0xf");
// Special case: the list is empty or the new task has higher priority
if (!task_head || new_task->priority < task_head->priority) {
new_task->next = task_head;
new_task->prev = NULL;
if (task_head) {
task_head->prev = new_task;
}
task_head = new_task;
} else {
// Find the correct position in the list
task_t *current = task_head;
while (current->next && current->next->priority <= new_task->priority) {
current = current->next;
}
// Insert the new task
new_task->next = current->next;
new_task->prev = current;
if (current->next) {
current->next->prev = new_task;
}
current->next = new_task;
}
// Enable interrupts
asm volatile("msr DAIFClr, 0xf");
}
```
```cpp=
void execute_tasks() {
while (task_head) {
task_head->callback();
task_head = task_head->next;
if (task_head) {
task_head->prev = NULL;
}
asm volatile("msr DAIFSet, 0xf"); // Disable interrupts
//simple_free(task);
}
asm volatile("msr DAIFClr, 0xf"); // Enable interrupts
}
```
Back to interrupt handler
```cpp=
void irq_except_handler_c() {
asm volatile("msr DAIFSet, 0xf"); // Disable interrupts
uint32_t irq_pending1 = mmio_read(IRQ_PENDING_1);
uint32_t core0_interrupt_source = mmio_read(CORE0_INTERRUPT_SOURCE);
uint32_t iir = mmio_read(AUX_MU_IIR);
if (core0_interrupt_source & CNTPSIRQ_BIT_POSITION) {
//djsable core 0 timer
unsigned int* address = (unsigned int*) CORE0_TIMER_IRQ_CTRL;
*address = 0;
create_task(timer_irq_handler,3);
}
// Handle UART interrupt
if (irq_pending1 & AUXINIT_BIT_POSTION) {
if ((iir & 0x06) == 0x04) {
//Disable receive interrupt
mmio_write(AUX_MU_IER, mmio_read(AUX_MU_IER) & ~(0x01));
create_task(uart_receive_handler,1);
}
if ((iir & 0x06) == 0x02) {
//Disable transmit interrupt
//mmio_write(AUX_MU_IER, mmio_read(AUX_MU_IER) & ~(0x02));
//create_task(uart_transmit_handler,2);
}
}
asm volatile("msr DAIFClr, 0xf"); // Enable interrupts
execute_tasks();
}
```
# Other Labs
[Operating System Capstone - Lab0](https://hackmd.io/@OJo2ruXGShKdpuewtwzZcQ/S104l7ZS3)
[Operating System Capstone - Lab1](https://hackmd.io/@OJo2ruXGShKdpuewtwzZcQ/S104l7ZS3)
[Operating System Capstone - Lab2](https://hackmd.io/@OJo2ruXGShKdpuewtwzZcQ/Hy6j7lzrn)
[Operating System Capstone - Lab3](https://hackmd.io/@OJo2ruXGShKdpuewtwzZcQ/r1WP_BrX3)
[Operating System Capstone - Lab4](https://hackmd.io/@OJo2ruXGShKdpuewtwzZcQ/SJYrXgY93)