First public release of my tiny kernel.
This commit is contained in:
parent
b7441659ca
commit
f028745f52
|
@ -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 :=
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -32,7 +32,6 @@
|
|||
|
||||
|
||||
void LGYFB_init(void);
|
||||
void LGYFB_processFrame(void);
|
||||
void LGYFB_deinit(void);
|
||||
|
||||
#ifndef NDEBUG
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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);
|
|
@ -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);
|
|
@ -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))
|
|
@ -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);
|
|
@ -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");
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
#pragma once
|
||||
|
||||
|
||||
#define LIKELY(expr) __builtin_expect((expr), true)
|
||||
#define UNLIKELY(expr) __builtin_expect((expr), false)
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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
|
|
@ -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);
|
||||
}*/
|
|
@ -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;
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in New Issue