diff --git a/arm11/Makefile b/arm11/Makefile
index ab9d824..c2ae063 100644
--- a/arm11/Makefile
+++ b/arm11/Makefile
@@ -18,11 +18,11 @@ include $(DEVKITARM)/base_rules
#---------------------------------------------------------------------------------
#TARGET := $(notdir $(CURDIR))
BUILD := build
-SOURCES := ../source ../source/hardware ../source/arm11 \
+SOURCES := ../kernel/source ../source ../source/hardware ../source/arm11 \
../source/arm11/allocator ../source/arm11/hardware \
../source/arm11/util/rbtree
DATA :=
-INCLUDES := ../include
+INCLUDES := ../kernel/include ../include
DEFINES := -DARM11 -D_3DS -DVERS_STRING=\"$(VERS_STRING)\" \
-DVERS_MAJOR=$(VERS_MAJOR) -DVERS_MINOR=$(VERS_MINOR)
ASSETS :=
diff --git a/include/arm11/hardware/lcd.h b/include/arm11/hardware/lcd.h
index b77d2cf..b78c07d 100644
--- a/include/arm11/hardware/lcd.h
+++ b/include/arm11/hardware/lcd.h
@@ -67,6 +67,7 @@
#define PDC_CNT_I_MASK_H (1u<<8) // Disables H(Blank?) IRQs.
#define PDC_CNT_I_MASK_V (1u<<9) // Disables VBlank IRQs.
#define PDC_CNT_I_MASK_ERR (1u<<10) // Disables error IRQs. What kind of errors?
+#define PDC_CNT_I_MASK_ALL (PDC_CNT_I_MASK_ERR | PDC_CNT_I_MASK_V | PDC_CNT_I_MASK_H)
#define PDC_CNT_OUT_E (1u<<16) // Output enable?
// REG_LCD_PDC_SWAP
diff --git a/include/arm11/hardware/lgyfb.h b/include/arm11/hardware/lgyfb.h
index e9a7f92..039b029 100644
--- a/include/arm11/hardware/lgyfb.h
+++ b/include/arm11/hardware/lgyfb.h
@@ -32,7 +32,6 @@
void LGYFB_init(void);
-void LGYFB_processFrame(void);
void LGYFB_deinit(void);
#ifndef NDEBUG
diff --git a/kernel/include/event.h b/kernel/include/event.h
new file mode 100644
index 0000000..f338fa4
--- /dev/null
+++ b/kernel/include/event.h
@@ -0,0 +1,87 @@
+#pragma once
+
+/*
+ * This file is part of fastboot 3DS
+ * Copyright (C) 2017 derrek, profi200
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include
+#include "kernel.h"
+
+
+typedef void* KEvent;
+
+
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+/**
+ * @brief Creates a new KEvent.
+ *
+ * @param[in] oneShot Event fires only once and auto clears if true.
+ *
+ * @return The KEvent handle or NULL when out of memory.
+ */
+KEvent createEvent(bool oneShot);
+
+/**
+ * @brief Deletes a KEvent handle.
+ *
+ * @param[in] kevent The KEvent handle.
+ */
+void deleteEvent(const KEvent kevent);
+
+/**
+ * @brief Binds the given KEvent to an interrupt.
+ *
+ * @param[in] kevent The KEvent handle.
+ * @param[in] id The interrupt id.
+ * @param[in] prio The interrupt priority.
+ */
+void bindInterruptToEvent(const KEvent kevent, uint8_t id, uint8_t prio);
+
+void unbindInterruptEvent(uint8_t id);
+
+/**
+ * @brief Waits for the given KEvent to be signaled.
+ *
+ * @param[in] kevent The KEvent handle.
+ *
+ * @return Returns the result. See Kres above.
+ */
+KRes waitForEvent(const KEvent kevent);
+
+/**
+ * @brief Signals a KEvent.
+ *
+ * @param[in] kevent The KEvent handle.
+ * @param[in] reschedule Set to true to immediately reschedule.
+ */
+void signalEvent(const KEvent kevent, bool reschedule);
+
+/**
+ * @brief Clears a KEvent.
+ *
+ * @param[in] kevent The KEvent handle.
+ */
+void clearEvent(const KEvent kevent);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
diff --git a/kernel/include/internal/config.h b/kernel/include/internal/config.h
new file mode 100644
index 0000000..7de6cbf
--- /dev/null
+++ b/kernel/include/internal/config.h
@@ -0,0 +1,40 @@
+#pragma once
+
+/*
+ * This file is part of fastboot 3DS
+ * Copyright (C) 2017 derrek, profi200
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+// MAX_PRIO_BITS The number of available priorities. Minimum 3. Maximum 32.
+#define MAX_PRIO_BITS (4)
+
+/*
+ * Maximum number of objects we can create (Slabheap).
+*/
+#define MAX_TASKS (3) // Including main and idle task.
+#define MAX_EVENTS (10)
+#define MAX_MUTEXES (3)
+#define MAX_SEMAPHORES (0)
+#define MAX_TIMERS (0)
+
+#define IDLE_STACK_SIZE (0x1000) // Keep in mind this stack is used in interrupt contex! TODO: Change this.
+
+
+
+// TODO: More checks. For example slabheap.
+#if (MAX_PRIO_BITS < 3 || MAX_PRIO_BITS > 32)
+ #error "Invalid number of maximum task priorities!"
+#endif
diff --git a/kernel/include/internal/contextswitch.h b/kernel/include/internal/contextswitch.h
new file mode 100644
index 0000000..355d3fc
--- /dev/null
+++ b/kernel/include/internal/contextswitch.h
@@ -0,0 +1,49 @@
+#pragma once
+
+/*
+ * This file is part of fastboot 3DS
+ * Copyright (C) 2017 derrek, profi200
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "types.h"
+#include "kernel.h"
+
+
+typedef struct
+{
+ u32 r4;
+ u32 r5;
+ u32 r6;
+ u32 r7;
+ u32 r8;
+ u32 r9;
+ u32 r10;
+ u32 r11;
+ u32 lr; // pc
+} cpuRegs;
+
+
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+KRes switchContext(KRes res, void *oldSp, uintptr_t newSp);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/kernel/include/internal/kernel_private.h b/kernel/include/internal/kernel_private.h
new file mode 100644
index 0000000..07b7de4
--- /dev/null
+++ b/kernel/include/internal/kernel_private.h
@@ -0,0 +1,59 @@
+#pragma once
+
+#include
+#include
+#include
+#include "types.h"
+#include "internal/list.h"
+#include "kernel.h"
+#include "arm.h"
+
+
+typedef enum
+{
+ TASK_STATE_DEAD = 0,
+ //TASK_STATE_READY = 1,
+ TASK_STATE_RUNNING = 1,
+ TASK_STATE_BLOCKED = 2,
+ TASK_STATE_RUNNING_SHORT = 3 // Continue task as soon as the woken ones are finished.
+} TaskState;
+
+typedef struct
+{
+ ListNode node;
+ u8 core; // TODO: Multicore
+ u8 prio;
+ u8 id;
+ KRes res; // Last error code. Also abused for taskArg.
+ uintptr_t savedSp;
+ void *stack;
+ // Name?
+ // Exit code?
+} TaskCb; // Task context
+static_assert(offsetof(TaskCb, node) == 0, "Error: Member node of TaskCb is not at offset 0!");
+
+
+
+const TaskCb* getCurrentTask(void);
+KRes waitQueueBlock(ListNode *waitQueue);
+bool waitQueueWakeN(ListNode *waitQueue, u32 wakeCount, KRes res, bool reschedule);
+
+
+static inline void kernelLock(void)
+{
+ __cpsid(i);
+ //spinlockLock(&g_lock);
+}
+static inline void kernelUnlock(void)
+{
+ __cpsie(i);
+ //spinlockUnlock(&g_lock);
+}
+
+
+// These functions belong in other headers however we
+// don't want to make them accessible in the public API.
+void _eventSlabInit(void);
+void _mutexSlabInit(void);
+void _semaphoreSlabInit(void);
+void _timerInit(void);
diff --git a/kernel/include/internal/kmemcpy_set.h b/kernel/include/internal/kmemcpy_set.h
new file mode 100644
index 0000000..b40e0fb
--- /dev/null
+++ b/kernel/include/internal/kmemcpy_set.h
@@ -0,0 +1,35 @@
+#pragma once
+
+/*
+ * This file is part of fastboot 3DS
+ * Copyright (C) 2019 Aurora Wright, TuxSH, derrek, profi200
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+// Based on https://github.com/AuroraWright/Luma3DS/blob/master/arm9/source/alignedseqmemcpy.s
+
+#include "types.h"
+
+
+
+void kmemcpy(u32 *restrict dst, const u32 *restrict src, u32 size);
+
+// Alias of kmemcpy() with volatile arguments.
+void iokmemcpy(vu32 *restrict dst, const vu32 *restrict src, u32 size);
+
+void kmemset(u32 *ptr, u32 value, u32 size);
+
+// Alias of kmemset() with volatile arguments.
+void iokmemset(vu32 *ptr, u32 value, u32 size);
diff --git a/kernel/include/internal/list.h b/kernel/include/internal/list.h
new file mode 100644
index 0000000..92c9743
--- /dev/null
+++ b/kernel/include/internal/list.h
@@ -0,0 +1,103 @@
+#pragma once
+
+// Based on https://github.com/torvalds/linux/blob/master/include/linux/list.h
+
+#include
+#include
+#include
+
+
+#define LIST_INIT_VAL(name) ((ListNode){&(name), &(name)})
+
+#define LIST_ENTRY(ptr, type, member) \
+({ \
+ void *__mptr = (void*)(ptr); \
+ (type*)(__mptr - (size_t)&((type*)0)->member); \
+})
+
+#define LIST_FIRST_ENTRY(ptr, type, member) \
+ LIST_ENTRY((ptr)->next, type, member)
+
+#define LIST_NEXT_ENTRY(pos, member) \
+ LIST_ENTRY((pos)->member.next, typeof(*(pos)), member)
+
+#define LIST_FOR_EACH_ENTRY(pos, start, member) \
+ for(pos = LIST_FIRST_ENTRY(start, typeof(*pos), member); \
+ &pos->member != (start); \
+ pos = LIST_NEXT_ENTRY(pos, member))
+
+
+typedef struct ListNode ListNode;
+struct ListNode
+{
+ ListNode *next;
+ ListNode *prev;
+};
+//static_assert(offsetof(ListNode, next) == 0, "Error: Member next of ListNode is not at offset 0!");
+
+
+
+static inline void listInit(ListNode *start)
+{
+ *start = LIST_INIT_VAL(*start);
+}
+
+static inline bool listEmpty(const ListNode *start)
+{
+ return start->next == start;
+}
+
+// Internal function. Don't use unless you know what you are doing!
+static inline void _listAdd(ListNode *node, ListNode *next, ListNode *prev)
+{
+ node->next = next;
+ node->prev = prev;
+ next->prev = node;
+ prev->next = node;
+}
+
+static inline void listAddBefore(ListNode *entry, ListNode *node)
+{
+ _listAdd(node, entry, entry->prev);
+}
+
+static inline void listAddAfter(ListNode *entry, ListNode *node)
+{
+ _listAdd(node, entry->next, entry);
+}
+
+// Internal function. Don't use unless you know what you are doing!
+static inline void _listDelete(ListNode *next, ListNode *prev)
+{
+ next->prev = prev;
+ prev->next = next;
+}
+
+static inline void listDelete(ListNode *entry)
+{
+ _listDelete(entry->next, entry->prev);
+}
+
+static inline ListNode* listRemoveTail(ListNode *start)
+{
+ ListNode *const node = start->next;
+
+ listDelete(node);
+
+ return node;
+}
+
+static inline ListNode* listRemoveHead(ListNode *start)
+{
+ ListNode *const node = start->prev;
+
+ listDelete(node);
+
+ return node;
+}
+
+// Some function aliases for queues.
+#define listPush(start, node) listAddBefore((start), (node))
+#define listPop(start) listRemoveTail((start))
+#define listPushTail(start, node) listAddAfter((start), (node))
+#define listPopHead(start) listRemoveHead((start))
diff --git a/kernel/include/internal/slabheap.h b/kernel/include/internal/slabheap.h
new file mode 100644
index 0000000..091ec7b
--- /dev/null
+++ b/kernel/include/internal/slabheap.h
@@ -0,0 +1,46 @@
+#pragma once
+
+
+#include
+#include "internal/list.h"
+
+
+typedef ListNode SlabHeap;
+
+
+
+/**
+ * @brief Initializes the slabheap.
+ *
+ * @param slab SlabHeap object pointer.
+ * @param[in] objSize The size of the object slots.
+ * @param[in] num The maximum number of object slots.
+ */
+void slabInit(SlabHeap *slab, size_t objSize, size_t num);
+
+/**
+ * @brief Allocates an object slot from the slabheap.
+ *
+ * @param slab SlabHeap object pointer.
+ *
+ * @return Returns a pointer to the object slot.
+ */
+void* slabAlloc(SlabHeap *slab);
+
+/**
+ * @brief Same as slabAlloc() but clears slots.
+ *
+ * @param slab SlabHeap object pointer.
+ * @param[in] clrSize The clear size (passed to memset()).
+ *
+ * @return Returns a pointer to the object slot.
+ */
+void* slabCalloc(SlabHeap *slab, size_t clrSize);
+
+/**
+ * @brief Deallocates an object slot.
+ *
+ * @param slab SlabHeap object pointer.
+ * @param ptr The object slot pointer.
+ */
+void slabFree(SlabHeap *slab, void *ptr);
diff --git a/kernel/include/internal/spinlock.h b/kernel/include/internal/spinlock.h
new file mode 100644
index 0000000..e430a27
--- /dev/null
+++ b/kernel/include/internal/spinlock.h
@@ -0,0 +1,45 @@
+#pragma once
+
+/*
+ * This file is part of fastboot 3DS
+ * Copyright (C) 2017 derrek, profi200
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "types.h"
+
+
+
+static inline void spinlockLock(u32 *lock)
+{
+ u32 tmp;
+ __asm__ volatile("1: ldrex %0, [%1]\n"
+ " teq %0, #0\n"
+ " wfene\n"
+ " strexeq %0, %2, [%1]\n"
+ " teqeq %0, #0\n"
+ " bne 1b\n"
+ " mcr p15, 0, %0, c7, c10, 5" // DMB
+ : "=&r" (tmp) : "r" (lock), "r" (1) : "cc", "memory");
+}
+
+static inline void spinlockUnlock(u32 *lock)
+{
+ __asm__ volatile("mcr p15, 0, %0, c7, c10, 5\n" // DMB
+ "str %0, [%1]\n"
+ "mcr p15, 0, %0, c7, c10, 4\n" // DSB
+ "sev"
+ : : "r" (0), "r" (lock) : "memory");
+}
diff --git a/kernel/include/internal/util.h b/kernel/include/internal/util.h
new file mode 100644
index 0000000..cc66a64
--- /dev/null
+++ b/kernel/include/internal/util.h
@@ -0,0 +1,5 @@
+#pragma once
+
+
+#define LIKELY(expr) __builtin_expect((expr), true)
+#define UNLIKELY(expr) __builtin_expect((expr), false)
diff --git a/kernel/include/kernel.h b/kernel/include/kernel.h
new file mode 100644
index 0000000..2405c37
--- /dev/null
+++ b/kernel/include/kernel.h
@@ -0,0 +1,78 @@
+#pragma once
+
+/*
+ * This file is part of fastboot 3DS
+ * Copyright (C) 2017 derrek, profi200
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include
+#include
+
+
+enum
+{
+ KRES_OK = 0, // No error.
+ KRES_INVALID_HANDLE = 1, // The handle or object doesn't exist.
+ KRES_HANDLE_DELETED = 2, // The handle has been deleted externally.
+ //KRES_WAIT_QUEUE_FULL = 3, // The wait queue is full. We can't block on it.
+ KRES_WOULD_BLOCK = 3, // The function would block. For non-blocking APIs.
+ KRES_NO_PERMISSIONS = 4 // You have no permissions. Example unlocking a mutex on a different task.
+};
+
+typedef uintptr_t KRes; // See createTask()
+typedef void* KTask;
+typedef void (*TaskFunc)(void*);
+
+
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+/**
+ * @brief Init the kernel. Only call this once.
+ *
+ * @param[in] priority The priority of the main task.
+ */
+void kernelInit(uint8_t priority);
+
+
+/**
+ * @brief Creates a new kernel task.
+ *
+ * @param[in] stackSize The stack size.
+ * @param[in] priority The priority.
+ * @param[in] entry The entry function.
+ * @param taskArg The task entry function argument.
+ *
+ * @return Returns a KTask handle.
+ */
+KTask createTask(size_t stackSize, uint8_t priority, TaskFunc entry, void *taskArg);
+
+/**
+ * @brief Switches to the next task. Use with care.
+ */
+void yieldTask(void);
+
+/**
+ * @brief Task exit function. Must be called from the task that exits.
+ */
+void taskExit(void);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
diff --git a/kernel/include/mutex.h b/kernel/include/mutex.h
new file mode 100644
index 0000000..24a8159
--- /dev/null
+++ b/kernel/include/mutex.h
@@ -0,0 +1,68 @@
+#pragma once
+
+/*
+ * This file is part of fastboot 3DS
+ * Copyright (C) 2017 derrek, profi200
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "kernel.h"
+
+
+typedef void* KMutex; // TODO: Implement this using semaphores?
+
+
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+/**
+ * @brief Creates a mutex.
+ *
+ * @return The KMutex handle or NULL when out of memory.
+ */
+KMutex createMutex(void);
+
+/**
+ * @brief Deletes a KMutex handle.
+ *
+ * @param[in] kmutex The KMutex handle.
+ */
+void deleteMutex(const KMutex kmutex);
+
+/**
+ * @brief Locks a KMutex.
+ *
+ * @param[in] kmutex The KMutex handle.
+ *
+ * @return Returns the result. See Kres.
+ */
+KRes lockMutex(const KMutex kmutex);
+
+/**
+ * @brief Unlocks a KMutex.
+ *
+ * @param[in] kmutex The KMutex handle.
+ *
+ * @return Returns KRES_NO_PERMISSIONS if the current task
+ * @return is not the locker. Otherwise KRES_OK.
+ */
+KRes unlockMutex(const KMutex kmutex);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
diff --git a/kernel/include/semaphore.h b/kernel/include/semaphore.h
new file mode 100644
index 0000000..47b5be4
--- /dev/null
+++ b/kernel/include/semaphore.h
@@ -0,0 +1,80 @@
+#pragma once
+
+/*
+ * This file is part of fastboot 3DS
+ * Copyright (C) 2017 derrek, profi200
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include
+#include
+#include "kernel.h"
+
+
+typedef void* KSema;
+
+
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+/**
+ * @brief Creates a KSema handle.
+ *
+ * @param[in] count The initial count of the semaphore.
+ *
+ * @return The KSema handle or NULL when out of memory.
+ */
+KSema createSemaphore(int32_t count);
+
+/**
+ * @brief Deletes a KSema handle.
+ *
+ * @param[in] ksema The KSema handle.
+ */
+void deleteSemaphore(const KSema ksema);
+
+/**
+ * @brief Polls a KSema.
+ *
+ * @param[in] ksema The KSema handle.
+ *
+ * @return Returns KRES_OK or KRES_WOULD_BLOCK.
+ */
+KRes pollSemaphore(const KSema ksema);
+
+/**
+ * @brief Decreases the semaphore and blocks if <=0.
+ *
+ * @param[in] ksema The KSema handle.
+ *
+ * @return Returns the result. See Kres above.
+ */
+KRes waitForSemaphore(const KSema ksema);
+
+/**
+ * @brief Increases the semaphore and wakes up signalCount waiting tasks if any.
+ *
+ * @param[in] ksema The KSema handle.
+ * @param[in] signalCount The number to increase the semaphore by.
+ * @param[in] reschedule Set to true to immediately reschedule.
+ */
+void signalSemaphore(const KSema ksema, uint32_t signalCount, bool reschedule);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
diff --git a/kernel/include/timer.h b/kernel/include/timer.h
new file mode 100644
index 0000000..6df230b
--- /dev/null
+++ b/kernel/include/timer.h
@@ -0,0 +1,45 @@
+#pragma once
+
+/*
+ * This file is part of fastboot 3DS
+ * Copyright (C) 2017 derrek, profi200
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "kernel.h"
+
+
+typedef void* KTimer;
+
+
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+KTimer createTimer(bool pulse);
+
+void deleteTimer(const KTimer ktimer);
+
+void startTimer(const KTimer ktimer, uint32_t usec);
+
+void stopTimer(const KTimer ktimer);
+
+KRes waitForTimer(const KTimer ktimer);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
diff --git a/kernel/source/contextswitch.s b/kernel/source/contextswitch.s
new file mode 100644
index 0000000..58beb13
--- /dev/null
+++ b/kernel/source/contextswitch.s
@@ -0,0 +1,67 @@
+/*
+ * This file is part of fastboot 3DS
+ * Copyright (C) 2017 derrek, profi200
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "asm_macros.h"
+
+.arm
+.cpu mpcore
+.fpu vfpv2
+
+
+
+@ void switchContextNoScratchRegs(u32 *curRegs, const u32 *newRegs)
+/*ASM_FUNC switchContextNoScratchRegs
+ stmia r0!, {r4-r11, sp, lr}
+ add r0, r0, #20 @ Skip r0-r3, r12
+ adr r2, switchContextNoScratchRegs_end
+ mrs r3, cpsr
+ stmia r0, {r2, r3}
+ ldmia r1!, {r4-r11, sp, lr}
+ add r1, r1, #20 @ Skip r0-r3, r12
+ rfeia r1
+switchContextNoScratchRegs_end:
+ cpsie i
+ bx lr
+
+@ void switchContextAllRegs(u32 *curRegs, const u32 *newRegs)
+ASM_FUNC switchContextAllRegs
+ stmia r0!, {r4-r11, sp, lr}
+ add r0, r0, #20 @ Skip r0-r3, r12
+ adr r2, switchContextAllRegs_end
+ mrs r3, cpsr
+ stmia r0, {r2, r3}
+ ldmia r1!, {r4-r11, sp, lr}
+ ldr r3, [r1, #24] @ cpsr
+ cps #19 @ SVC mode
+ msr spsr_fsxc, r3
+ ldmia r1, {r0-r3, r12, pc}^
+switchContextAllRegs_end:
+ cpsie i
+ bx lr*/
+
+@ KRes switchContext(KRes res, void *oldSp, uintptr_t newSp);
+BEGIN_ASM_FUNC switchContext
+ stmfd sp!, {r4-r11, lr}
+ str sp, [r1]
+ mov sp, r2
+ ldmfd sp!, {r4-r11, lr}
+ bx lr
+END_ASM_FUNC
+
+
+.pool
diff --git a/kernel/source/event.c b/kernel/source/event.c
new file mode 100644
index 0000000..1696243
--- /dev/null
+++ b/kernel/source/event.c
@@ -0,0 +1,119 @@
+#include
+#include
+#include "types.h"
+#include "event.h"
+#include "internal/list.h"
+#include "arm11/hardware/interrupt.h"
+#include "internal/kernel_private.h"
+#include "internal/slabheap.h"
+#include "internal/config.h"
+
+
+typedef struct
+{
+ bool signaled;
+ const bool oneShot;
+ ListNode waitQueue;
+} Event;
+
+
+static SlabHeap g_eventSlab = {0};
+static KEvent g_irqEventTable[128 - 32] = {0}; // 128 - 32 private interrupts.
+
+
+
+void _eventSlabInit(void)
+{
+ slabInit(&g_eventSlab, sizeof(Event), MAX_EVENTS);
+}
+
+static void eventIrqHandler(u32 intSource)
+{
+ signalEvent(g_irqEventTable[intSource - 32], false);
+}
+
+KEvent createEvent(bool oneShot)
+{
+ Event *const event = (Event*)slabAlloc(&g_eventSlab);
+
+ event->signaled = false;
+ *(bool*)&event->oneShot = oneShot;
+ listInit(&event->waitQueue);
+
+ return event;
+}
+
+void deleteEvent(const KEvent kevent)
+{
+ Event *const event = (Event*)kevent;
+
+ kernelLock();
+ waitQueueWakeN(&event->waitQueue, (u32)-1, KRES_HANDLE_DELETED, true);
+
+ slabFree(&g_eventSlab, event);
+}
+
+// TODO: Critical sections needed for bin/unbind?
+void bindInterruptToEvent(const KEvent kevent, uint8_t id, uint8_t prio)
+{
+ if(id < 32 || id > 127) return;
+
+ g_irqEventTable[id - 32] = kevent;
+ IRQ_registerIsr(id, prio, 0, eventIrqHandler);
+}
+
+void unbindInterruptEvent(uint8_t id)
+{
+ if(id < 32 || id > 127) return;
+
+ g_irqEventTable[id - 32] = NULL;
+ IRQ_unregisterIsr(id);
+}
+
+// TODO: Timeout.
+KRes waitForEvent(const KEvent kevent)
+{
+ Event *const event = (Event*)kevent;
+ KRes res;
+
+ kernelLock();
+ if(event->signaled)
+ {
+ if(event->oneShot) event->signaled = false;
+ kernelUnlock();
+ res = KRES_OK;
+ }
+ else res = waitQueueBlock(&event->waitQueue);
+
+ return res;
+}
+
+void signalEvent(const KEvent kevent, bool reschedule)
+{
+ Event *const event = (Event*)kevent;
+
+ kernelLock();
+ if(!event->signaled)
+ {
+ if(event->oneShot)
+ {
+ if(!waitQueueWakeN(&event->waitQueue, 1, KRES_OK, reschedule))
+ event->signaled = true;
+ }
+ else
+ {
+ event->signaled = true;
+ waitQueueWakeN(&event->waitQueue, (u32)-1, KRES_OK, reschedule);
+ }
+ }
+ else kernelUnlock();
+}
+
+void clearEvent(const KEvent kevent)
+{
+ Event *const event = (Event*)kevent;
+
+ kernelLock(); // TODO: Can we do this without locks?
+ event->signaled = false;
+ kernelUnlock();
+}
diff --git a/kernel/source/kernel.c b/kernel/source/kernel.c
new file mode 100644
index 0000000..e48bfb1
--- /dev/null
+++ b/kernel/source/kernel.c
@@ -0,0 +1,245 @@
+#include
+#include
+#include
+#include "types.h"
+#include "internal/config.h"
+#include "internal/kernel_private.h"
+#include "internal/kmemcpy_set.h"
+#include "internal/slabheap.h"
+#include "internal/util.h"
+#include "internal/list.h"
+#include "internal/contextswitch.h"
+#include "kernel.h"
+#include "event.h"
+#include "mutex.h"
+#include "semaphore.h"
+#include "timer.h"
+#include "arm.h"
+
+
+static TaskCb *g_curTask = NULL;
+static u32 g_readyBitmap = 0;
+static ListNode g_runQueues[MAX_PRIO_BITS] = {0};
+static SlabHeap g_taskSlab = {0};
+static u32 g_numTasks = 0;
+static TaskCb *g_curDeadTask = NULL; // TODO: Improve dead task handling.
+
+
+
+static KRes scheduler(TaskState curTaskState);
+noreturn static void kernelIdleTask(void);
+
+static void initKernelState(void)
+{
+ for(int i = 0; i < MAX_PRIO_BITS; i++) listInit(&g_runQueues[i]);
+ slabInit(&g_taskSlab, sizeof(TaskCb), MAX_TASKS);
+ _eventSlabInit();
+ _mutexSlabInit();
+ _semaphoreSlabInit();
+ //_timerInit();
+}
+
+/*
+ * Public kernel API.
+*/
+// TODO: Are KTask handles needed? (for the main task)
+// TODO: Thread local storage. Needed?
+void kernelInit(uint8_t priority)
+{
+ if(priority > MAX_PRIO_BITS - 1u) return;
+
+ // TODO: Split this mess into helper functions.
+ initKernelState();
+
+ TaskCb *const idleT = (TaskCb*)slabAlloc(&g_taskSlab);
+ void *const iStack = malloc(IDLE_STACK_SIZE);
+ TaskCb *const mainT = (TaskCb*)slabCalloc(&g_taskSlab, sizeof(TaskCb));
+ if(idleT == NULL || iStack == NULL || mainT == NULL)
+ {
+ slabFree(&g_taskSlab, idleT);
+ free(iStack);
+ slabFree(&g_taskSlab, mainT);
+ return;
+ }
+
+ cpuRegs *const regs = (cpuRegs*)(iStack + IDLE_STACK_SIZE - sizeof(cpuRegs));
+ regs->lr = (u32)kernelIdleTask;
+ idleT->prio = 1;
+ // id is already set to 0.
+ idleT->savedSp = (uintptr_t)regs;
+ idleT->stack = iStack;
+
+ // Main task already running. Nothing more to setup.
+ mainT->id = 1;
+ mainT->prio = priority;
+
+ g_curTask = mainT;
+ g_readyBitmap = 1u<<1; // The idle task has priority 1 and is always ready.
+ listPush(&g_runQueues[1], &idleT->node);
+ g_numTasks = 2;
+}
+
+KTask createTask(size_t stackSize, uint8_t priority, TaskFunc entry, void *taskArg)
+{
+ if(priority > MAX_PRIO_BITS - 1u) return NULL;
+
+ // Make sure the stack is aligned to 8 bytes
+ stackSize = (stackSize + 7u) & ~7u;
+
+ SlabHeap *const taskSlabPtr = &g_taskSlab;
+ TaskCb *const newT = (TaskCb*)slabAlloc(taskSlabPtr);
+ void *const stack = malloc(stackSize);
+ if(newT == NULL || stack == NULL)
+ {
+ slabFree(taskSlabPtr, newT);
+ free(stack);
+ return NULL;
+ }
+
+ cpuRegs *const regs = (cpuRegs*)(stack + stackSize - sizeof(cpuRegs));
+ kmemset((u32*)regs, 0, sizeof(cpuRegs));
+ regs->lr = (u32)entry;
+ newT->prio = priority;
+ newT->id = g_numTasks; // TODO: Make this more sophisticated.
+ // TODO: This is kinda hacky abusing the result member to pass the task arg.
+ // Pass args and stuff on the stack?
+ newT->res = (KRes)taskArg;
+ newT->savedSp = (uintptr_t)regs;
+ newT->stack = stack;
+
+ kernelLock();
+ listPush(&g_runQueues[priority], &newT->node);
+ g_readyBitmap |= 1u<node);
+ return scheduler(TASK_STATE_BLOCKED);
+}
+
+bool waitQueueWakeN(ListNode *waitQueue, u32 wakeCount, KRes res, bool reschedule)
+{
+ if(listEmpty(waitQueue) || !wakeCount)
+ {
+ kernelUnlock();
+ return false;
+ }
+
+ u32 readyBitmap = 0;
+ ListNode *const runQueues = g_runQueues;
+ if(LIKELY(reschedule))
+ {
+ // Put ourself on top of the list first so we run immediately
+ // after the woken tasks to finish the work we were doing.
+ // TODO: Verify if this is a good strategy.
+ TaskCb *const curTask = g_curTask;
+ const u8 curPrio = curTask->prio;
+ listPushTail(&runQueues[curPrio], &curTask->node);
+ readyBitmap = 1u<prio;
+ task->res = res;
+ listPushTail(&runQueues[task->prio], &task->node);
+ } while(!listEmpty(waitQueue) && --wakeCount);
+ g_readyBitmap |= readyBitmap;
+
+ if(LIKELY(reschedule)) scheduler(TASK_STATE_RUNNING_SHORT);
+ else kernelUnlock();
+
+ return true;
+}
+
+static KRes scheduler(TaskState curTaskState)
+{
+ TaskCb *const curDeadTask = g_curDeadTask;
+ // TODO: Get rid of this and find a better way.
+ if(UNLIKELY(curDeadTask != NULL))
+ {
+ free(curDeadTask->stack);
+ slabFree(&g_taskSlab, curDeadTask);
+ g_curDeadTask = NULL;
+ }
+
+ TaskCb *const curTask = g_curTask;
+ u32 readyBitmap = g_readyBitmap;
+ ListNode *const runQueues = g_runQueues;
+ // Warning. The result is undefined if the input of this builtin is 0!
+ // Edge case: All tasks are sleeping except the (curently running) idle task.
+ // g_readyBitmap is 0 in this case.
+ const unsigned int readyPrio = (readyBitmap ? 31u - __builtin_clz(readyBitmap) : 0u);
+ if(LIKELY(curTaskState == TASK_STATE_RUNNING))
+ {
+ const u8 curPrio = curTask->prio;
+
+ if(readyPrio < curPrio)
+ {
+ kernelUnlock();
+ return KRES_OK;
+ }
+
+ listPush(&runQueues[curPrio], &curTask->node);
+ readyBitmap |= 1u<res;
+ kernelUnlock();
+
+ return switchContext(res, &oldTask->savedSp, newTask->savedSp);
+}
+
+// TODO: Cleanup deleted tasks in here? Or create a worker task?
+noreturn static void kernelIdleTask(void)
+{
+ do
+ {
+ __wfi();
+ kernelLock();
+ scheduler(TASK_STATE_RUNNING);
+ } while(1);
+}
diff --git a/kernel/source/kmemcpy_set.s b/kernel/source/kmemcpy_set.s
new file mode 100644
index 0000000..be22522
--- /dev/null
+++ b/kernel/source/kmemcpy_set.s
@@ -0,0 +1,101 @@
+/*
+ * This file is part of fastboot 3DS
+ * Copyright (C) 2019 Aurora Wright, TuxSH, derrek, profi200
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+ @ Based on https://github.com/AuroraWright/Luma3DS/blob/master/arm9/source/alignedseqmemcpy.s
+
+.arm
+.cpu arm946e-s
+.fpu softvfp
+
+
+
+@ void kmemcpy(u32 *restrict dst, const u32 *restrict src, u32 size);
+@ void iokmemcpy(vu32 *restrict dst, const vu32 *restrict src, u32 size);
+.section .text.kmemcpy, "ax", %progbits
+.global kmemcpy
+.global iokmemcpy
+.type kmemcpy %function
+.type iokmemcpy %function
+.align 2
+kmemcpy:
+iokmemcpy:
+ bics r12, r2, #31
+ beq kmemcpy_test_words
+ stmfd sp!, {r4-r10}
+ kmemcpy_blocks_lp:
+ ldmia r1!, {r3-r10}
+ subs r12, #32
+ stmia r0!, {r3-r10}
+ bne kmemcpy_blocks_lp
+ ldmfd sp!, {r4-r10}
+kmemcpy_test_words:
+ ands r12, r2, #28
+ beq kmemcpy_halfword_byte
+ kmemcpy_words_lp:
+ ldr r3, [r1], #4
+ subs r12, #4
+ str r3, [r0], #4
+ bne kmemcpy_words_lp
+kmemcpy_halfword_byte:
+ tst r2, #2
+ ldrneh r3, [r1], #2
+ strneh r3, [r0], #2
+ tst r2, #1
+ ldrneb r3, [r1]
+ strneb r3, [r0]
+ bx lr
+
+
+@ void kmemset(u32 *ptr, u32 value, u32 size);
+@ void iokmemset(vu32 *ptr, u32 value, u32 size);
+.section .text.kmemset, "ax", %progbits
+.global kmemset
+.global iokmemset
+.type kmemset %function
+.type iokmemset %function
+.align 2
+kmemset:
+iokmemset:
+ bics r12, r2, #31
+ beq kmemset_test_words
+ stmfd sp!, {r4-r9}
+ mov r3, r1
+ mov r4, r1
+ mov r5, r1
+ mov r6, r1
+ mov r7, r1
+ mov r8, r1
+ mov r9, r1
+ kmemset_blocks_lp:
+ stmia r0!, {r1, r3-r9}
+ subs r12, #32
+ bne kmemset_blocks_lp
+ ldmfd sp!, {r4-r9}
+kmemset_test_words:
+ ands r12, r2, #28
+ beq kmemset_halfword_byte
+ kmemset_words_lp:
+ str r1, [r0], #4
+ subs r12, #4
+ bne kmemset_words_lp
+kmemset_halfword_byte:
+ tst r2, #2
+ strneh r1, [r0], #2
+ tst r2, #1
+ strneb r1, [r0]
+ bx lr
diff --git a/kernel/source/ktimer.c b/kernel/source/ktimer.c
new file mode 100644
index 0000000..c42e5db
--- /dev/null
+++ b/kernel/source/ktimer.c
@@ -0,0 +1,116 @@
+#include
+#include
+#include "types.h"
+#include "timer.h"
+#include "internal/list.h"
+#include "arm11/hardware/interrupt.h"
+#include "arm11/hardware/timer.h"
+#include "internal/kernel_private.h"
+#include "internal/slabheap.h"
+#include "internal/config.h"
+ //#include "arm11/fmt.h"
+
+
+/*typedef struct
+{
+ ListNode node;
+ u32 delta;
+ u32 ticks;
+ const bool pulse;
+ ListNode waitQueue;
+} Timer;
+
+
+static SlabHeap g_timerSlab = {0};
+static ListNode g_deltaQueue = {0};
+
+
+
+static void timerIrqHandler(UNUSED u32 intSource);
+static void addToDeltaQueue(Timer *const timer, u32 ticks);
+
+void _timerInit(void)
+{
+ slabInit(&g_timerSlab, sizeof(Timer), MAX_TIMERS);
+ listInit(&g_deltaQueue);
+ IRQ_registerHandler(IRQ_TIMER, 12, 0, true, timerIrqHandler);
+}
+
+KTimer createTimer(bool pulse)
+{
+ Timer *const timer = (Timer*)slabAlloc(&g_timerSlab);
+
+ *(bool*)&timer->pulse = pulse;
+ listInit(&timer->waitQueue);
+
+ return timer;
+}
+
+void deleteTimer(const KTimer ktimer)
+{
+ Timer *const timer = (Timer*)ktimer;
+
+ kernelLock();
+ waitQueueWakeN(&timer->waitQueue, (u32)-1, KRES_HANDLE_DELETED, true);
+
+ slabFree(&g_timerSlab, timer);
+}
+
+static void timerIrqHandler(UNUSED u32 intSource)
+{
+ kernelLock();
+//if(listEmpty(&g_deltaQueue)) *((vu32*)4) = 4; // This should never happen
+ Timer *timer = LIST_ENTRY(listPop(&g_deltaQueue), Timer, node);
+ if(timer->pulse) addToDeltaQueue(timer, timer->ticks);
+ if(!listEmpty(&g_deltaQueue))
+ {
+ // Don't use fp math in ISRs.
+ TIMER_start(1, LIST_FIRST_ENTRY(&g_deltaQueue, Timer, node)->delta, false, true);
+ }
+ waitQueueWakeN(&timer->waitQueue, (u32)-1, KRES_OK, false);
+}
+
+static void addToDeltaQueue(Timer *const timer, u32 ticks)
+{
+ Timer *pos;
+ u32 deltaSum = 0;
+ LIST_FOR_EACH_ENTRY(pos, &g_deltaQueue, node)
+ {
+ deltaSum += pos->delta;
+ if(deltaSum > ticks)
+ {
+ timer->delta = ticks - (deltaSum - pos->delta);
+ listAddBefore(&pos->node, &timer->node);
+ return;
+ }
+ }
+
+ timer->delta = ticks;
+ listPush(&g_deltaQueue, &timer->node);
+}
+
+void startTimer(const KTimer ktimer, uint32_t usec)
+{
+ Timer *const timer = (Timer*)ktimer;
+
+ const u32 ticks = TIMER_FREQ(1, 1000000) * usec;
+ timer->ticks = ticks;
+
+ kernelLock();
+ const bool firstTimer = listEmpty(&g_deltaQueue);
+ addToDeltaQueue(timer, ticks);
+ kernelUnlock();
+ if(firstTimer) TIMER_start(1, ticks, false, true);
+}
+
+void stopTimer(const KTimer ktimer)
+{
+}
+
+KRes waitForTimer(const KTimer ktimer)
+{
+ Timer *const timer = (Timer*)ktimer;
+
+ kernelLock();
+ return waitQueueBlock(&timer->waitQueue);
+}*/
diff --git a/kernel/source/mutex.c b/kernel/source/mutex.c
new file mode 100644
index 0000000..bf30ba7
--- /dev/null
+++ b/kernel/source/mutex.c
@@ -0,0 +1,93 @@
+#include
+#include "types.h"
+#include "mutex.h"
+#include "internal/list.h"
+#include "internal/kernel_private.h"
+#include "internal/util.h"
+#include "internal/slabheap.h"
+#include "internal/config.h"
+
+
+typedef struct
+{
+ const TaskCb *owner;
+ ListNode waitQueue;
+} Mutex;
+
+
+static SlabHeap g_mutexSlab = {0};
+
+
+
+void _mutexSlabInit(void)
+{
+ slabInit(&g_mutexSlab, sizeof(Mutex), MAX_MUTEXES);
+}
+
+// TODO: Test mutex with multiple cores.
+KMutex createMutex(void)
+{
+ Mutex *const mutex = (Mutex*)slabAlloc(&g_mutexSlab);
+
+ mutex->owner = NULL;
+ listInit(&mutex->waitQueue);
+
+ return mutex;
+}
+
+void deleteMutex(const KMutex kmutex)
+{
+ Mutex *const mutex = (Mutex*)kmutex;
+
+ kernelLock();
+ waitQueueWakeN(&mutex->waitQueue, (u32)-1, KRES_HANDLE_DELETED, true);
+
+ slabFree(&g_mutexSlab, mutex);
+}
+
+KRes lockMutex(const KMutex kmutex)
+{
+ KRes res;
+
+ do
+ {
+ Mutex *const mutex = (Mutex*)kmutex;
+
+ kernelLock();
+ if(UNLIKELY(mutex->owner != NULL))
+ {
+ res = waitQueueBlock(&mutex->waitQueue);
+ if(UNLIKELY(res != KRES_OK)) break;
+ }
+ else
+ {
+ mutex->owner = getCurrentTask();
+ kernelUnlock();
+ res = KRES_OK;
+ break;
+ }
+ } while(1);
+
+ return res;
+}
+
+// TODO: Test if it works and only unlocks if current task == owner.
+KRes unlockMutex(const KMutex kmutex)
+{
+ Mutex *const mutex = (Mutex*)kmutex;
+ KRes res = KRES_OK;
+
+ kernelLock();
+ if(LIKELY(mutex->owner != NULL))
+ {
+ if(LIKELY(mutex->owner == getCurrentTask()))
+ {
+ mutex->owner = NULL;
+ waitQueueWakeN(&mutex->waitQueue, 1, KRES_OK, true);
+ }
+ else res = KRES_NO_PERMISSIONS;
+ }
+ else kernelUnlock();
+
+ return res;
+}
diff --git a/kernel/source/semaphore.c b/kernel/source/semaphore.c
new file mode 100644
index 0000000..7372ef7
--- /dev/null
+++ b/kernel/source/semaphore.c
@@ -0,0 +1,84 @@
+#include
+#include
+#include "types.h"
+#include "semaphore.h"
+#include "internal/list.h"
+#include "internal/kernel_private.h"
+#include "internal/util.h"
+#include "internal/slabheap.h"
+#include "internal/config.h"
+
+
+typedef struct
+{
+ s32 count;
+ ListNode waitQueue;
+} Semaphore;
+
+
+static SlabHeap g_semaSlab = {0};
+
+
+
+void _semaphoreSlabInit(void)
+{
+ slabInit(&g_semaSlab, sizeof(Semaphore), MAX_SEMAPHORES);
+}
+
+// TODO: Test semaphore with multiple cores.
+KSema createSemaphore(int32_t count)
+{
+ Semaphore *const sema = (Semaphore*)slabAlloc(&g_semaSlab);
+
+ sema->count = count;
+ listInit(&sema->waitQueue);
+
+ return sema;
+}
+
+void deleteSemaphore(const KSema ksema)
+{
+ Semaphore *const sema = (Semaphore*)ksema;
+
+ kernelLock();
+ waitQueueWakeN(&sema->waitQueue, (u32)-1, KRES_HANDLE_DELETED, true);
+
+ slabFree(&g_semaSlab, sema);
+}
+
+KRes pollSemaphore(const KSema ksema)
+{
+ Semaphore *const sema = (Semaphore*)ksema;
+ KRes res;
+
+ // TODO: Plain spinlocks instead?
+ kernelLock();
+ if(UNLIKELY(sema->count <= 0)) res = KRES_WOULD_BLOCK;
+ else {sema->count--; res = KRES_OK;}
+ kernelUnlock();
+
+ return res;
+}
+
+KRes waitForSemaphore(const KSema ksema)
+{
+ Semaphore *const sema = (Semaphore*)ksema;
+ KRes res;
+
+ kernelLock();
+ if(UNLIKELY(--sema->count < 0)) res = waitQueueBlock(&sema->waitQueue);
+ else {kernelUnlock(); res = KRES_OK;}
+
+ return res;
+}
+
+void signalSemaphore(const KSema ksema, uint32_t signalCount, bool reschedule)
+{
+ Semaphore *const sema = (Semaphore*)ksema;
+
+ kernelLock();
+ //if(UNLIKELY(++sema->count <= 0))
+ if(UNLIKELY((sema->count += signalCount) <= 0))
+ waitQueueWakeN(&sema->waitQueue, signalCount, KRES_OK, reschedule);
+ else kernelUnlock();
+}
diff --git a/kernel/source/slabheap.c b/kernel/source/slabheap.c
new file mode 100644
index 0000000..9483449
--- /dev/null
+++ b/kernel/source/slabheap.c
@@ -0,0 +1,44 @@
+#include
+#include
+#include "internal/slabheap.h"
+#include "internal/kmemcpy_set.h"
+
+
+
+void slabInit(SlabHeap *slab, size_t objSize, size_t num)
+{
+ if(objSize < sizeof(SlabHeap) || !num) return;
+
+ listInit(slab);
+
+ void *pool = malloc(objSize * num);
+ if(!pool) return;
+ do
+ {
+ listPush(slab, (SlabHeap*)pool);
+ pool += objSize;
+ } while(--num);
+}
+
+void* slabAlloc(SlabHeap *slab)
+{
+ if(!slab || listEmpty(slab)) return NULL;
+
+ return listPop(slab);
+}
+
+void* slabCalloc(SlabHeap *slab, size_t clrSize)
+{
+ void *const ptr = slabAlloc(slab);
+ if(ptr) kmemset(ptr, 0, clrSize);
+ return ptr;
+}
+
+void slabFree(SlabHeap *slab, void *ptr)
+{
+ if(!slab || !ptr) return;
+
+ // Keep gaps filled by allocating the same mem
+ // again next time an object is allocated.
+ listPushTail(slab, (SlabHeap*)ptr);
+}
diff --git a/source/arm11/hardware/gfx.c b/source/arm11/hardware/gfx.c
index d219a90..3bd49d2 100644
--- a/source/arm11/hardware/gfx.c
+++ b/source/arm11/hardware/gfx.c
@@ -36,13 +36,14 @@
#include "arm.h"
#include "util.h"
#include "arm11/allocator/vram.h"
+#include "event.h"
static struct
{
u8 lcdPower; // 1 = on. Bit 4 top light, bit 2 bottom light, bit 0 LCDs.
u8 lcdLights[2]; // LCD backlight brightness. Top, bottom.
- bool events[6];
+ KEvent events[6];
u32 swap; // Currently active framebuffer.
void *framebufs[2][4]; // For each screen A1, A2, B1, B2
u8 doubleBuf[2]; // Top, bottom, 1 = enable.
@@ -60,6 +61,7 @@ static void gfxIrqHandler(u32 intSource);
void GFX_init(GfxFbFmt fmtTop, GfxFbFmt fmtBot)
{
+ g_gfxState.lcdPower = 0x15; // All on.
setupFramebufs(fmtTop, fmtBot);
g_gfxState.doubleBuf[0] = 1;
g_gfxState.doubleBuf[1] = 1;
@@ -82,7 +84,7 @@ void GFX_init(GfxFbFmt fmtTop, GfxFbFmt fmtBot)
REG_LCD_PDC0_SWAP = 0; // Select framebuf 0.
REG_LCD_PDC1_SWAP = 0;
REG_LCD_PDC0_CNT = PDC_CNT_OUT_E | PDC_CNT_I_MASK_ERR | PDC_CNT_I_MASK_H | PDC_CNT_E; // Start
- REG_LCD_PDC1_CNT = PDC_CNT_OUT_E | PDC_CNT_I_MASK_ERR | PDC_CNT_I_MASK_H | PDC_CNT_E;
+ REG_LCD_PDC1_CNT = PDC_CNT_OUT_E | PDC_CNT_I_MASK_ALL | PDC_CNT_E;
// LCD reg setup.
REG_LCD_ABL0_FILL = 1u<<24; // Force blackscreen
@@ -92,13 +94,14 @@ void GFX_init(GfxFbFmt fmtTop, GfxFbFmt fmtBot)
REG_LCD_RST = 0;
REG_LCD_UNK00C = 0x10001;
- // Register IRQ handlers.
- IRQ_registerIsr(IRQ_PSC0, 14, 0, gfxIrqHandler);
- IRQ_registerIsr(IRQ_PSC1, 14, 0, gfxIrqHandler);
- IRQ_registerIsr(IRQ_PDC0, 14, 0, gfxIrqHandler);
- //IRQ_registerIsr(IRQ_PDC1, 14, 0, gfxIrqHandler);
- IRQ_registerIsr(IRQ_PPF, 14, 0, gfxIrqHandler);
- IRQ_registerIsr(IRQ_P3D, 14, 0, gfxIrqHandler);
+ // Create IRQ events.
+ // PSC0, PSC1, PDC0, PDC1, PPF, P3D
+ for(u8 i = 0; i < 6; i++)
+ {
+ KEvent tmp = createEvent(false);
+ bindInterruptToEvent(tmp, IRQ_PSC0 + i, 14);
+ g_gfxState.events[i] = tmp;
+ }
// Clear entire VRAM.
GX_memoryFill((u32*)VRAM_BANK0, 1u<<9, VRAM_SIZE / 2, 0,
@@ -129,7 +132,6 @@ void GFX_init(GfxFbFmt fmtTop, GfxFbFmt fmtBot)
REG_LCD_ABL1_LIGHT_PWM = 0x1023E;
MCU_controlLCDPower(0x28u); // Power on backlights.
if(MCU_waitEvents(0x3Fu<<24) != 0x28u<<24) panic();
- g_gfxState.lcdPower = 0x15; // All on.
// Make sure the fills finished.
GFX_waitForPSC0();
@@ -176,12 +178,13 @@ void GFX_deinit(void)
deallocFramebufs();
- IRQ_unregisterIsr(IRQ_PSC0);
- IRQ_unregisterIsr(IRQ_PSC1);
- IRQ_unregisterIsr(IRQ_PDC0);
- //IRQ_unregisterIsr(IRQ_PDC1);
- IRQ_unregisterIsr(IRQ_PPF);
- IRQ_unregisterIsr(IRQ_P3D);
+ // PSC0, PSC1, PDC0, PDC1, PPF, P3D
+ for(u8 i = 0; i < 6; i++)
+ {
+ unbindInterruptEvent(IRQ_PSC0 + i);
+ deleteEvent(g_gfxState.events[i]);
+ g_gfxState.events[i] = NULL;
+ }
}
void GFX_setFramebufFmt(GfxFbFmt fmtTop, GfxFbFmt fmtBot)
@@ -386,18 +389,11 @@ void GFX_swapFramebufs(void)
void GFX_waitForEvent(GfxEvent event, bool discard)
{
- bool *const events = g_gfxState.events;
+ const KEvent kevent = g_gfxState.events[event];
- if(discard) atomic_store_explicit(&events[event], false, memory_order_relaxed);
- while(!atomic_load_explicit(&events[event], memory_order_relaxed)) __wfe();
- atomic_store_explicit(&events[event], false, memory_order_relaxed);
-}
-
-static void gfxIrqHandler(u32 intSource)
-{
- bool *const events = g_gfxState.events;
-
- atomic_store_explicit(&events[intSource - IRQ_PSC0], true, memory_order_relaxed);
+ if(discard) clearEvent(kevent);
+ waitForEvent(kevent);
+ clearEvent(kevent);
}
void GX_memoryFill(u32 *buf0a, u32 buf0v, u32 buf0Sz, u32 val0, u32 *buf1a, u32 buf1v, u32 buf1Sz, u32 val1)
diff --git a/source/arm11/hardware/i2c.c b/source/arm11/hardware/i2c.c
index 011c1df..4689b8e 100644
--- a/source/arm11/hardware/i2c.c
+++ b/source/arm11/hardware/i2c.c
@@ -19,6 +19,8 @@
#include "types.h"
#include "mem_map.h"
#include "arm11/hardware/i2c.h"
+#include "event.h"
+#include "mutex.h"
#include "arm11/hardware/interrupt.h"
@@ -63,8 +65,189 @@ static const struct
{I2C_BUS3, 0x54}
};
+typedef struct
+{
+ I2cRegs *const regs;
+ KEvent event;
+ KMutex mutex;
+} I2cState;
+static I2cState g_i2cState[3] = {{(I2cRegs*)I2C1_REGS_BASE, NULL, NULL},
+ {(I2cRegs*)I2C2_REGS_BASE, NULL, NULL},
+ {(I2cRegs*)I2C3_REGS_BASE, NULL, NULL}};
+
+static bool checkAck(I2cRegs *const regs)
+{
+ // If we received a NACK stop the transfer.
+ if((regs->I2C_CNT & I2C_ACK) == 0u)
+ {
+ regs->I2C_CNT = I2C_ENABLE | I2C_IRQ_ENABLE | I2C_ERROR | I2C_STOP;
+ return false;
+ }
+
+ return true;
+}
+
+static void sendByte(I2cRegs *const regs, u8 data, u8 params, const KEvent event)
+{
+ regs->I2C_DATA = data;
+ regs->I2C_CNT = I2C_ENABLE | I2C_IRQ_ENABLE | I2C_DIR_WRITE | params;
+ waitForEvent(event);
+}
+
+static u8 recvByte(I2cRegs *const regs, u8 params, const KEvent event)
+{
+ regs->I2C_CNT = I2C_ENABLE | I2C_IRQ_ENABLE | I2C_DIR_READ | params;
+ waitForEvent(event);
+ return regs->I2C_DATA;
+}
+
+void I2C_init(void)
+{
+ static bool inited = false;
+ if(inited) return;
+ inited = true;
+
+
+ KEvent tmp = createEvent(true);
+ bindInterruptToEvent(tmp, IRQ_I2C1, 14);
+ g_i2cState[0].event = tmp;
+ tmp = createEvent(true);
+ bindInterruptToEvent(tmp, IRQ_I2C2, 14);
+ g_i2cState[1].event = tmp;
+ tmp = createEvent(true);
+ bindInterruptToEvent(tmp, IRQ_I2C3, 14);
+ g_i2cState[2].event = tmp;
+
+ for(u32 i = 0; i < 3; i++)
+ {
+ g_i2cState[i].mutex = createMutex();
+ }
+
+ while(REG_I2C1_CNT & I2C_ENABLE);
+ REG_I2C1_CNTEX = I2C_CLK_STRETCH;
+ REG_I2C1_SCL = I2C_DELAYS(5u, 0u);
+
+ while(REG_I2C2_CNT & I2C_ENABLE);
+ REG_I2C2_CNTEX = I2C_CLK_STRETCH;
+ REG_I2C2_SCL = I2C_DELAYS(5u, 0u);
+
+ while(REG_I2C3_CNT & I2C_ENABLE);
+ REG_I2C3_CNTEX = I2C_CLK_STRETCH;
+ REG_I2C3_SCL = I2C_DELAYS(5u, 0u);
+}
+
+static bool startTransfer(u8 devAddr, u8 regAddr, bool read, const I2cState *const state)
+{
+ u32 tries = 8;
+ do
+ {
+ I2cRegs *const regs = state->regs;
+ const KEvent event = state->event;
+
+ // Edge case on previous transfer error (NACK).
+ // This is a special case where we can't predict when or if
+ // the IRQ has fired. If it fires after checking but
+ // before a wfi this would hang.
+ if(regs->I2C_CNT & I2C_ENABLE) waitForEvent(event);
+ clearEvent(event);
+
+ // Select device and start.
+ sendByte(regs, devAddr, I2C_START, event);
+ if(!checkAck(regs)) continue;
+
+ // Select register.
+ sendByte(regs, regAddr, 0, event);
+ if(!checkAck(regs)) continue;
+
+ // Select device in read mode for read transfer.
+ if(read)
+ {
+ sendByte(regs, devAddr | 1u, I2C_START, event);
+ if(!checkAck(regs)) continue;
+ }
+
+ break;
+ } while(--tries > 0);
+
+ return tries > 0;
+}
+
+bool I2C_readRegBuf(I2cDevice devId, u8 regAddr, u8 *out, u32 size)
+{
+ const u8 devAddr = i2cDevTable[devId].devAddr;
+ const I2cState *const state = &g_i2cState[i2cDevTable[devId].busId];
+ I2cRegs *const regs = state->regs;
+ const KEvent event = state->event;
+ const KMutex mutex = state->mutex;
+
+
+ bool res = true;
+ lockMutex(mutex);
+ if(startTransfer(devAddr, regAddr, true, state))
+ {
+ while(--size) *out++ = recvByte(regs, I2C_ACK, event);
+
+ // Last byte transfer.
+ *out = recvByte(regs, I2C_STOP, event);
+ }
+ else res = false;
+ unlockMutex(mutex);
+
+ return res;
+}
+
+bool I2C_writeRegBuf(I2cDevice devId, u8 regAddr, const u8 *in, u32 size)
+{
+ const u8 devAddr = i2cDevTable[devId].devAddr;
+ const I2cState *const state = &g_i2cState[i2cDevTable[devId].busId];
+ I2cRegs *const regs = state->regs;
+ const KEvent event = state->event;
+ const KMutex mutex = state->mutex;
+
+
+ lockMutex(mutex);
+ if(!startTransfer(devAddr, regAddr, false, state))
+ {
+ unlockMutex(mutex);
+ return false;
+ }
+
+ while(--size)
+ {
+ sendByte(regs, *in++, 0, event);
+ if(!checkAck(regs))
+ {
+ unlockMutex(mutex);
+ return false;
+ }
+ }
+
+ // Last byte transfer.
+ sendByte(regs, *in, I2C_STOP, event);
+ if(!checkAck(regs))
+ {
+ unlockMutex(mutex);
+ return false;
+ }
+ unlockMutex(mutex);
+
+ return true;
+}
+
+u8 I2C_readReg(I2cDevice devId, u8 regAddr)
+{
+ u8 data;
+ if(!I2C_readRegBuf(devId, regAddr, &data, 1)) return 0xFF;
+ return data;
+}
+
+bool I2C_writeReg(I2cDevice devId, u8 regAddr, u8 data)
+{
+ return I2C_writeRegBuf(devId, regAddr, &data, 1);
+}
+
static I2cRegs* i2cGetBusRegsBase(u8 busId)
{
I2cRegs *base;
@@ -86,150 +269,6 @@ static I2cRegs* i2cGetBusRegsBase(u8 busId)
return base;
}
-static inline void i2cWaitBusyIrq(const I2cRegs *const regs)
-{
- do
- {
- __wfi();
- } while(regs->I2C_CNT & I2C_ENABLE);
-}
-
-static bool i2cCheckAck(I2cRegs *const regs)
-{
- // If we received a NACK stop the transfer.
- if((regs->I2C_CNT & I2C_ACK) == 0u)
- {
- regs->I2C_CNT = I2C_ENABLE | I2C_IRQ_ENABLE | I2C_ERROR | I2C_STOP;
- return false;
- }
-
- return true;
-}
-
-void I2C_init(void)
-{
- static bool inited = false;
- if(inited) return;
- inited = true;
-
- I2cRegs *regs = i2cGetBusRegsBase(I2C_BUS1);
- while(regs->I2C_CNT & I2C_ENABLE);
- regs->I2C_CNTEX = I2C_CLK_STRETCH;
- regs->I2C_SCL = I2C_DELAYS(5u, 0u);
-
- regs = i2cGetBusRegsBase(I2C_BUS2);
- while(regs->I2C_CNT & I2C_ENABLE);
- regs->I2C_CNTEX = I2C_CLK_STRETCH;
- regs->I2C_SCL = I2C_DELAYS(5u, 0u);
-
- regs = i2cGetBusRegsBase(I2C_BUS3);
- while(regs->I2C_CNT & I2C_ENABLE);
- regs->I2C_CNTEX = I2C_CLK_STRETCH;
- regs->I2C_SCL = I2C_DELAYS(5u, 0u);
-
- IRQ_registerIsr(IRQ_I2C1, 14, 0, NULL);
- IRQ_registerIsr(IRQ_I2C2, 14, 0, NULL);
- IRQ_registerIsr(IRQ_I2C3, 14, 0, NULL);
-}
-
-static bool i2cStartTransfer(u8 devAddr, u8 regAddr, bool read, I2cRegs *const regs)
-{
- u32 tries = 8;
- do
- {
- // Edge case on previous transfer error.
- // This is a special case where we can't predict when or if
- // the IRQ has already fired. If it fires after checking but
- // before a wfi this would hang.
- while(regs->I2C_CNT & I2C_ENABLE) __wfe();
-
- // Select device and start.
- regs->I2C_DATA = devAddr;
- regs->I2C_CNT = I2C_ENABLE | I2C_IRQ_ENABLE | I2C_DIR_WRITE | I2C_START;
- i2cWaitBusyIrq(regs);
- if(!i2cCheckAck(regs)) continue;
-
- // Select register.
- regs->I2C_DATA = regAddr;
- regs->I2C_CNT = I2C_ENABLE | I2C_IRQ_ENABLE | I2C_DIR_WRITE;
- i2cWaitBusyIrq(regs);
- if(!i2cCheckAck(regs)) continue;
-
- // Select device in read mode for read transfer.
- if(read)
- {
- regs->I2C_DATA = devAddr | 1u; // Set bit 0 for read.
- regs->I2C_CNT = I2C_ENABLE | I2C_IRQ_ENABLE | I2C_DIR_WRITE | I2C_START;
- i2cWaitBusyIrq(regs);
- if(!i2cCheckAck(regs)) continue;
- }
-
- break;
- } while(--tries > 0);
-
- return tries > 0;
-}
-
-bool I2C_readRegBuf(I2cDevice devId, u8 regAddr, u8 *out, u32 size)
-{
- const u8 devAddr = i2cDevTable[devId].devAddr;
- I2cRegs *const regs = i2cGetBusRegsBase(i2cDevTable[devId].busId);
-
-
- if(!i2cStartTransfer(devAddr, regAddr, true, regs)) return false;
-
- while(--size)
- {
- regs->I2C_CNT = I2C_ENABLE | I2C_IRQ_ENABLE | I2C_DIR_READ | I2C_ACK;
- i2cWaitBusyIrq(regs);
- *out++ = regs->I2C_DATA;
- }
-
- // Last byte transfer.
- regs->I2C_CNT = I2C_ENABLE | I2C_IRQ_ENABLE | I2C_DIR_READ | I2C_STOP;
- i2cWaitBusyIrq(regs);
- *out = regs->I2C_DATA;
-
- return true;
-}
-
-bool I2C_writeRegBuf(I2cDevice devId, u8 regAddr, const u8 *in, u32 size)
-{
- const u8 devAddr = i2cDevTable[devId].devAddr;
- I2cRegs *const regs = i2cGetBusRegsBase(i2cDevTable[devId].busId);
-
-
- if(!i2cStartTransfer(devAddr, regAddr, false, regs)) return false;
-
- while(--size)
- {
- regs->I2C_DATA = *in++;
- regs->I2C_CNT = I2C_ENABLE | I2C_IRQ_ENABLE | I2C_DIR_WRITE;
- i2cWaitBusyIrq(regs);
- if(!i2cCheckAck(regs)) return false;
- }
-
- // Last byte transfer.
- regs->I2C_DATA = *in;
- regs->I2C_CNT = I2C_ENABLE | I2C_IRQ_ENABLE | I2C_DIR_WRITE | I2C_STOP;
- i2cWaitBusyIrq(regs);
- if(!i2cCheckAck(regs)) return false;
-
- return true;
-}
-
-u8 I2C_readReg(I2cDevice devId, u8 regAddr)
-{
- u8 data;
- if(!I2C_readRegBuf(devId, regAddr, &data, 1)) return 0xFF;
- return data;
-}
-
-bool I2C_writeReg(I2cDevice devId, u8 regAddr, u8 data)
-{
- return I2C_writeRegBuf(devId, regAddr, &data, 1);
-}
-
bool I2C_writeRegIntSafe(I2cDevice devId, u8 regAddr, u8 data)
{
const u8 devAddr = i2cDevTable[devId].devAddr;
@@ -245,13 +284,13 @@ bool I2C_writeRegIntSafe(I2cDevice devId, u8 regAddr, u8 data)
regs->I2C_DATA = devAddr;
regs->I2C_CNT = I2C_ENABLE | I2C_DIR_WRITE | I2C_START;
while(regs->I2C_CNT & I2C_ENABLE);
- if(!i2cCheckAck(regs)) continue;
+ if(!checkAck(regs)) continue;
// Select register.
regs->I2C_DATA = regAddr;
regs->I2C_CNT = I2C_ENABLE | I2C_DIR_WRITE;
while(regs->I2C_CNT & I2C_ENABLE);
- if(!i2cCheckAck(regs)) continue;
+ if(!checkAck(regs)) continue;
break;
} while(--tries > 0);
@@ -261,7 +300,7 @@ bool I2C_writeRegIntSafe(I2cDevice devId, u8 regAddr, u8 data)
regs->I2C_DATA = data;
regs->I2C_CNT = I2C_ENABLE | I2C_DIR_WRITE | I2C_STOP;
while(regs->I2C_CNT & I2C_ENABLE);
- if(!i2cCheckAck(regs)) return false;
+ if(!checkAck(regs)) return false;
return true;
}
diff --git a/source/arm11/hardware/lgy.c b/source/arm11/hardware/lgy.c
index 26d09e9..e2b9e69 100644
--- a/source/arm11/hardware/lgy.c
+++ b/source/arm11/hardware/lgy.c
@@ -345,8 +345,6 @@ void LGY_handleEvents(void)
debugTests();
#endif
- LGYFB_processFrame();
-
// Bit 0 triggers wakeup. Bit 1 sleep state/ack sleep end. Bit 2 unk. Bit 15 IRQ enable (triggers IRQ 89).
//if(REG_LGY_SLEEP & 2u) REG_HID_PADCNT = REG_LGY_PADCNT;
}
diff --git a/source/arm11/hardware/lgyfb.c b/source/arm11/hardware/lgyfb.c
index 9f3dfde..bfdeddf 100644
--- a/source/arm11/hardware/lgyfb.c
+++ b/source/arm11/hardware/lgyfb.c
@@ -7,6 +7,8 @@
#include "arm11/hardware/lcd.h"
#include "hardware/gfx.h"
#include "lgyfb_dma330.h"
+#include "kernel.h"
+#include "event.h"
#define LGYFB_TOP_REGS_BASE (IO_MEM_ARM9_ARM11 + 0x11000)
@@ -75,7 +77,8 @@
#define LGYFB_BOT_FIFO *((const vu32*)(0x10310000))
-static bool g_frameReady = false;
+static KTask g_lgyFbTask = NULL;
+static KEvent g_frameReadyEvent = NULL;
@@ -92,121 +95,21 @@ static void lgyFbDmaIrqHandler(UNUSED u32 intSource)
else vtotal = 414; // Faster than GBA.
REG_LCD_PDC0_VTOTAL = vtotal;
- atomic_store_explicit(&g_frameReady, true, memory_order_relaxed);
+ signalEvent(g_frameReadyEvent, false);
}
-static void setScaleMatrixTop(u32 len, u32 patt, const s16 *const matrix)
+//#include "arm11/fmt.h"
+static void lgyFbTask(void *args)
{
- REG_LGYFB_TOP_V_LEN = len - 1;
- REG_LGYFB_TOP_V_PATT = patt;
- REG_LGYFB_TOP_H_LEN = len - 1;
- REG_LGYFB_TOP_H_PATT = patt;
+ const KEvent event = (KEvent)args;
- for(u32 y = 0; y < 6; y++)
+ do
{
- for(u32 x = 0; x < len; x++)
- {
- const s16 tmp = matrix[len * y + x];
+ waitForEvent(event);
- // Correct the color range using the scale matrix hardware.
- // For example when converting RGB555 to RGB8 LgyFb lazily shifts the 5 bits up
- // so 0b00011111 becomes 0b11111000. This creates wrong spacing between colors.
- // TODO: What is the "+ 8" good for?
- REG_LGYFB_TOP_V_MATRIX[y][x] = tmp * 0xFF / 0xF8 + 8;
- REG_LGYFB_TOP_H_MATRIX[y][x] = tmp + 8;
- }
- }
-}
-
-void LGYFB_init(void)
-{
- if(DMA330_run(0, program)) return;
-
- //REG_LGYFB_TOP_SIZE = LGYFB_SIZE(240u, 160u);
- REG_LGYFB_TOP_SIZE = LGYFB_SIZE(360u, 240u);
- REG_LGYFB_TOP_STAT = LGYFB_IRQ_MASK;
- REG_LGYFB_TOP_IRQ = 0;
- REG_LGYFB_TOP_ALPHA = 0xFF;
-
- /*
- * Limitations:
- * First pattern bit must be 1 and last 0 (for V-scale) or it loses sync with the DS/GBA input.
- *
- * Matrix ranges:
- * in[-3] -1024-1023 (0xFC00-0x03FF)
- * in[-2] -4096-4095 (0xF000-0x0FFF)
- * in[-1] -32768-32767 (0x8000-0x7FFF)
- * in[0] -32768-32767 (0x8000-0x7FFF)
- * in[1] -4096-4095 (0xF000-0x0FFF)
- * in[2] -1024-1023 (0xFC00-0x03FF)
- *
- * Note: At scanline start the in FIFO is all filled with the first pixel.
- */
- static const s16 scaleMatrix[6 * 6] =
- {
- // Original from AGB_FIRM.
- /* 0, 0, 0, 0, 0, 0, // in[-3]
- 0, 0, 0, 0, 0, 0, // in[-2]
- 0, 0x2000, 0x4000, 0, 0x2000, 0x4000, // in[-1]
- 0x4000, 0x2000, 0, 0x4000, 0x2000, 0, // in[0]
- 0, 0, 0, 0, 0, 0, // in[1]
- 0, 0, 0, 0, 0, 0*/ // in[2]
- // out[0] out[1] out[2] out[3] out[4] out[5] out[6] out[7]
-
- // Razor sharp (pixel duplication).
- /* 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0,
- 0, 0, 0x4000, 0, 0, 0x4000,
- 0x4000, 0x4000, 0, 0x4000, 0x4000, 0,
- 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0*/
-
- // Sharp interpolated.
- 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0,
- 0, 0, 0x2000, 0, 0, 0x2000,
- 0x4000, 0x4000, 0x2000, 0x4000, 0x4000, 0x2000,
- 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0
- };
- setScaleMatrixTop(6, 0b00011011, scaleMatrix);
-
- // With RGB8 output solid red and blue are converted to 0xF8 and green to 0xFA.
- // The green bias exists on the whole range of green colors.
- // Some results:
- // RGBA8: Same as RGB8 but with useless alpha component.
- // RGB8: Observed best format. Invisible dithering and best color accuracy.
- // RGB565: A little visible dithering. Good color accuracy.
- // RGB5551: Lots of visible dithering. Good color accuracy (a little worse than 565).
- REG_LGYFB_TOP_CNT = LGYFB_DMA_E | LGYFB_OUT_SWIZZLE | LGYFB_OUT_FMT_8880 |
- LGYFB_HSCALE_E | LGYFB_VSCALE_E | LGYFB_ENABLE;
-
- IRQ_registerIsr(IRQ_CDMA_EVENT0, 13, 0, lgyFbDmaIrqHandler);
-
-
- const double inGamma = 4.0;
- const double outGamma = 2.2;
- //const double contrast = .74851331406341291833644689906823; // GBA
- //const double brightness = .25148668593658708166355310093177; // GBA
- const double contrast = 1.0; // No-op
- const double brightness = 0.0; // No-op
- //REG_LCD_PDC0_GTBL_IDX = 0;
- for(u32 i = 0; i < 256; i++)
- {
- // Credits for this algo go to Extrems.
- // Originally from Game Boy Interface Standard Edition for the Game Cube.
- //const u32 x = (i & ~7u) | i>>5;
- u32 res = pow(pow(contrast, inGamma) * pow((double)i / 255.0f + brightness / contrast, inGamma),
- 1.0 / outGamma) * 255.0f;
- if(res > 255) res = 255;
-
- // Same adjustment for red/green/blue.
- REG_LCD_PDC0_GTBL_FIFO = res<<16 | res<<8 | res;
- }
-}
-
-static void rotateFrame(void)
-{
+ // Rotate the frame using the GPU.
+ // 240x160: TODO.
+ // 360x240: about 0.623620315 ms.
// 360x240, no filter.
alignas(16) static const u8 firstList[1136] =
{
@@ -491,39 +394,140 @@ alignas(16) static const u8 secondList[448] =
0x10, 0x00, 0x0F, 0x00
};*/
- static bool normalRender = false;
- u32 listSize;
- const u32 *list;
- if(normalRender == false)
- {
- normalRender = true;
+ static bool normalRender = false;
+ u32 listSize;
+ const u32 *list;
+ if(normalRender == false)
+ {
+ normalRender = true;
- listSize = 1136;
- list = (u32*)firstList;
- }
- else
- {
- listSize = 448;
- list = (u32*)secondList;
- }
- GX_processCommandList(listSize, list);
- GFX_waitForP3D();
- GX_displayTransfer((u32*)(0x18180000 + (16 * 240 * 3)), 368u<<16 | 240u,
- GFX_getFramebuffer(SCREEN_TOP) + (16 * 240 * 3), 368u<<16 | 240u, 1u<<12 | 1u<<8);
- GFX_waitForPPF();
+ listSize = 1136;
+ list = (u32*)firstList;
+ }
+ else
+ {
+ listSize = 448;
+ list = (u32*)secondList;
+ }
+ GX_processCommandList(listSize, list);
+ GFX_waitForP3D();
+ GX_displayTransfer((u32*)(0x18180000 + (16 * 240 * 3)), 368u<<16 | 240u,
+ GFX_getFramebuffer(SCREEN_TOP) + (16 * 240 * 3), 368u<<16 | 240u, 1u<<12 | 1u<<8);
+ GFX_waitForPPF();
+ GFX_swapFramebufs();
+ } while(1);
}
-void LGYFB_processFrame(void)
+static void setScaleMatrixTop(u32 len, u32 patt, const s16 *const matrix)
{
- if(atomic_load_explicit(&g_frameReady, memory_order_relaxed))
- {
- atomic_store_explicit(&g_frameReady, false, memory_order_relaxed);
+ REG_LGYFB_TOP_V_LEN = len - 1;
+ REG_LGYFB_TOP_V_PATT = patt;
+ REG_LGYFB_TOP_H_LEN = len - 1;
+ REG_LGYFB_TOP_H_PATT = patt;
- // Rotate the frame using the GPU.
- // 240x160: TODO.
- // 360x240: about 0.623620315 ms.
- rotateFrame();
- GFX_swapFramebufs();
+ for(u32 y = 0; y < 6; y++)
+ {
+ for(u32 x = 0; x < len; x++)
+ {
+ const s16 tmp = matrix[len * y + x];
+
+ // Correct the color range using the scale matrix hardware.
+ // For example when converting RGB555 to RGB8 LgyFb lazily shifts the 5 bits up
+ // so 0b00011111 becomes 0b11111000. This creates wrong spacing between colors.
+ // TODO: What is the "+ 8" good for?
+ REG_LGYFB_TOP_V_MATRIX[y][x] = tmp * 0xFF / 0xF8 + 8;
+ REG_LGYFB_TOP_H_MATRIX[y][x] = tmp + 8;
+ }
+ }
+}
+
+void LGYFB_init(void)
+{
+ if(DMA330_run(0, program)) return;
+
+ //REG_LGYFB_TOP_SIZE = LGYFB_SIZE(240u, 160u);
+ REG_LGYFB_TOP_SIZE = LGYFB_SIZE(360u, 240u);
+ REG_LGYFB_TOP_STAT = LGYFB_IRQ_MASK;
+ REG_LGYFB_TOP_IRQ = 0;
+ REG_LGYFB_TOP_ALPHA = 0xFF;
+
+ /*
+ * Limitations:
+ * First pattern bit must be 1 and last 0 (for V-scale) or it loses sync with the DS/GBA input.
+ *
+ * Matrix ranges:
+ * in[-3] -1024-1023 (0xFC00-0x03FF)
+ * in[-2] -4096-4095 (0xF000-0x0FFF)
+ * in[-1] -32768-32767 (0x8000-0x7FFF)
+ * in[0] -32768-32767 (0x8000-0x7FFF)
+ * in[1] -4096-4095 (0xF000-0x0FFF)
+ * in[2] -1024-1023 (0xFC00-0x03FF)
+ *
+ * Note: At scanline start the in FIFO is all filled with the first pixel.
+ */
+ static const s16 scaleMatrix[6 * 6] =
+ {
+ // Original from AGB_FIRM.
+ /* 0, 0, 0, 0, 0, 0, // in[-3]
+ 0, 0, 0, 0, 0, 0, // in[-2]
+ 0, 0x2000, 0x4000, 0, 0x2000, 0x4000, // in[-1]
+ 0x4000, 0x2000, 0, 0x4000, 0x2000, 0, // in[0]
+ 0, 0, 0, 0, 0, 0, // in[1]
+ 0, 0, 0, 0, 0, 0*/ // in[2]
+ // out[0] out[1] out[2] out[3] out[4] out[5] out[6] out[7]
+
+ // Razor sharp (pixel duplication).
+ /* 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0x4000, 0, 0, 0x4000,
+ 0x4000, 0x4000, 0, 0x4000, 0x4000, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0*/
+
+ // Sharp interpolated.
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0x2000, 0, 0, 0x2000,
+ 0x4000, 0x4000, 0x2000, 0x4000, 0x4000, 0x2000,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0
+ };
+ setScaleMatrixTop(6, 0b00011011, scaleMatrix);
+
+ // With RGB8 output solid red and blue are converted to 0xF8 and green to 0xFA.
+ // The green bias exists on the whole range of green colors.
+ // Some results:
+ // RGBA8: Same as RGB8 but with useless alpha component.
+ // RGB8: Observed best format. Invisible dithering and best color accuracy.
+ // RGB565: A little visible dithering. Good color accuracy.
+ // RGB5551: Lots of visible dithering. Good color accuracy (a little worse than 565).
+ REG_LGYFB_TOP_CNT = LGYFB_DMA_E | LGYFB_OUT_SWIZZLE | LGYFB_OUT_FMT_8880 |
+ LGYFB_HSCALE_E | LGYFB_VSCALE_E | LGYFB_ENABLE;
+
+ KEvent tmp = createEvent(true);
+ g_frameReadyEvent = tmp;
+ IRQ_registerIsr(IRQ_CDMA_EVENT0, 13, 0, lgyFbDmaIrqHandler);
+ g_lgyFbTask = createTask(0x800, 3, lgyFbTask, tmp);
+
+
+ const double inGamma = 4.0;
+ const double outGamma = 2.2;
+ //const double contrast = .74851331406341291833644689906823; // GBA
+ //const double brightness = .25148668593658708166355310093177; // GBA
+ const double contrast = 1.0; // No-op
+ const double brightness = 0.0; // No-op
+ //REG_LCD_PDC0_GTBL_IDX = 0;
+ for(u32 i = 0; i < 256; i++)
+ {
+ // Credits for this algo go to Extrems.
+ // Originally from Game Boy Interface Standard Edition for the Game Cube.
+ //const u32 x = (i & ~7u) | i>>5;
+ u32 res = pow(pow(contrast, inGamma) * pow((double)i / 255.0f + brightness / contrast, inGamma),
+ 1.0 / outGamma) * 255.0f;
+ if(res > 255) res = 255;
+
+ // Same adjustment for red/green/blue.
+ REG_LCD_PDC0_GTBL_FIFO = res<<16 | res<<8 | res;
}
}
diff --git a/source/arm11/main.c b/source/arm11/main.c
index b07e3b7..60303d9 100644
--- a/source/arm11/main.c
+++ b/source/arm11/main.c
@@ -29,6 +29,7 @@
#include "fs.h"
#include "arm11/filebrowser.h"
#include "arm.h"
+#include "kernel.h"
@@ -64,7 +65,7 @@ int main(void)
LGY_handleEvents();
- __wfi();
+ yieldTask();
} while(1);
}
diff --git a/source/arm11/system.c b/source/arm11/system.c
index 24f44b3..ef9d868 100644
--- a/source/arm11/system.c
+++ b/source/arm11/system.c
@@ -21,6 +21,7 @@
#include "arm11/start.h"
#include "arm11/hardware/interrupt.h"
#include "arm11/hardware/timer.h"
+#include "kernel.h"
#include "hardware/corelink_dma-330.h"
#include "arm11/hardware/i2c.h"
#include "arm11/hardware/mcu.h"
@@ -37,6 +38,7 @@ void WEAK __systemInit(void)
if(!__getCpuId()) // Core 0
{
+ kernelInit(2);
DMA330_init();
I2C_init();
hidInit();