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();