// 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.
};

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;

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

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