440 lines
17 KiB
C
440 lines
17 KiB
C
// Kernel surface storage. This file owns shared backing frames, mappings, commit generations, and privileged presentation.
|
|
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
#include <string.h>
|
|
#include <kernel/ke_surface.h>
|
|
#include <kernel/ke_process.h>
|
|
#include <kernel/ke_mutex.h>
|
|
#include <kernel/ke_event.h>
|
|
#include <kernel/ke_spinlock.h>
|
|
#include <kernel/ke_display.h>
|
|
#include <kernel/mm_virt.h>
|
|
#include <kernel/mm_pfn.h>
|
|
#include <kernel/mm_pool.h>
|
|
#include <ntlite/syscall.h>
|
|
|
|
typedef struct KE_SURFACE {
|
|
OBJECT Header;
|
|
uint32_t Width;
|
|
uint32_t Height;
|
|
uint32_t PitchBytes;
|
|
uint32_t Format;
|
|
uint32_t Flags;
|
|
size_t PayloadSize;
|
|
size_t PageCount;
|
|
bool StatsRegistered;
|
|
bool LastDamageValid;
|
|
uint16_t Reserved;
|
|
uint32_t Reserved2;
|
|
uint64_t MapCount;
|
|
uint64_t ContentGeneration;
|
|
uint64_t CommitCount;
|
|
uint64_t CommitRectCount;
|
|
uint64_t CommitClippedRectCount;
|
|
uint64_t CommitEmptyClipCount;
|
|
DISPLAY_RECT LastDamageBounds;
|
|
PHYSICAL_ADDRESS* Frames;
|
|
MM_KERNEL_VIEW* KernelView;
|
|
void* KernelBase;
|
|
EVENT* CommitEvent;
|
|
MUTEX* Lock;
|
|
} KE_SURFACE;
|
|
|
|
static SPINLOCK gSurfaceStatsLock = SPINLOCK_INIT;
|
|
static SYS_SURFACE_STATS gSurfaceStats;
|
|
|
|
// Multiplies two sizes while rejecting overflow.
|
|
static bool KiSurfaceMulSize(size_t a, size_t b, size_t* out) {
|
|
if (!out || (a != 0 && b > SIZE_MAX / a)) return false;
|
|
*out = a * b;
|
|
return true;
|
|
}
|
|
|
|
// Aligns one size upward while rejecting wraparound.
|
|
static size_t KiSurfaceAlignUp(size_t value, size_t alignment) {
|
|
if (!alignment || (alignment & (alignment - 1u))) return 0;
|
|
size_t mask = alignment - 1u;
|
|
if (value > SIZE_MAX - mask) return 0;
|
|
return (value + mask) & ~mask;
|
|
}
|
|
|
|
// Converts a generic object reference to a surface only when its type is correct.
|
|
static KE_SURFACE* KiSurfaceFromObject(OBJECT* object) {
|
|
if (!object || object->Type != ObjectTypeSurface) return (KE_SURFACE*)0;
|
|
return CONTAINING_RECORD(object, KE_SURFACE, Header);
|
|
}
|
|
|
|
// Registers a surface in the global telemetry snapshot.
|
|
static void KiSurfaceStatsRegister(KE_SURFACE* surface) {
|
|
if (!surface || surface->StatsRegistered) return;
|
|
uintptr_t irq = KeAcquireSpinlock(&gSurfaceStatsLock);
|
|
gSurfaceStats.CreatedSurfaces++;
|
|
gSurfaceStats.LiveSurfaces++;
|
|
gSurfaceStats.LivePages += surface->PageCount;
|
|
gSurfaceStats.LiveBytes += surface->PayloadSize;
|
|
surface->StatsRegistered = true;
|
|
KeReleaseSpinlock(&gSurfaceStatsLock, irq);
|
|
}
|
|
|
|
// Removes a surface from the global telemetry snapshot.
|
|
static void KiSurfaceStatsUnregister(KE_SURFACE* surface) {
|
|
if (!surface || !surface->StatsRegistered) return;
|
|
uintptr_t irq = KeAcquireSpinlock(&gSurfaceStatsLock);
|
|
gSurfaceStats.DestroyedSurfaces++;
|
|
if (gSurfaceStats.LiveSurfaces) gSurfaceStats.LiveSurfaces--;
|
|
gSurfaceStats.LivePages = gSurfaceStats.LivePages >= surface->PageCount
|
|
? gSurfaceStats.LivePages - surface->PageCount : 0;
|
|
gSurfaceStats.LiveBytes = gSurfaceStats.LiveBytes >= surface->PayloadSize
|
|
? gSurfaceStats.LiveBytes - surface->PayloadSize : 0;
|
|
surface->StatsRegistered = false;
|
|
KeReleaseSpinlock(&gSurfaceStatsLock, irq);
|
|
}
|
|
|
|
// Records one successful surface mapping operation.
|
|
static void KiSurfaceStatsRecordMap(KE_SURFACE* surface) {
|
|
uintptr_t irq = KeAcquireSpinlock(&gSurfaceStatsLock);
|
|
surface->MapCount++;
|
|
gSurfaceStats.MapCalls++;
|
|
KeReleaseSpinlock(&gSurfaceStatsLock, irq);
|
|
}
|
|
|
|
// Records one commit after rectangle validation and clipping.
|
|
static void KiSurfaceStatsRecordCommit(uint64_t inputRects, uint64_t clippedRects, uint64_t emptyClips) {
|
|
uintptr_t irq = KeAcquireSpinlock(&gSurfaceStatsLock);
|
|
gSurfaceStats.CommitCalls++;
|
|
gSurfaceStats.CommitRectInputs += inputRects;
|
|
gSurfaceStats.CommitClippedRects += clippedRects;
|
|
gSurfaceStats.CommitEmptyClips += emptyClips;
|
|
KeReleaseSpinlock(&gSurfaceStatsLock, irq);
|
|
}
|
|
|
|
// Records one presentation performed through DisplayManager authority.
|
|
static void KiSurfaceStatsRecordPresent(uint64_t bytesCopied) {
|
|
uintptr_t irq = KeAcquireSpinlock(&gSurfaceStatsLock);
|
|
gSurfaceStats.PhysicalPresentCalls++;
|
|
gSurfaceStats.PhysicalPresentBytesCopied += bytesCopied;
|
|
KeReleaseSpinlock(&gSurfaceStatsLock, irq);
|
|
}
|
|
|
|
// Releases surface storage. User VADs retain their own backing-frame references until unmapped.
|
|
static void KiSurfaceDestroy(OBJECT* object) {
|
|
KE_SURFACE* surface = KiSurfaceFromObject(object);
|
|
if (!surface) return;
|
|
|
|
KiSurfaceStatsUnregister(surface);
|
|
if (surface->KernelView) {
|
|
MmDestroyKernelView(surface->KernelView);
|
|
surface->KernelView = (MM_KERNEL_VIEW*)0;
|
|
surface->KernelBase = (void*)0;
|
|
}
|
|
if (surface->CommitEvent) {
|
|
ObDereferenceObject(&surface->CommitEvent->Header);
|
|
surface->CommitEvent = (EVENT*)0;
|
|
}
|
|
if (surface->Lock) {
|
|
ObDereferenceObject(&surface->Lock->Header);
|
|
surface->Lock = (MUTEX*)0;
|
|
}
|
|
if (surface->Frames) {
|
|
for (size_t i = 0; i < surface->PageCount; ++i)
|
|
if (surface->Frames[i]) (void)MmPfnRelease(surface->Frames[i]);
|
|
MmFreePool(surface->Frames);
|
|
surface->Frames = (PHYSICAL_ADDRESS*)0;
|
|
}
|
|
}
|
|
|
|
// Clips one rectangle to the surface extent.
|
|
static bool KiSurfaceClipRect(const KE_SURFACE* surface, const DISPLAY_RECT* input, DISPLAY_RECT* output) {
|
|
if (!surface || !output) return false;
|
|
DISPLAY_RECT r;
|
|
if (input) {
|
|
r = *input;
|
|
} else {
|
|
r.X = 0;
|
|
r.Y = 0;
|
|
r.W = (int32_t)surface->Width;
|
|
r.H = (int32_t)surface->Height;
|
|
}
|
|
if (r.W <= 0 || r.H <= 0) return false;
|
|
|
|
int64_t x0 = r.X;
|
|
int64_t y0 = r.Y;
|
|
int64_t x1 = x0 + r.W;
|
|
int64_t y1 = y0 + r.H;
|
|
if (x0 < 0) x0 = 0;
|
|
if (y0 < 0) y0 = 0;
|
|
if (x1 > surface->Width) x1 = surface->Width;
|
|
if (y1 > surface->Height) y1 = surface->Height;
|
|
if (x0 >= x1 || y0 >= y1) return false;
|
|
|
|
output->X = (int32_t)x0;
|
|
output->Y = (int32_t)y0;
|
|
output->W = (int32_t)(x1 - x0);
|
|
output->H = (int32_t)(y1 - y0);
|
|
return true;
|
|
}
|
|
|
|
// Expands one damage bound to include another clipped rectangle.
|
|
static void KiSurfaceUnionRect(DISPLAY_RECT* bounds, const DISPLAY_RECT* rect) {
|
|
int32_t x0 = bounds->X < rect->X ? bounds->X : rect->X;
|
|
int32_t y0 = bounds->Y < rect->Y ? bounds->Y : rect->Y;
|
|
int32_t bx1 = bounds->X + bounds->W;
|
|
int32_t by1 = bounds->Y + bounds->H;
|
|
int32_t rx1 = rect->X + rect->W;
|
|
int32_t ry1 = rect->Y + rect->H;
|
|
int32_t x1 = bx1 > rx1 ? bx1 : rx1;
|
|
int32_t y1 = by1 > ry1 ? by1 : ry1;
|
|
bounds->X = x0;
|
|
bounds->Y = y0;
|
|
bounds->W = x1 - x0;
|
|
bounds->H = y1 - y0;
|
|
}
|
|
|
|
// Clips one kernel-captured rectangle list. A zero count means the whole surface.
|
|
static KSTATUS KiSurfaceCaptureRects(KE_SURFACE* surface, const DISPLAY_RECT* rects, uint32_t rectCount,
|
|
DISPLAY_RECT* clippedOut, uint32_t* clippedCountOut,
|
|
uint64_t* emptyOut) {
|
|
if (!surface || !clippedOut || !clippedCountOut || !emptyOut)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
if (rectCount > SYS_SURFACE_MAX_RECTS || (rectCount && !rects))
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
DISPLAY_RECT whole = {0, 0, (int32_t)surface->Width, (int32_t)surface->Height};
|
|
if (!rectCount) {
|
|
rects = &whole;
|
|
rectCount = 1;
|
|
}
|
|
|
|
uint32_t clippedCount = 0;
|
|
uint64_t empty = 0;
|
|
for (uint32_t i = 0; i < rectCount; ++i) {
|
|
DISPLAY_RECT clipped;
|
|
if (!KiSurfaceClipRect(surface, &rects[i], &clipped)) {
|
|
empty++;
|
|
continue;
|
|
}
|
|
clippedOut[clippedCount++] = clipped;
|
|
}
|
|
*clippedCountOut = clippedCount;
|
|
*emptyOut = empty;
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
// Creates one surface storage object and returns its initial object reference.
|
|
KSTATUS KeSurfaceCreate(uint32_t width, uint32_t height, uint32_t format, uint32_t flags,
|
|
OBJECT** surfaceOut) {
|
|
if (!surfaceOut) return NTLSTATUS_INVALID_PARAMETER;
|
|
*surfaceOut = (OBJECT*)0;
|
|
if (!width || !height || width > SYS_SURFACE_MAX_DIMENSION || height > SYS_SURFACE_MAX_DIMENSION)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
if (format != SYS_SURFACE_FORMAT_XRGB8888 || flags != SYS_SURFACE_FLAG_NONE)
|
|
return NTLSTATUS_NOT_SUPPORTED;
|
|
|
|
size_t rowBytes = 0;
|
|
if (!KiSurfaceMulSize((size_t)width, sizeof(uint32_t), &rowBytes))
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
size_t pitchBytes = KiSurfaceAlignUp(rowBytes, 64u);
|
|
if (!pitchBytes || pitchBytes < rowBytes) return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
size_t payloadSize = 0;
|
|
if (!KiSurfaceMulSize(pitchBytes, (size_t)height, &payloadSize) || payloadSize > SYS_SURFACE_MAX_BYTES)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
size_t pageCount = (payloadSize + PAGE_SIZE - 1u) / PAGE_SIZE;
|
|
if (!pageCount || pageCount > SIZE_MAX / sizeof(PHYSICAL_ADDRESS))
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
KE_SURFACE* surface = (KE_SURFACE*)ObAllocateObject(ObjectTypeSurface, sizeof(KE_SURFACE));
|
|
if (!surface) return NTLSTATUS_NO_MEMORY;
|
|
surface->Header.Destroy = KiSurfaceDestroy;
|
|
surface->Width = width;
|
|
surface->Height = height;
|
|
surface->PitchBytes = (uint32_t)pitchBytes;
|
|
surface->Format = format;
|
|
surface->Flags = flags;
|
|
surface->PayloadSize = payloadSize;
|
|
surface->PageCount = pageCount;
|
|
|
|
surface->Frames = (PHYSICAL_ADDRESS*)MmAllocatePoolTag(pageCount * sizeof(PHYSICAL_ADDRESS),
|
|
MM_POOL_TAG('s', 'f', 'r', 'm'));
|
|
if (surface->Frames)
|
|
memset(surface->Frames, 0, pageCount * sizeof(PHYSICAL_ADDRESS));
|
|
|
|
KSTATUS status = KeMutexCreate(&surface->Lock);
|
|
if (KSUCCESS(status)) status = KeEventCreate(EventAutoReset, 0, &surface->CommitEvent);
|
|
if (!surface->Frames || !KSUCCESS(status)) {
|
|
ObDereferenceObject(&surface->Header);
|
|
return !surface->Frames ? NTLSTATUS_NO_MEMORY : status;
|
|
}
|
|
for (size_t i = 0; i < pageCount; ++i) {
|
|
PHYSICAL_ADDRESS frame = MmPfnAllocate(MmPfnUseSurface);
|
|
if (!frame) {
|
|
ObDereferenceObject(&surface->Header);
|
|
return NTLSTATUS_NO_MEMORY;
|
|
}
|
|
surface->Frames[i] = frame;
|
|
void* kva = MmPfnToVirtual(frame);
|
|
if (kva) memset(kva, 0, PAGE_SIZE);
|
|
}
|
|
|
|
status = MmCreateKernelViewFromFrames(surface->Frames, surface->PageCount, &surface->KernelView);
|
|
if (!KSUCCESS(status)) {
|
|
ObDereferenceObject(&surface->Header);
|
|
return status;
|
|
}
|
|
surface->KernelBase = MmKernelViewBase(surface->KernelView);
|
|
KiSurfaceStatsRegister(surface);
|
|
*surfaceOut = &surface->Header;
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
// Maps one surface into the calling process's VAD namespace with attenuated page permissions.
|
|
KSTATUS KeSurfaceMap(PROCESS* process, OBJECT* object, uint32_t flags,
|
|
void** baseOut, uint32_t* pitchBytesOut) {
|
|
KE_SURFACE* surface = KiSurfaceFromObject(object);
|
|
if (!process || !surface || !baseOut || !pitchBytesOut || !(flags & SYS_SURFACE_MAP_READ) ||
|
|
(flags & ~(SYS_SURFACE_MAP_READ | SYS_SURFACE_MAP_WRITE)))
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
uint64_t pageFlags = PAGE_PRESENT | PAGE_USER | PAGE_NX;
|
|
if (flags & SYS_SURFACE_MAP_WRITE) pageFlags |= PAGE_WRITE;
|
|
KSTATUS status = MmVadCreateSharedViewFromFrames(process, surface->Frames, surface->PageCount,
|
|
surface->PayloadSize, pageFlags,
|
|
MM_VAD_SURFACE, MmVadShared, baseOut);
|
|
if (KSUCCESS(status)) {
|
|
*pitchBytesOut = surface->PitchBytes;
|
|
KiSurfaceStatsRecordMap(surface);
|
|
}
|
|
return status;
|
|
}
|
|
|
|
// Removes one mapping only when the supplied VAD is a surface shared view owned by the process.
|
|
KSTATUS KeSurfaceUnmap(PROCESS* process, void* base) {
|
|
if (!process || !base) return NTLSTATUS_INVALID_PARAMETER;
|
|
void* vadBase = (void*)0;
|
|
size_t vadSize = 0;
|
|
uint64_t pageFlags = 0;
|
|
MM_VAD_TYPE type = MmVadGeneric;
|
|
uint32_t vadFlags = 0;
|
|
KSTATUS status = MmVadQuery(process, base, &vadBase, &vadSize, &pageFlags, &type, &vadFlags);
|
|
(void)vadSize;
|
|
(void)pageFlags;
|
|
if (!KSUCCESS(status)) return status;
|
|
if (vadBase != base || type != MmVadShared || !(vadFlags & MM_VAD_SURFACE))
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
return MmVadDestroy(process, base);
|
|
}
|
|
|
|
// Snapshots one surface's storage and commit metadata.
|
|
KSTATUS KeSurfaceQuery(OBJECT* object, SYS_SURFACE_DESC* descOut) {
|
|
KE_SURFACE* surface = KiSurfaceFromObject(object);
|
|
if (!surface || !descOut) return NTLSTATUS_INVALID_PARAMETER;
|
|
memset(descOut, 0, sizeof(*descOut));
|
|
|
|
KSTATUS status = KeAcquireMutex(surface->Lock, KE_WAIT_INFINITE);
|
|
if (!KE_MUTEX_ACQUIRE_SUCCEEDED(status)) return status;
|
|
descOut->Width = surface->Width;
|
|
descOut->Height = surface->Height;
|
|
descOut->PitchBytes = surface->PitchBytes;
|
|
descOut->Format = surface->Format;
|
|
descOut->PayloadBytes = surface->PayloadSize;
|
|
descOut->PageCount = surface->PageCount;
|
|
descOut->ContentGeneration = surface->ContentGeneration;
|
|
descOut->CommitCount = surface->CommitCount;
|
|
descOut->CommitRectCount = surface->CommitRectCount;
|
|
descOut->CommitClippedRectCount = surface->CommitClippedRectCount;
|
|
descOut->CommitEmptyClipCount = surface->CommitEmptyClipCount;
|
|
descOut->LastDamageBounds = surface->LastDamageBounds;
|
|
descOut->LastDamageValid = surface->LastDamageValid;
|
|
KeReleaseMutex(surface->Lock);
|
|
|
|
uintptr_t irq = KeAcquireSpinlock(&gSurfaceStatsLock);
|
|
descOut->MapCount = surface->MapCount;
|
|
KeReleaseSpinlock(&gSurfaceStatsLock, irq);
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
// Announces changed pixels without touching physical scanout.
|
|
KSTATUS KeSurfaceCommit(OBJECT* object, const DISPLAY_RECT* rects, uint32_t rectCount,
|
|
uint64_t* generationOut) {
|
|
KE_SURFACE* surface = KiSurfaceFromObject(object);
|
|
if (!surface) return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
uint32_t requestedCount = rectCount ? rectCount : 1u;
|
|
DISPLAY_RECT clipped[SYS_SURFACE_MAX_RECTS];
|
|
uint32_t clippedCount = 0;
|
|
uint64_t emptyCount = 0;
|
|
KSTATUS status = KiSurfaceCaptureRects(surface, rects, rectCount, clipped, &clippedCount, &emptyCount);
|
|
if (!KSUCCESS(status)) return status;
|
|
|
|
status = KeAcquireMutex(surface->Lock, KE_WAIT_INFINITE);
|
|
if (!KE_MUTEX_ACQUIRE_SUCCEEDED(status)) return status;
|
|
surface->ContentGeneration++;
|
|
surface->CommitCount++;
|
|
surface->CommitRectCount += requestedCount;
|
|
surface->CommitClippedRectCount += clippedCount;
|
|
surface->CommitEmptyClipCount += emptyCount;
|
|
surface->LastDamageValid = clippedCount ? 1u : 0u;
|
|
if (clippedCount) {
|
|
surface->LastDamageBounds = clipped[0];
|
|
for (uint32_t i = 1; i < clippedCount; ++i)
|
|
KiSurfaceUnionRect(&surface->LastDamageBounds, &clipped[i]);
|
|
} else {
|
|
memset(&surface->LastDamageBounds, 0, sizeof(surface->LastDamageBounds));
|
|
}
|
|
uint64_t generation = surface->ContentGeneration;
|
|
KeReleaseMutex(surface->Lock);
|
|
|
|
KiSurfaceStatsRecordCommit(requestedCount, clippedCount, emptyCount);
|
|
KeSignalEvent(surface->CommitEvent);
|
|
if (generationOut) *generationOut = generation;
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
// Presents one surface after a caller has separately proved DisplayManager PRESENT authority.
|
|
KSTATUS KeSurfacePresent(OBJECT* object, const DISPLAY_RECT* rects, uint32_t rectCount,
|
|
uint64_t* bytesCopiedOut) {
|
|
KE_SURFACE* surface = KiSurfaceFromObject(object);
|
|
if (!surface || !bytesCopiedOut) return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
DISPLAY_RECT clipped[SYS_SURFACE_MAX_RECTS];
|
|
uint32_t clippedCount = 0;
|
|
uint64_t emptyCount = 0;
|
|
KSTATUS status = KiSurfaceCaptureRects(surface, rects, rectCount, clipped, &clippedCount, &emptyCount);
|
|
(void)emptyCount;
|
|
if (!KSUCCESS(status)) return status;
|
|
if (!surface->KernelBase) return NTLSTATUS_DEVICE_ERROR;
|
|
|
|
const uint8_t* sourceBase = (const uint8_t*)surface->KernelBase;
|
|
uint64_t bytesCopied = 0;
|
|
for (uint32_t i = 0; i < clippedCount; ++i) {
|
|
const DISPLAY_RECT* rect = &clipped[i];
|
|
const uint32_t* source = (const uint32_t*)(sourceBase +
|
|
(size_t)(uint32_t)rect->Y * surface->PitchBytes +
|
|
(size_t)(uint32_t)rect->X * sizeof(uint32_t));
|
|
status = KeDisplayPresentPitched(rect->X, rect->Y, rect->W, rect->H, source, surface->PitchBytes);
|
|
if (!KSUCCESS(status)) return status;
|
|
bytesCopied += (uint64_t)(uint32_t)rect->W * (uint64_t)(uint32_t)rect->H * sizeof(uint32_t);
|
|
}
|
|
|
|
*bytesCopiedOut = bytesCopied;
|
|
KiSurfaceStatsRecordPresent(bytesCopied);
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
// Returns the passive event used to wait for commit-generation changes.
|
|
EVENT* KeSurfaceWaitEvent(OBJECT* object) {
|
|
KE_SURFACE* surface = KiSurfaceFromObject(object);
|
|
return surface ? surface->CommitEvent : (EVENT*)0;
|
|
}
|
|
|
|
// Snapshots global surface telemetry.
|
|
void KeSurfaceQueryStats(SYS_SURFACE_STATS* statsOut) {
|
|
if (!statsOut) return;
|
|
uintptr_t irq = KeAcquireSpinlock(&gSurfaceStatsLock);
|
|
*statsOut = gSurfaceStats;
|
|
statsOut->Size = sizeof(*statsOut);
|
|
statsOut->Version = SYS_STATS_VERSION;
|
|
KeReleaseSpinlock(&gSurfaceStatsLock, irq);
|
|
}
|