essence-os/kernel/module.h

1036 lines
42 KiB
C

// This file is part of the Essence operating system.
// It is released under the terms of the MIT license -- see LICENSE.md.
// Written by: nakst.
// TODO Include more functions and definitions.
// TODO Add K- prefix to more identifiers.
#define KERNEL
#ifndef K_PRIVATE
#define K_PRIVATE private:
#endif
#include <stdint.h>
#include <stddef.h>
#include <stdarg.h>
#include <stdbool.h>
#include <mmintrin.h>
#include <xmmintrin.h>
#include <emmintrin.h>
#define alloca __builtin_alloca
#define KERNEL_VERSION (1)
typedef uint64_t (*KGetKernelVersionCallback)();
#ifdef KERNEL_MODULE
extern "C" uint64_t GetKernelVersion() { return KERNEL_VERSION; }
#endif
// ---------------------------------------------------------------------------------------------------------------
// API header.
// ---------------------------------------------------------------------------------------------------------------
#define ES_FORWARD
#include <essence.h>
// ---------------------------------------------------------------------------------------------------------------
// Global defines.
// ---------------------------------------------------------------------------------------------------------------
#define K_VERSION (0x010000)
#define K_USER_BUFFER // Used to mark pointers that (might) point to non-kernel memory.
#define K_MAX_PROCESSORS (256) // See cpu_local_storage_size in x86_64.s.
#define K_MAX_PATH (4096)
#define K_ACCESS_IMPLEMENTATION_DEFINED (2)
// ---------------------------------------------------------------------------------------------------------------
// Heap allocations.
// ---------------------------------------------------------------------------------------------------------------
struct EsHeap;
extern EsHeap heapCore;
extern EsHeap heapFixed;
#define K_CORE (&heapCore)
#define K_FIXED (&heapFixed)
#define K_PAGED (&heapFixed)
void *EsHeapAllocate(size_t size, bool zeroMemory, EsHeap *kernelHeap);
void EsHeapFree(void *address, size_t expectedSize, EsHeap *kernelHeap);
void *EsHeapReallocate(void *oldAddress, size_t newAllocationSize, bool zeroNewSpace, EsHeap *_heap);
// ---------------------------------------------------------------------------------------------------------------
// Debug output.
// ---------------------------------------------------------------------------------------------------------------
enum KLogLevel {
LOG_VERBOSE,
LOG_INFO,
LOG_ERROR,
};
void KernelLog(KLogLevel level, const char *subsystem, const char *event, const char *format, ...);
void KernelPanic(const char *format, ...);
void EsPrint(const char *format, ...);
void StartDebugOutput(); // Causes the output to be directly displayed on the screen. For debugging only.
#define EsPanic KernelPanic
// ---------------------------------------------------------------------------------------------------------------
// IRQs.
// ---------------------------------------------------------------------------------------------------------------
typedef bool (*KIRQHandler)(uintptr_t interruptIndex /* tag for MSI */, void *context);
// Interrupts are active high and level triggered, unless overridden by the ACPI MADT table.
bool KRegisterIRQ(intptr_t interruptIndex, KIRQHandler handler, void *context, const char *cOwnerName,
struct KPCIDevice *pciDevice = nullptr /* do not use; see KPCIDevice::EnableSingleInterrupt */);
struct KMSIInformation {
// Both fields are zeroed if the MSI could not be registered.
uintptr_t address;
uintptr_t data;
uintptr_t tag;
};
KMSIInformation KRegisterMSI(KIRQHandler handler, void *context, const char *cOwnerName);
void KUnregisterMSI(uintptr_t tag);
// ---------------------------------------------------------------------------------------------------------------
// Common data types, algorithms and things.
// ---------------------------------------------------------------------------------------------------------------
#define SHARED_MATH_WANT_BASIC_UTILITIES
#include <shared/math.cpp>
#ifndef K_IN_CORE_KERNEL
#define SHARED_DEFINITIONS_ONLY
#define ARRAY_DEFINITIONS_ONLY
#endif
#include <shared/unicode.cpp>
#include <shared/linked_list.cpp>
#include <shared/array.cpp>
uint32_t CalculateCRC32(const void *_buffer, size_t length, uint32_t carry);
uint64_t CalculateCRC64(const void *_buffer, size_t length, uint64_t carry);
// ---------------------------------------------------------------------------------------------------------------
// Processor IO.
// ---------------------------------------------------------------------------------------------------------------
extern "C" void ProcessorOut8(uint16_t port, uint8_t value);
extern "C" uint8_t ProcessorIn8(uint16_t port);
extern "C" void ProcessorOut16(uint16_t port, uint16_t value);
extern "C" uint16_t ProcessorIn16(uint16_t port);
extern "C" void ProcessorOut32(uint16_t port, uint32_t value);
extern "C" uint32_t ProcessorIn32(uint16_t port);
// ---------------------------------------------------------------------------------------------------------------
// Async tasks.
// ---------------------------------------------------------------------------------------------------------------
// Async tasks are executed on the same processor that registered it.
// They can be registered with interrupts disabled (e.g. in IRQ handlers).
// They are executed in the order they were registered.
// They can acquire mutexes, but cannot perform IO.
typedef void (*KAsyncTaskCallback)(struct KAsyncTask *task);
struct KAsyncTask {
SimpleList item;
KAsyncTaskCallback callback;
};
void KRegisterAsyncTask(KAsyncTask *task, KAsyncTaskCallback callback);
// ---------------------------------------------------------------------------------------------------------------
// Kernel core.
// ---------------------------------------------------------------------------------------------------------------
uint64_t KGetTimeInMs(); // Scheduler time.
EsUniqueIdentifier KGetBootIdentifier();
size_t KGetCPUCount();
struct CPULocalStorage *KGetCPULocal(uintptr_t index);
uint64_t KCPUCurrentID();
bool KInIRQ();
void KSwitchThreadAfterIRQ();
void KDebugKeyPressed();
#if defined(ES_ARCH_X86_32) || defined(ES_ARCH_X86_64)
void KPS2SafeToInitialise();
#endif
struct KTimeout {
uint64_t end;
inline KTimeout(int ms) { end = KGetTimeInMs() + ms; }
inline bool Hit() { return KGetTimeInMs() >= end; }
};
enum KernelObjectType : uint32_t {
COULD_NOT_RESOLVE_HANDLE = 0x00000000,
KERNEL_OBJECT_NONE = 0x80000000,
KERNEL_OBJECT_PROCESS = 0x00000001, // A process.
KERNEL_OBJECT_THREAD = 0x00000002, // A thread.
KERNEL_OBJECT_WINDOW = 0x00000004, // A window.
KERNEL_OBJECT_SHMEM = 0x00000008, // A region of shared memory.
KERNEL_OBJECT_NODE = 0x00000010, // A file system node (file or directory).
KERNEL_OBJECT_EVENT = 0x00000020, // A synchronisation event.
KERNEL_OBJECT_CONSTANT_BUFFER = 0x00000040, // A buffer of unmodifiable data stored in the kernel's address space.
#ifdef ENABLE_POSIX_SUBSYSTEM
KERNEL_OBJECT_POSIX_FD = 0x00000100, // A POSIX file descriptor, used in the POSIX subsystem.
#endif
KERNEL_OBJECT_PIPE = 0x00000200, // A pipe through which data can be sent between processes, blocking when full or empty.
KERNEL_OBJECT_EMBEDDED_WINDOW = 0x00000400, // An embedded window object, referencing its container Window.
KERNEL_OBJECT_CONNECTION = 0x00004000, // A network connection.
KERNEL_OBJECT_DEVICE = 0x00008000, // A device.
};
// TODO Rename to KObjectReference and KObjectDereference?
void CloseHandleToObject(void *object, KernelObjectType type, uint32_t flags = 0);
bool OpenHandleToObject(void *object, KernelObjectType type, uint32_t flags = 0);
// ---------------------------------------------------------------------------------------------------------------
// Module loading.
// ---------------------------------------------------------------------------------------------------------------
#define KModuleResolveSymbolCallback (const char *name, size_t nameBytes)
typedef void *(*KModuleResolveSymbolCallbackFunction) KModuleResolveSymbolCallback;
struct KModule {
const char *path;
size_t pathBytes;
KModuleResolveSymbolCallbackFunction resolveSymbol;
uint8_t *buffer;
};
struct KLoadedExecutable {
uintptr_t startAddress;
uintptr_t tlsImageStart;
uintptr_t tlsImageBytes;
uintptr_t tlsBytes; // All bytes after the image are to be zeroed.
bool isDesktop, isBundle;
};
EsError KLoadELF(struct KNode *node, KLoadedExecutable *executable);
EsError KLoadELFModule(KModule *module);
uintptr_t KFindSymbol(KModule *module, const char *name, size_t nameBytes);
// ---------------------------------------------------------------------------------------------------------------
// Synchronisation primitives.
// ---------------------------------------------------------------------------------------------------------------
struct KSpinlock { // Mutual exclusion. CPU-owned. Disables interrupts. The only synchronisation primitive that can be acquired with interrupts disabled.
K_PRIVATE
volatile uint8_t state, ownerCPU;
volatile bool interruptsEnabled;
#ifdef DEBUG_BUILD
struct Thread *volatile owner;
volatile uintptr_t acquireAddress, releaseAddress;
#endif
};
void KSpinlockAcquire(KSpinlock *spinlock);
void KSpinlockRelease(KSpinlock *spinlock, bool force = false);
void KSpinlockAssertLocked(KSpinlock *spinlock);
struct KMutex { // Mutual exclusion. Thread-owned.
K_PRIVATE
struct Thread *volatile owner;
#ifdef DEBUG_BUILD
uintptr_t acquireAddress, releaseAddress, id;
#endif
LinkedList<struct Thread> blockedThreads;
};
#ifdef DEBUG_BUILD
bool _KMutexAcquire(KMutex *mutex, const char *cMutexString, const char *cFile, int line);
void _KMutexRelease(KMutex *mutex, const char *cMutexString, const char *cFile, int line);
#define KMutexAcquire(mutex) _KMutexAcquire(mutex, #mutex, __FILE__, __LINE__)
#define KMutexRelease(mutex) _KMutexRelease(mutex, #mutex, __FILE__, __LINE__)
#else
bool KMutexAcquire(KMutex *mutex);
void KMutexRelease(KMutex *mutex);
#endif
void KMutexAssertLocked(KMutex *mutex);
struct KEvent { // Waiting and notifying. Can wait on multiple at once. Can be set and reset with interrupts disabled.
volatile bool autoReset; // This should be first field in the structure, so that the type of KEvent can be easily declared with {autoReset}.
volatile uintptr_t state; // TODO Change this to a bool?
K_PRIVATE
LinkedList<Thread> blockedThreads;
volatile size_t handles;
};
bool KEventSet(KEvent *event, bool maybeAlreadySet = false);
void KEventReset(KEvent *event);
bool KEventPoll(KEvent *event); // TODO Remove this! Currently it is only used by KAudioFillBuffersFromMixer.
bool KEventWait(KEvent *event, uint64_t timeoutMs = ES_WAIT_NO_TIMEOUT); // See KEventWaitMultiple to wait for multiple events. Returns false if the wait timed out.
struct KWriterLock { // One writer or many readers.
K_PRIVATE
LinkedList<Thread> blockedThreads;
volatile intptr_t state; // -1: exclusive; >0: shared owners.
#ifdef DEBUG_BUILD
volatile Thread *exclusiveOwner;
#endif
};
#define K_LOCK_EXCLUSIVE (true)
#define K_LOCK_SHARED (false)
bool KWriterLockTake(KWriterLock *lock, bool write, bool poll = false);
void KWriterLockReturn(KWriterLock *lock, bool write);
void KWriterLockConvertExclusiveToShared(KWriterLock *lock);
void KWriterLockAssertExclusive(KWriterLock *lock);
void KWriterLockAssertShared(KWriterLock *lock);
void KWriterLockAssertLocked(KWriterLock *lock);
struct KSemaphore { // Exclusion with a multiple units.
KEvent available;
volatile uintptr_t units;
K_PRIVATE
KMutex mutex; // TODO Make this a spinlock?
uintptr_t _custom;
uintptr_t lastTaken;
};
bool KSemaphoreTake(KSemaphore *semaphore, uintptr_t units = 1, uintptr_t timeoutMs = ES_WAIT_NO_TIMEOUT);
void KSemaphoreReturn(KSemaphore *semaphore, uintptr_t units = 1);
bool KSemaphorePoll(KSemaphore *semaphore);
void KSemaphoreSet(KSemaphore *semaphore, uintptr_t units = 1);
struct KTimer {
KEvent event;
KAsyncTask asyncTask;
K_PRIVATE
LinkedItem<KTimer> item;
uint64_t triggerTimeMs;
KAsyncTaskCallback callback;
EsGeneric argument;
};
void KTimerSet(KTimer *timer, uint64_t triggerInMs, KAsyncTaskCallback callback = nullptr, EsGeneric argument = 0);
void KTimerRemove(KTimer *timer); // Timers with callbacks cannot be removed (it'd race with async task delivery).
// ---------------------------------------------------------------------------------------------------------------
// Memory manager.
// ---------------------------------------------------------------------------------------------------------------
#define K_PAGE_BITS ES_PAGE_BITS
#define K_PAGE_SIZE ES_PAGE_SIZE
struct MMSpace;
MMSpace *MMGetKernelSpace();
MMSpace *MMGetCurrentProcessSpace();
#ifdef K_IN_CORE_KERNEL
// Memory spaces.
// kernelMMSpace - Whence the kernel allocates memory.
// coreMMSpace - Whence other memory managers allocate memory.
extern MMSpace _kernelMMSpace, _coreMMSpace;
#define kernelMMSpace (&_kernelMMSpace)
#define coreMMSpace (&_coreMMSpace)
#endif
#define MM_REGION_FIXED (0x01) // A region where all the physical pages are allocated up-front, and cannot be removed from the working set.
#define MM_REGION_NOT_CACHEABLE (0x02) // Do not cache the pages in the region.
#define MM_REGION_NO_COMMIT_TRACKING (0x04) // Page committing is manually tracked.
#define MM_REGION_READ_ONLY (0x08) // Generate page faults when written to.
#define MM_REGION_COPY_ON_WRITE (0x10) // Copy on write.
#define MM_REGION_WRITE_COMBINING (0x20) // Write combining caching is enabled. Incompatible with MM_REGION_NOT_CACHEABLE.
#define MM_REGION_EXECUTABLE (0x40)
#define MM_REGION_USER (0x80) // The application created it, and is therefore allowed to modify it.
// Limited by region type flags.
void *MMMapPhysical(MMSpace *space, uintptr_t address, size_t bytes, uint64_t caching);
void MMRemapPhysical(MMSpace *space, const void *virtualAddress, uintptr_t newPhysicalAddress); // Must be done with interrupts disabled; does not invalidate on other processors.
void *MMStandardAllocate(MMSpace *space, size_t bytes, uint32_t flags, void *baseAddress = nullptr, bool commitAll = true);
bool MMFree(MMSpace *space, void *address, size_t expectedSize = 0, bool userOnly = false);
void MMAllowWriteCombiningCaching(MMSpace *space, void *virtualAddress);
size_t MMGetRegionPageCount(MMSpace *space, void *virtualAddress);
uint64_t MMNumberOfUsablePhysicalPages();
// Returns 0 if not mapped. Rounds address down to nearest page.
uintptr_t MMArchTranslateAddress(MMSpace *space, uintptr_t virtualAddress, bool writeAccess = false /* if true, return 0 if address not writable */);
#define MM_PHYSICAL_ALLOCATE_CAN_FAIL (1 << 0) // Don't panic if the allocation fails.
#define MM_PHYSICAL_ALLOCATE_COMMIT_NOW (1 << 1) // Commit (fixed) the allocated pages.
#define MM_PHYSICAL_ALLOCATE_ZEROED (1 << 2) // Zero the pages.
#define MM_PHYSICAL_ALLOCATE_LOCK_ACQUIRED (1 << 3) // The page frame mutex is already acquired.
uintptr_t /* Returns physical address of first page, or 0 if none were available. */ MMPhysicalAllocate(unsigned flags,
uintptr_t count = 1 /* Number of contiguous pages to allocate. */,
uintptr_t align = 1 /* Alignment, in pages. */,
uintptr_t below = 0 /* Upper limit of physical address, in pages. E.g. for 32-bit pages only, pass (0x100000000 >> K_PAGE_BITS). */);
void MMPhysicalFree(uintptr_t page /* Physical address. */,
bool mutexAlreadyAcquired = false /* Internal use. Pass false. */,
size_t count = 1 /* Number of consecutive pages to free. */);
bool MMPhysicalAllocateAndMap(size_t sizeBytes, size_t alignmentBytes, size_t maximumBits, bool zeroed,
uint64_t caching, uint8_t **virtualAddress, uintptr_t *physicalAddress);
void MMPhysicalFreeAndUnmap(void *virtualAddress, uintptr_t physicalAddress);
#define MM_SHARED_ENTRY_PRESENT (1)
struct MMSharedRegion;
MMSharedRegion *MMSharedCreateRegion(size_t sizeBytes, bool fixed = false, uintptr_t below = 0 /* See fixed = true, passed to MMPhysicalAllocate. */);
uintptr_t MMSharedLookupPage(MMSharedRegion *region, uintptr_t pageIndex);
void *MMMapShared(MMSpace *space, MMSharedRegion *sharedRegion, uintptr_t offset, size_t bytes, uint32_t additionalFlags = ES_FLAGS_DEFAULT, void *baseAddresses = nullptr);
// Check that the range of physical memory is unusable.
// Panics on failure.
void MMCheckUnusable(uintptr_t physicalStart, size_t bytes);
typedef SimpleList MMObjectCacheItem;
struct MMObjectCache {
K_PRIVATE
KSpinlock lock; // Used instead of a mutex to keep accesses to the list lightweight.
SimpleList items;
size_t count;
bool (*trim)(MMObjectCache *cache); // Return true if an object was trimmed.
KWriterLock trimLock; // Open in shared access to trim the cache.
LinkedItem<MMObjectCache> item;
size_t averageObjectBytes;
};
void MMObjectCacheInsert(MMObjectCache *cache, MMObjectCacheItem *item);
void MMObjectCacheRemove(MMObjectCache *cache, MMObjectCacheItem *item, bool alreadyLocked = false);
MMObjectCacheItem *MMObjectCacheRemoveLRU(MMObjectCache *cache);
void MMObjectCacheRegister(MMObjectCache *cache, bool (*trimCallback)(MMObjectCache *), size_t averageObjectBytes);
void MMObjectCacheUnregister(MMObjectCache *cache);
void MMObjectCacheFlush(MMObjectCache *cache);
// ---------------------------------------------------------------------------------------------------------------
// Scheduler.
// ---------------------------------------------------------------------------------------------------------------
uint64_t KProcessCurrentID();
uint64_t KThreadCurrentID();
bool KThreadCreate(const char *cName, void (*startAddress)(uintptr_t), uintptr_t argument = 0);
extern "C" void KThreadTerminate(); // Terminates the current thread. Kernel threads can only be terminated by themselves.
void KYield();
uintptr_t KEventWaitMultiple(KEvent **events, size_t count);
struct KWorkGroup {
inline void Initialise() {
remaining = 1;
success = 1;
KEventReset(&event);
}
inline bool Wait() {
if (__sync_fetch_and_sub(&remaining, 1) != 1) {
KEventWait(&event);
}
if (remaining) {
KernelPanic("KWorkGroup::Wait - Expected remaining operations to be 0 after event set.\n");
}
return success ? true : false;
}
inline void Start() {
if (__sync_fetch_and_add(&remaining, 1) == 0) {
KernelPanic("KWorkGroup::Start - Could not start operation on completed dispatch group.\n");
}
}
inline void End(bool _success) {
if (!_success) {
success = false;
__sync_synchronize();
}
if (__sync_fetch_and_sub(&remaining, 1) == 1) {
KEventSet(&event);
}
}
K_PRIVATE
volatile uintptr_t remaining;
volatile uintptr_t success;
KEvent event;
};
// ---------------------------------------------------------------------------------------------------------------
// Device management.
// ---------------------------------------------------------------------------------------------------------------
struct KInstalledDriver {
char *name; // The name of the driver.
size_t nameBytes;
char *parent; // The name of the parent driver.
size_t parentBytes;
char *config; // The driver's configuration, taken from kernel/config.ini.
size_t configBytes;
bool builtin; // True if the driver is builtin to the kernel executable.
struct KDriver *loadedDriver; // The corresponding driver, if it has been loaded.
};
struct KDevice {
const char *cDebugName;
KDevice *parent; // The parent device.
Array<KDevice *, K_FIXED> children; // Child devices.
#define K_DEVICE_REMOVED (1 << 0)
#define K_DEVICE_VISIBLE_TO_USER (1 << 1) // A ES_MSG_DEVICE_CONNECTED message was sent to Desktop for this device.
uint8_t flags;
uint32_t handles;
EsDeviceType type;
EsObjectID objectID;
// These callbacks are called with the deviceTreeMutex locked, and are all optional.
void (*shutdown)(KDevice *device); // Called when the computer is about to shutdown.
void (*dumpState)(KDevice *device); // Dump the entire state of the device for debugging.
void (*removed)(KDevice *device); // Called when the device is removed. Called after the children are informed.
void (*destroy)(KDevice *device); // Called just before the device is destroyed.
void (*trackHandle)(KDevice *device, bool opened); // Called when a handle to the device with K_DEVICE_HANDLE_TRACKED is opened/closed.
#define K_DEVICE_HANDLE_TRACKED (1 << 0)
};
struct KDriver {
// Called when a new device the driver implements is attached.
// You should pass this the parent device to `KDeviceCreate`.
// The parent device pointer cannot be used after the function returns.
void (*attach)(KDevice *parent);
};
typedef bool KDriverIsImplementorCallback(KInstalledDriver *driver, KDevice *device); // Return true if the child driver implements the device.
// Searches for a driver that implements the device. Loads the driver if necessary (possibly asynchronously), and calls `attach`.
// Returns true if a matching driver was found; a maximum of one driver will be called.
// Example usage:
// - A bus driver finds a function with a connected device.
// - It creates a device for that function with KDeviceCreate, and sets the parent to the bus controller device.
// - It calls KDeviceAttach on the function device.
// - A suitable driver is found, which creates a device with that as its parent.
bool KDeviceAttach(KDevice *parentDevice, const char *cParentDriver /* match the parent field in the config */, KDriverIsImplementorCallback callback);
// Similar to KDeviceAttach, except it calls `attach` for every driver that matches the parent field.
void KDeviceAttachAll(KDevice *parentDevice, const char *cParentDriver);
// Similar to KDeviceAttach, except it calls `attach` only for the driver matching the provided name. Returns true if the driver was found.
bool KDeviceAttachByName(KDevice *parentDevice, const char *cName);
KDevice *KDeviceCreate(const char *cDebugName, KDevice *parent, size_t bytes /* must be at least the size of a KDevice */);
void KDeviceOpenHandle(KDevice *device, uint32_t handleFlags = ES_FLAGS_DEFAULT);
void KDeviceDestroy(KDevice *device); // Call if initialisation of the device failed. Otherwise use KDeviceCloseHandle.
void KDeviceCloseHandle(KDevice *device, uint32_t handleFlags = ES_FLAGS_DEFAULT); // The device creator is responsible for one handle after the creating it. The device is destroyed once all handles are closed.
void KDeviceRemoved(KDevice *device); // Call when a child device is removed. Must be called only once!
void KDeviceSendConnectedMessage(KDevice *device, EsDeviceType type, uint32_t handleFlags = ES_FLAGS_DEFAULT); // Send a message to Desktop to inform it the device was connected.
#include <bin/generated_code/kernel_config.h>
struct KClockDevice : KDevice {
EsError (*read)(KClockDevice *device, EsDateComponents *components, uint64_t *linearMs);
};
// ---------------------------------------------------------------------------------------------------------------
// Direct memory access.
// ---------------------------------------------------------------------------------------------------------------
struct KDMASegment {
uintptr_t physicalAddress;
size_t byteCount;
bool isLast;
};
struct KDMABuffer;
uintptr_t KDMABufferGetVirtualAddress(KDMABuffer *buffer); // TODO Temporary.
size_t KDMABufferGetTotalByteCount(KDMABuffer *buffer);
KDMASegment KDMABufferNextSegment(KDMABuffer *buffer, bool peek = false);
bool KDMABufferIsComplete(KDMABuffer *buffer); // Returns true if the end of the transfer buffer has been reached.
// ---------------------------------------------------------------------------------------------------------------
// Window manager.
// ---------------------------------------------------------------------------------------------------------------
struct KMouseUpdateData {
#define K_CURSOR_MOVEMENT_SCALE (0x100)
int32_t xMovement, yMovement;
bool xIsAbsolute, yIsAbsolute;
int32_t xFrom, xTo, yFrom, yTo;
int32_t xScroll, yScroll;
#define K_LEFT_BUTTON (1)
#define K_MIDDLE_BUTTON (2)
#define K_RIGHT_BUTTON (4)
uint32_t buttons;
};
struct KHIDevice : KDevice {
#define K_KEYBOARD_INDICATOR_NUM_LOCK (1 << 0)
#define K_KEYBOARD_INDICATOR_CAPS_LOCK (1 << 1)
#define K_KEYBOARD_INDICATOR_SCROLL_LOCK (1 << 2)
#define K_KEYBOARD_INDICATOR_COMPOSE (1 << 3)
#define K_KEYBOARD_INDICATOR_KANA (1 << 4)
#define K_KEYBOARD_INDICATOR_SHIFT (1 << 5)
void (*setKeyboardIndicators)(KHIDevice *device, uint32_t indicators);
};
void KMouseUpdate(const KMouseUpdateData *data);
#define K_SCANCODE_KEY_RELEASED (1 << 15)
#define K_SCANCODE_KEY_PRESSED (0 << 15)
void KKeyPress(uint32_t scancode);
void KKeyboardUpdate(uint16_t *keysDown, size_t keysDownCount);
uint64_t KGameControllerConnect();
void KGameControllerDisconnect(uint64_t id);
void KGameControllerUpdate(EsGameControllerState *state);
void KRegisterHIDevice(KHIDevice *device);
// ---------------------------------------------------------------------------------------------------------------
// Block devices.
// ---------------------------------------------------------------------------------------------------------------
#define K_ACCESS_READ (0)
#define K_ACCESS_WRITE (1)
struct KBlockDeviceAccessRequest {
struct KBlockDevice *device;
EsFileOffset offset;
size_t count;
int operation;
KDMABuffer *buffer;
uint64_t flags;
KWorkGroup *dispatchGroup;
};
typedef void (*KDeviceAccessCallbackFunction)(KBlockDeviceAccessRequest request);
struct KBlockDevice : KDevice {
KDeviceAccessCallbackFunction access; // Don't call directly; see KFileSystem::Access.
EsBlockDeviceInformation information;
size_t maxAccessSectorCount;
K_PRIVATE
uint8_t *signatureBlock; // Signature block. Only valid during fileSystem detection.
KMutex detectFileSystemMutex;
};
void FSPartitionDeviceCreate(KBlockDevice *parent, EsFileOffset offset, EsFileOffset sectorCount, uint32_t flags, const char *name, size_t nameBytes);
// ---------------------------------------------------------------------------------------------------------------
// PCI.
// ---------------------------------------------------------------------------------------------------------------
struct KPCIDevice : KDevice {
void WriteBAR8(uintptr_t index, uintptr_t offset, uint8_t value);
uint8_t ReadBAR8(uintptr_t index, uintptr_t offset);
void WriteBAR16(uintptr_t index, uintptr_t offset, uint16_t value);
uint16_t ReadBAR16(uintptr_t index, uintptr_t offset);
void WriteBAR32(uintptr_t index, uintptr_t offset, uint32_t value);
uint32_t ReadBAR32(uintptr_t index, uintptr_t offset);
void WriteBAR64(uintptr_t index, uintptr_t offset, uint64_t value);
uint64_t ReadBAR64(uintptr_t index, uintptr_t offset);
void WriteConfig8(uintptr_t offset, uint8_t value);
uint8_t ReadConfig8(uintptr_t offset);
void WriteConfig16(uintptr_t offset, uint16_t value);
uint16_t ReadConfig16(uintptr_t offset);
void WriteConfig32(uintptr_t offset, uint32_t value);
uint32_t ReadConfig32(uintptr_t offset);
#define K_PCI_FEATURE_BAR_0 (1 << 0)
#define K_PCI_FEATURE_BAR_1 (1 << 1)
#define K_PCI_FEATURE_BAR_2 (1 << 2)
#define K_PCI_FEATURE_BAR_3 (1 << 3)
#define K_PCI_FEATURE_BAR_4 (1 << 4)
#define K_PCI_FEATURE_BAR_5 (1 << 5)
#define K_PCI_FEATURE_INTERRUPTS (1 << 8)
#define K_PCI_FEATURE_BUSMASTERING_DMA (1 << 9)
#define K_PCI_FEATURE_MEMORY_SPACE_ACCESS (1 << 10)
#define K_PCI_FEATURE_IO_PORT_ACCESS (1 << 11)
bool EnableFeatures(uint64_t features);
bool EnableSingleInterrupt(KIRQHandler irqHandler, void *context, const char *cOwnerName);
uint32_t deviceID, subsystemID, domain;
uint8_t classCode, subclassCode, progIF;
uint8_t bus, slot, function;
uint8_t interruptPin, interruptLine;
uint8_t *baseAddressesVirtual[6];
uintptr_t baseAddressesPhysical[6];
size_t baseAddressesSizes[6];
uint32_t baseAddresses[6];
K_PRIVATE
bool EnableMSI(KIRQHandler irqHandler, void *context, const char *cOwnerName);
};
uint32_t KPCIReadConfig(uint8_t bus, uint8_t device, uint8_t function, uint8_t offset, int size = 32);
void KPCIWriteConfig(uint8_t bus, uint8_t device, uint8_t function, uint8_t offset, uint32_t value, int size = 32);
// ---------------------------------------------------------------------------------------------------------------
// USB.
// ---------------------------------------------------------------------------------------------------------------
struct KUSBDescriptorHeader {
uint8_t length;
uint8_t descriptorType;
} ES_STRUCT_PACKED;
struct KUSBConfigurationDescriptor : KUSBDescriptorHeader {
uint16_t totalLength;
uint8_t interfaceCount;
uint8_t configurationIndex;
uint8_t configurationString;
uint8_t attributes;
uint8_t maximumPower;
} ES_STRUCT_PACKED;
struct KUSBInterfaceDescriptor : KUSBDescriptorHeader {
uint8_t interfaceIndex;
uint8_t alternateSetting;
uint8_t endpointCount;
uint8_t interfaceClass;
uint8_t interfaceSubclass;
uint8_t interfaceProtocol;
uint8_t interfaceString;
} ES_STRUCT_PACKED;
struct KUSBDeviceDescriptor : KUSBDescriptorHeader {
uint16_t specificationVersion;
uint8_t deviceClass;
uint8_t deviceSubclass;
uint8_t deviceProtocol;
uint8_t maximumPacketSize;
uint16_t vendorID;
uint16_t productID;
uint16_t deviceVersion;
uint8_t manufacturerString;
uint8_t productString;
uint8_t serialNumberString;
uint8_t configurationCount;
} ES_STRUCT_PACKED;
struct KUSBEndpointCompanionDescriptor : KUSBDescriptorHeader {
uint8_t maxBurst;
uint8_t attributes;
uint16_t bytesPerInterval;
inline uint8_t GetMaximumStreams() { return attributes & 0x1F; }
inline bool HasISOCompanion() { return attributes & (1 << 7); }
} ES_STRUCT_PACKED;
struct KUSBEndpointIsochronousCompanionDescriptor : KUSBDescriptorHeader {
uint16_t reserved;
uint32_t bytesPerInterval;
} ES_STRUCT_PACKED;
struct KUSBEndpointDescriptor : KUSBDescriptorHeader {
uint8_t address;
uint8_t attributes;
uint16_t maximumPacketSize;
uint8_t pollInterval;
inline bool IsControl() { return (attributes & 3) == 0; }
inline bool IsIsochronous() { return (attributes & 3) == 1; }
inline bool IsBulk() { return (attributes & 3) == 2; }
inline bool IsInterrupt() { return (attributes & 3) == 3; }
inline bool IsInput() { return (address & 0x80); }
inline bool IsOutput() { return !(address & 0x80); }
inline uint8_t GetAddress() { return address & 0x0F; }
inline uint16_t GetMaximumPacketSize() { return maximumPacketSize & 0x7FF; }
} ES_STRUCT_PACKED;
typedef void (*KUSBTransferCallback)(ptrdiff_t bytesNotTransferred /* -1 if error */, EsGeneric context);
struct KUSBDevice : KDevice {
bool GetString(uint8_t index, char *buffer, size_t bufferBytes);
KUSBDescriptorHeader *GetCommonDescriptor(uint8_t type, uintptr_t index);
bool RunTransfer(KUSBEndpointDescriptor *endpoint, void *buffer, size_t bufferBytes, size_t *bytesNotTransferred /* if null, fails if positive */);
// Callbacks provided by the host controller:
// NOTE These do not provide mutual exclusion; you must ensure this manually.
bool (*controlTransfer)(KUSBDevice *device, uint8_t flags, uint8_t request, uint16_t value, uint16_t index,
void *buffer, uint16_t length, int operation /* K_ACCESS_READ/WRITE */, uint16_t *transferred);
bool (*queueTransfer)(KUSBDevice *device, KUSBEndpointDescriptor *endpoint, KUSBTransferCallback callback,
void *buffer, size_t bufferBytes, EsGeneric context);
bool (*selectConfigurationAndInterface)(KUSBDevice *device);
uint8_t *configurationDescriptors;
size_t configurationDescriptorsBytes;
uintptr_t selectedConfigurationOffset;
KUSBDeviceDescriptor deviceDescriptor;
KUSBConfigurationDescriptor configurationDescriptor;
KUSBInterfaceDescriptor interfaceDescriptor;
};
void KRegisterUSBDevice(KUSBDevice *device); // Takes ownership of the device's main handle.
// ---------------------------------------------------------------------------------------------------------------
// File systems.
// ---------------------------------------------------------------------------------------------------------------
struct CCSpace {
// A sorted list of the cached sections in the file.
// Maps offset -> physical address.
KMutex cachedSectionsMutex;
Array<struct CCCachedSection, K_CORE> cachedSections;
// A sorted list of the active sections.
// Maps offset -> virtual address.
KMutex activeSectionsMutex;
Array<struct CCActiveSectionReference, K_CORE> activeSections;
// Used by CCSpaceFlush.
KEvent writeComplete;
// Callbacks.
const struct CCSpaceCallbacks *callbacks;
};
struct KNodeMetadata {
// Metadata stored in the node's directory entry.
EsNodeType type;
bool removingNodeFromCache, removingThisFromCache;
EsFileOffset totalSize;
EsFileOffsetDifference directoryChildren; // ES_DIRECTORY_CHILDREN_UNKNOWN if not supported by the file system.
EsUniqueIdentifier contentType;
};
struct KNode {
void *driverNode;
K_PRIVATE
volatile size_t handles;
struct FSDirectoryEntry *directoryEntry;
struct KFileSystem *fileSystem;
uint64_t id;
KWriterLock writerLock; // Acquire before the parent's.
EsError error;
volatile uint32_t flags;
MMObjectCacheItem cacheItem;
};
struct KFileSystem : KDevice {
KBlockDevice *block; // Gives the sector size and count.
KNode *rootDirectory;
// Only use this for file system metadata that isn't cached in a Node.
// This must be used consistently, i.e. if you ever read a region cached, then you must always write that region cached, and vice versa.
#define FS_BLOCK_ACCESS_CACHED (1)
#define FS_BLOCK_ACCESS_SOFT_ERRORS (2)
// Access the block device. Returns true on success.
// Offset and count must be sector aligned. Buffer must be DWORD aligned.
EsError Access(EsFileOffset offset, size_t count, int operation, void *buffer, uint32_t flags, KWorkGroup *dispatchGroup = nullptr);
// Fill these fields in before registering the file system:
char name[64];
size_t nameBytes;
EsVolumeFlags volumeFlags; // Note: ES_VOLUME_READ_ONLY will be automatically set if you don't set the write() callback pointer.
size_t directoryEntryDataBytes; // The size of the driverData passed to FSDirectoryEntryFound and received in the load callback.
size_t nodeDataBytes; // The average bytes allocated by the driver per node (used for managing cache sizes).
EsFileOffsetDifference rootDirectoryInitialChildren;
EsFileOffset spaceTotal, spaceUsed;
EsUniqueIdentifier identifier;
size_t (*read) (KNode *node, void *buffer, EsFileOffset offset, EsFileOffset count);
size_t (*write) (KNode *node, const void *buffer, EsFileOffset offset, EsFileOffset count);
void (*sync) (KNode *directory, KNode *node); // TODO Error reporting?
EsError (*scan) (const char *name, size_t nameLength, KNode *directory); // Add the entry with FSDirectoryEntryFound.
EsError (*load) (KNode *directory, KNode *node, KNodeMetadata *metadata /* for if you need to update it */,
const void *entryData /* driverData passed to FSDirectoryEntryFound */);
EsFileOffset (*resize) (KNode *file, EsFileOffset newSize, EsError *error);
EsError (*create) (const char *name, size_t nameLength, EsNodeType type, KNode *parent, KNode *node, void *driverData);
EsError (*enumerate) (KNode *directory); // Add the entries with FSDirectoryEntryFound.
EsError (*remove) (KNode *directory, KNode *file);
EsError (*move) (KNode *oldDirectory, KNode *file, KNode *newDirectory, const char *newName, size_t newNameLength);
void (*close) (KNode *node);
void (*unmount) (KFileSystem *fileSystem);
// TODO Normalizing file names, for case-insensitive filesystems.
// void * (*normalize) (const char *name, size_t nameLength, size_t *resultLength);
// Internals.
KMutex moveMutex;
bool isBootFileSystem, unmounting;
EsUniqueIdentifier installationIdentifier;
volatile uint64_t totalHandleCount;
CCSpace cacheSpace;
MMObjectCache cachedDirectoryEntries, // Directory entries without a loaded node.
cachedNodes; // Nodes with no handles or directory entries.
};
EsError FSDirectoryEntryFound(KNode *parentDirectory, KNodeMetadata *metadata /* ignored if the entry is already cached */,
const void *driverData /* if update is false and the entry is already cached, this must match the previous driverData */,
const void *name, size_t nameBytes,
bool update /* set to true if you don't want to insert an new entry if it isn't already cached; returns ES_SUCCESS or ES_ERROR_FILE_DOES_NOT_EXIST only */,
KNode **node = nullptr /* set if scanning to immediately load; call FSNodeScanAndLoadComplete afterwards */);
// Call if you are scanning and used immediate load with FSDirectoryEntryFound.
void FSNodeScanAndLoadComplete(KNode *node, bool success);
// Equivalent to FSDirectoryEntryFound with update set to true,
// but lets you pass an arbitrary KNode instead of a [directory, file name] pair.
void FSNodeUpdateDriverData(KNode *node, const void *newDriverData);
bool FSFileSystemInitialise(KFileSystem *fileSystem); // Do not attempt to load the file system if this returns false; the file system will be destroyed.
// All these functions take ownership of the device's main handle.
void FSRegisterBlockDevice(KBlockDevice *blockDevice);
void FSRegisterFileSystem(KFileSystem *fileSystem);
void FSRegisterBootFileSystem(KFileSystem *fileSystem, EsUniqueIdentifier identifier);
#define K_SIGNATURE_BLOCK_SIZE (65536)
struct KNodeInformation {
EsError error;
KNode *node;
};
KNodeInformation FSNodeOpen(const char *path, size_t pathBytes, uint32_t flags, KNode *baseDirectory = nullptr);
EsFileOffset FSNodeGetTotalSize(KNode *node);
EsUniqueIdentifier FSNodeGetContentType(KNode *node);
char *FSNodeGetName(KNode *node, size_t *bytes); // For debugging use only.
// Do not pass memory-mapped buffers.
#define FS_FILE_ACCESS_USER_BUFFER_MAPPED (1 << 0)
ptrdiff_t FSFileReadSync(KNode *node, K_USER_BUFFER void *buffer, EsFileOffset offset, EsFileOffset bytes, uint32_t flags);
ptrdiff_t FSFileWriteSync(KNode *node, const K_USER_BUFFER void *buffer, EsFileOffset offset, EsFileOffset bytes, uint32_t flags);
// ---------------------------------------------------------------------------------------------------------------
// Graphics.
// ---------------------------------------------------------------------------------------------------------------
struct KGraphicsTarget : KDevice {
size_t screenWidth, screenHeight;
bool reducedColors; // Set to true if using less than 15 bit color.
void (*updateScreen)(K_USER_BUFFER const uint8_t *source, uint32_t sourceWidth, uint32_t sourceHeight, uint32_t sourceStride,
uint32_t destinationX, uint32_t destinationY);
void (*debugPutBlock)(uintptr_t x, uintptr_t y, bool toggle);
int (*debugPutData)(const uint8_t *data, size_t dataBytes); // Return the width used. dataBytes must be a multiple of 16.
void (*debugClearScreen)();
};
// TODO Locking for these functions?
void KRegisterGraphicsTarget(KGraphicsTarget *target);
bool KGraphicsIsTargetRegistered();
// Shared implementation of updating the screen for targets that use 32-bit linear buffers.
void GraphicsUpdateScreen32(K_USER_BUFFER const uint8_t *source, uint32_t sourceWidth, uint32_t sourceHeight, uint32_t sourceStride,
uint32_t destinationX, uint32_t destinationY,
uint32_t width, uint32_t height, uint32_t stride, volatile uint8_t *pixel);
void GraphicsUpdateScreen24(K_USER_BUFFER const uint8_t *_source, uint32_t sourceWidth, uint32_t sourceHeight, uint32_t sourceStride,
uint32_t destinationX, uint32_t destinationY,
uint32_t width, uint32_t height, uint32_t stride, volatile uint8_t *pixel);
void GraphicsDebugPutBlock32(uintptr_t x, uintptr_t y, bool toggle,
unsigned screenWidth, unsigned screenHeight, unsigned stride, volatile uint8_t *linearBuffer);
int GraphicsDebugPutData32(const uint8_t *data, size_t dataBytes,
unsigned screenWidth, unsigned screenHeight, unsigned stride, volatile uint8_t *linearBuffer);
void GraphicsDebugClearScreen32(unsigned screenWidth, unsigned screenHeight, unsigned stride, volatile uint8_t *linearBuffer);
// ---------------------------------------------------------------------------------------------------------------
// Networking.
// ---------------------------------------------------------------------------------------------------------------
struct KIPAddress {
uint8_t d[4];
};
struct KMACAddress {
uint8_t d[6];
};
struct NetTask {
void (*callback)(NetTask *task, void *receivedData);
struct NetInterface *interface;
uint16_t index;
int16_t error;
uint8_t step;
bool completed;
};
struct NetAddressSetupTask : NetTask {
uint32_t dhcpTransactionID;
bool changedState;
};
struct NetInterface : KDevice {
KIPAddress ipAddress;
// Set by driver before registering:
bool (*transmit)(NetInterface *self, void *dataVirtual, uintptr_t dataPhysical, size_t dataBytes);
union {
KMACAddress macAddress;
uint64_t macAddress64;
};
// Internals:
K_PRIVATE
SimpleList item;
NetAddressSetupTask addressSetupTask;
Array<struct ARPEntry, K_FIXED> arpTable;
Array<struct ARPRequest, K_FIXED> arpRequests;
KWriterLock arpTableLock;
// Changing the connection status and cancelling packets requires exclusive access.
// NetTaskBegin and NetInterfaceReceive (and hence all NetTask callbacks) run with shared access.
KWriterLock connectionLock;
bool connected, hasIP;
uint16_t ipIdentification;
KIPAddress serverIdentifier;
KIPAddress dnsServerIP;
KIPAddress routerIP;
};
enum NetPacketType {
NET_PACKET_ETHERNET,
};
void NetTransmitBufferReturn(void *data); // Once a driver is finished with a transmit buffer, it should return it here. If the driver returns false from the transmit callback, then the driver must *not* return the buffer.
void NetTaskBegin(NetTask *task);
void NetTaskComplete(NetTask *task, EsError error);
void KRegisterNetInterface(NetInterface *interface);
void NetInterfaceReceive(NetInterface *interface, const uint8_t *data, size_t dataBytes, NetPacketType packetType); // NOTE Currently this can be only called on one thread for each NetInterface. (This restriction will hopefully be removed soon.)
void NetInterfaceSetConnected(NetInterface *interface, bool connected); // NOTE This shouldn't be called by more than one thread.
void NetInterfaceShutdown(NetInterface *interface); // NOTE This doesn't do any disconnecting/cancelling of tasks. Currently it only sends a DHCP request to release the IP address, and is expected to be called at the final stages of system shutdown.
// ---------------------------------------------------------------------------------------------------------------
// ACPI.
// ---------------------------------------------------------------------------------------------------------------
struct KACPIObject;
typedef void (*KACPINotificationHandler)(KACPIObject *object, uint32_t value, EsGeneric context);
EsError KACPIObjectEvaluateInteger(KACPIObject *object, const char *pathName, uint64_t *_integer);
EsError KACPIObjectEvaluateMethodWithInteger(KACPIObject *object, const char *pathName, uint64_t integer);
EsError KACPIObjectSetDeviceNotificationHandler(KACPIObject *object, KACPINotificationHandler handler, EsGeneric context);