First public release of my tiny kernel.

This commit is contained in:
profi200 2020-09-05 17:20:38 +02:00
parent b7441659ca
commit f028745f52
No known key found for this signature in database
GPG Key ID: 17B42AE5911139F3
30 changed files with 1967 additions and 318 deletions

View File

@ -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 :=

View File

@ -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

View File

@ -32,7 +32,6 @@
void LGYFB_init(void);
void LGYFB_processFrame(void);
void LGYFB_deinit(void);
#ifndef NDEBUG

87
kernel/include/event.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <stdbool.h>
#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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
// 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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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

View File

@ -0,0 +1,59 @@
#pragma once
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#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);

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
// 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);

View File

@ -0,0 +1,103 @@
#pragma once
// Based on https://github.com/torvalds/linux/blob/master/include/linux/list.h
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#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))

View File

@ -0,0 +1,46 @@
#pragma once
#include <stddef.h>
#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);

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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");
}

View File

@ -0,0 +1,5 @@
#pragma once
#define LIKELY(expr) __builtin_expect((expr), true)
#define UNLIKELY(expr) __builtin_expect((expr), false)

78
kernel/include/kernel.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <stddef.h>
#include <stdint.h>
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

68
kernel/include/mutex.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <stdint.h>
#include <stdbool.h>
#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

45
kernel/include/timer.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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

119
kernel/source/event.c Normal file
View File

@ -0,0 +1,119 @@
#include <stdbool.h>
#include <stdlib.h>
#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();
}

245
kernel/source/kernel.c Normal file
View File

@ -0,0 +1,245 @@
#include <stdlib.h>
#include <stdnoreturn.h>
#include <string.h>
#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<<priority;
g_numTasks++;
kernelUnlock();
return newT;
}
// TODO: setTaskPriority().
void yieldTask(void)
{
kernelLock();
scheduler(TASK_STATE_RUNNING);
}
void taskExit(void)
{
kernelLock();
scheduler(TASK_STATE_DEAD);
while(1); // TODO: panic?
}
/*
* Internal functions.
*/
const TaskCb* getCurrentTask(void)
{
return g_curTask;
}
// The wait queue and scheduler functions automatically unlock the kernel lock
// and expect to be called with locked lock.
KRes waitQueueBlock(ListNode *waitQueue)
{
listPush(waitQueue, &g_curTask->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<<curPrio;
}
do
{
TaskCb *task = LIST_ENTRY(listPopHead(waitQueue), TaskCb, node);
readyBitmap |= 1u<<task->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<<curPrio;
}
else if(UNLIKELY(curTaskState == TASK_STATE_DEAD))
{
g_curDeadTask = curTask;
g_numTasks--;
}
TaskCb *newTask = LIST_ENTRY(listPop(&runQueues[readyPrio]), TaskCb, node);
if(listEmpty(&runQueues[readyPrio])) readyBitmap &= ~(1u<<readyPrio);
g_readyBitmap = readyBitmap;
TaskCb *oldTask = curTask;
g_curTask = newTask;
const KRes res = newTask->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);
}

101
kernel/source/kmemcpy_set.s Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
@ 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

116
kernel/source/ktimer.c Normal file
View File

@ -0,0 +1,116 @@
#include <stdbool.h>
#include <stdlib.h>
#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);
}*/

93
kernel/source/mutex.c Normal file
View File

@ -0,0 +1,93 @@
#include <stdlib.h>
#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;
}

84
kernel/source/semaphore.c Normal file
View File

@ -0,0 +1,84 @@
#include <stdbool.h>
#include <stdlib.h>
#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();
}

44
kernel/source/slabheap.c Normal file
View File

@ -0,0 +1,44 @@
#include <stddef.h>
#include <stdlib.h>
#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);
}

View File

@ -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)

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

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