Files
UniXNU/drivers/NativeMobileDisplay/NativeLegacyDisplay.cpp
T
2026-10-19 18:49:31 +08:00

324 lines
21 KiB
C++

#include "NativeLegacyRequest.h"
// SPDX-License-Identifier: BSD-2-Clause-Patent
// Reuse the unchanged original generic framebuffer implementation. Unknown
// backend leaves stop; this class is not yet a published system display.
#include "OriginalLegacyFramebufferABI.h"
#include <IOKit/IOLib.h>
#include <IOKit/IOMemoryDescriptor.h>
#include <IOKit/pwr_mgt/IOPM.h>
#include <IOKit/pwr_mgt/IOPMpowerState.h>
#include <libkern/c++/OSData.h>
#include <pexpert/pexpert.h>
extern "C" __attribute__((noreturn)) void native_legacy_backend_guard(unsigned,void *,void *);
extern "C" IOService *native_legacy_display_allocate(void);
extern "C" bool native_legacy_gop_mapping_observed(IOService *,IOMemoryMap *,uint32_t,uint32_t);
extern "C" void native_legacy_registration_observed(IOService *);
extern "C" void native_legacy_client_enter(task_t,uint32_t);
extern "C" void native_legacy_client_observed(IOReturn,IOUserClient *);
extern "C" void native_legacy_pm_observed(unsigned,IOReturn,unsigned long);
extern "C" IOMemoryDescriptor *native_legacy_surface_descriptor(OSObject *);
extern "C" uint64_t native_legacy_surface_width(OSObject *),native_legacy_surface_height(OSObject *);
extern "C" uint64_t native_legacy_surface_stride(OSObject *),native_legacy_surface_allocation(OSObject *);
extern "C" uint32_t native_legacy_surface_format(OSObject *),native_legacy_surface_planes(OSObject *);
extern "C" void native_legacy_frame_observed(unsigned kind,uint64_t count,uint64_t minx,uint64_t miny,uint64_t maxx,uint64_t maxy);
extern "C" void native_legacy_frame_colors_observed(const uint32_t *colors,const uint64_t *counts,uint64_t other);
extern "C" void native_legacy_swap_validation_observed(bool,unsigned,const void *,const void *);
extern "C" bool native_legacy_prepare_scanout(void);
extern "C" void native_legacy_transfer_observed(IOReturn,const void *,uint64_t);
extern "C" void native_legacy_copy_timing_observed(uint64_t ticks,uint64_t bytes);
extern "C" void native_legacy_retirement_observed(IOService *);
class NativeLegacyDisplay : public IOMobileFramebufferLegacy {
OSDeclareDefaultStructors(NativeLegacyDisplay)
IOMemoryMap *bootMapping=nullptr;
uint32_t scanBudget=32;
void *shadow=nullptr;uint64_t shadowBytes=0;bool shadowValid=false;
bool primary=false;
bool logicalPM=false;
bool transferred=false;
const OriginalLegacySwapRequest *validatedRequest=nullptr;
public:
void free() override {if(logicalPM){PMstop();logicalPM=false;}primary=false;OSSafeReleaseNULL(bootMapping);IOMobileFramebufferLegacy::free();}
void stop(IOService *provider) override {
transferred=false;validatedRequest=nullptr;
if(logicalPM){PMstop();logicalPM=false;}
primary=false;IOMobileFramebufferLegacy::stop(provider);OSSafeReleaseNULL(bootMapping);
}
bool start(IOService *provider) override {
if(!IOMobileFramebufferLegacy::start(provider))return false;
unsigned enabled=0;PE_parse_boot_argn("offline-legacy-pm",&enabled,sizeof(enabled));
if(!enabled)return true;
unsigned phase=0;PE_parse_boot_argn("offline-legacy-phase",&phase,sizeof(phase));
if(enabled!=1 || phase!=4 || !provider || !bootMapping || !primary) {
native_legacy_pm_observed(1,kIOReturnNotReady,0);IOMobileFramebufferLegacy::stop(provider);return false;
}
// Actual IOKit PM lifecycle for logical scanout scheduling only. GOP
// provides no panel power switch: neither state advertises physical
// power output, power control or a hardware power transition.
static IOPMPowerState states[2]={};
states[0].version=states[1].version=1;
states[1].capabilityFlags=kIOPMDeviceUsable;
PMinit();logicalPM=initialized;
if(!logicalPM){native_legacy_pm_observed(1,kIOReturnNotReady,0);IOMobileFramebufferLegacy::stop(provider);return false;}
provider->joinPMtree(this);
IOReturn status=registerPowerDriver(this,states,2);native_legacy_pm_observed(1,status,0);
if(status==kIOReturnSuccess){status=changePowerStateToPriv(1);native_legacy_pm_observed(2,status,1);}
if(status!=kIOReturnSuccess){PMstop();logicalPM=false;IOMobileFramebufferLegacy::stop(provider);return false;}
return true;
}
IOReturn setPowerState(unsigned long ordinal,IOService *device) override {
if(!logicalPM)return IOMobileFramebufferLegacy::setPowerState(ordinal,device);
if(ordinal>1 || !bootMapping){native_legacy_pm_observed(3,kIOReturnUnsupported,ordinal);return kIOReturnUnsupported;}
native_legacy_pm_observed(3,kIOReturnSuccess,ordinal);
IOReturn status=IOMobileFramebufferLegacy::setPowerState(ordinal,device);
native_legacy_pm_observed(4,status,ordinal);return status;
}
// The explicit offline system-client experiment publishes only the real
// GOP mode. Unknown backend calls retain their stop-with-caller guards.
void registerService(IOOptionBits options=0) override {
unsigned enabled=0;PE_parse_boot_argn("offline-legacy-register",&enabled,sizeof(enabled));
if(enabled!=1)native_legacy_backend_guard(84,this,__builtin_return_address(0));
IOService::registerService(options);native_legacy_registration_observed(this);
}
bool isMainDisplay() override {return primary;}
void kickSwapQueue() override {
unsigned enabled=0;PE_parse_boot_argn("offline-legacy-transfer",&enabled,sizeof(enabled));
if(enabled!=1 || transferred || !logicalPM || !bootMapping || !primary)return;
const OriginalLegacySwapRequest *request=pendingSwapRequest();
if(!request || request!=validatedRequest)return;
// This bounded first-frame transfer preserves the original queue's
// resource ownership. Separately gated retirement follows the copy;
// neither path signals a hardware fence, announces a vblank or
// claims general producer synchronization.
// The owned GPU instrument completes its work before submission.
// The separate original software-renderer experiment uses completed
// synchronous CPU surfaces, with no GPU or display fence fabricated.
OSObject *surface=nullptr;
memcpy(&surface,reinterpret_cast<const unsigned char *>(request)+2624,sizeof(surface));
if(!surface) {
// Validated blank swap (all layers disabled over black): solid-fill the actual GOP.
PE_Video blank={};
uint32_t bw=0,bh=0;getDisplayDimensions(&bw,&bh);
uint64_t blankLength=(uint64_t)bw*bh*4;
if(PE_current_console(&blank) || blank.v_baseAddr!=bootMapping->getPhysicalAddress() ||
blank.v_width!=bw || blank.v_height!=bh || blank.v_depth!=32 || blank.v_rotate ||
!native_legacy_surface_bounds(bw,bh,blank.v_rowBytes,bootMapping->getLength(),bootMapping->getLength())) {
native_legacy_transfer_observed(kIOReturnNotReady,request,0);return;
}
if(!native_legacy_prepare_scanout()) {native_legacy_transfer_observed(kIOReturnNotReady,request,0);return;}
if(!native_legacy_fill_pixels(reinterpret_cast<volatile void *>(bootMapping->getVirtualAddress()),
bootMapping->getLength(),blank.v_rowBytes,bw,bh,0xff000000U)) {
native_legacy_transfer_observed(kIOReturnBadArgument,request,0);return;
}
__asm__ volatile("dsb sy" ::: "memory");
shadowValid=false;
transferred=true;
native_legacy_frame_observed(1,0,0,0,0,0);
{const uint32_t colors[4]={0xff000000U,0,0,0};const uint64_t counts[4]={(uint64_t)bw*bh,0,0,0};native_legacy_frame_colors_observed(colors,counts,0);}
native_legacy_transfer_observed(kIOReturnSuccess,request,blankLength);
finishTransfer(request);
return;
}
if(!surface->metaCast("IOSurface"))return;
IOMemoryDescriptor *memory=native_legacy_surface_descriptor(surface);
PE_Video video={};
uint32_t width=0,height=0;getDisplayDimensions(&width,&height);
uint64_t length=(uint64_t)width*height*4;
uint64_t sourceStride=native_legacy_surface_stride(surface);
uint64_t allocation=native_legacy_surface_allocation(surface);
if(!memory || !memory->metaCast("IOMemoryDescriptor") || !native_legacy_surface_bounds(width,height,sourceStride,allocation,memory->getLength()) ||
native_legacy_surface_width(surface)!=width || native_legacy_surface_height(surface)!=height ||
native_legacy_surface_format(surface)!=0x42475241 || native_legacy_surface_planes(surface) ||
PE_current_console(&video) || video.v_baseAddr!=bootMapping->getPhysicalAddress() ||
video.v_width!=width || video.v_height!=height || video.v_depth!=32 || video.v_rotate ||
!native_legacy_surface_bounds(width,height,video.v_rowBytes,bootMapping->getLength(),bootMapping->getLength())) {
native_legacy_transfer_observed(kIOReturnNotReady,request,0);return;
}
IOMemoryMap *source=memory->map(kIOMapAnywhere|kIOMapReadOnly);
if(!source || !source->getVirtualAddress() || source->getLength()<allocation) {
OSSafeReleaseNULL(source);native_legacy_transfer_observed(kIOReturnNoMemory,request,0);return;
}
if(!native_legacy_copy_layout(reinterpret_cast<volatile void *>(bootMapping->getVirtualAddress()),
bootMapping->getLength(),video.v_rowBytes,reinterpret_cast<const void *>(source->getVirtualAddress()),
source->getLength(),sourceStride,width,height)) {
OSSafeReleaseNULL(source);native_legacy_transfer_observed(kIOReturnBadArgument,request,0);return;
}
// The two content scans read every pixel again; they only describe the first frames (the probe keeps 32).
if(scanBudget) {
--scanBudget;
uint64_t scan[5];
if(native_legacy_frame_scan(reinterpret_cast<const void *>(source->getVirtualAddress()),source->getLength(),sourceStride,width,height,scan))
native_legacy_frame_observed(0,scan[0],scan[1],scan[2],scan[3],scan[4]);
uint32_t colors[4];uint64_t counts[4],other=0;
if(native_legacy_frame_colors(reinterpret_cast<const void *>(source->getVirtualAddress()),source->getLength(),sourceStride,width,height,colors,counts,&other))
native_legacy_frame_colors_observed(colors,counts,other);
}
if(!native_legacy_prepare_scanout()) {
OSSafeReleaseNULL(source);native_legacy_transfer_observed(kIOReturnNotReady,request,0);return;
}
uint64_t copyStart;__asm__ volatile("isb\n\tmrs %0, cntpct_el0":"=r"(copyStart));
uint64_t stored=UINT64_MAX;
{
// Store only what changed since the previous frame (the scanout memory is slow device memory).
uint64_t need=(uint64_t)width*height*4;
// Differential copy is opt-in (sheng-fb-diff=1): on hardware it reads the whole source every frame and was not faster
// than the plain copy (143-285 ms per swap either way), and the first boots with it were followed by an unresponsive
// lock screen that disappeared with the plain copy (int-005).
static int diffOff=-1;
if(diffOff<0){unsigned v=0;PE_parse_boot_argn("sheng-fb-diff",&v,sizeof v);diffOff=(v!=1);}
if(!diffOff && !shadow && need<=(64ull<<20)){shadow=IOMallocAligned((vm_size_t)need,16384);if(shadow){shadowBytes=need;shadowValid=false;}}
if(!diffOff && shadow && shadowBytes>=need)
stored=native_legacy_copy_diff(reinterpret_cast<volatile void *>(bootMapping->getVirtualAddress()),
bootMapping->getLength(),video.v_rowBytes,reinterpret_cast<const void *>(source->getVirtualAddress()),
source->getLength(),sourceStride,width,height,shadow,shadowValid);
if(stored!=UINT64_MAX)shadowValid=true;
}
if(stored==UINT64_MAX) {
shadowValid=false;
if(!native_legacy_copy_pixels(reinterpret_cast<volatile void *>(bootMapping->getVirtualAddress()),
bootMapping->getLength(),video.v_rowBytes,reinterpret_cast<const void *>(source->getVirtualAddress()),
source->getLength(),sourceStride,width,height)) {
OSSafeReleaseNULL(source);native_legacy_transfer_observed(kIOReturnBadArgument,request,0);return;
}
stored=length;
}
__asm__ volatile("dsb sy" ::: "memory");
{uint64_t copyEnd;__asm__ volatile("isb\n\tmrs %0, cntpct_el0":"=r"(copyEnd));native_legacy_copy_timing_observed(copyEnd-copyStart,stored);}
OSSafeReleaseNULL(source);transferred=true;
native_legacy_transfer_observed(kIOReturnSuccess,request,length);
finishTransfer(request);
}
// Separately gated retirement after a real scanout store, shared by copied and blank frames.
void finishTransfer(const OriginalLegacySwapRequest *request) {
unsigned retire=0,cpuPresent=0;
PE_parse_boot_argn("offline-legacy-retire",&retire,sizeof(retire));
PE_parse_boot_argn("offline-legacy-cpu-present",&cpuPresent,sizeof(cpuPresent));
if(retire==1 || cpuPresent==1) {
// Only after the real copy and store barrier: select the actual
// pending head and let the unchanged family release it and wake
// waiters. This reports memory scanout completion, not a panel
// vblank or hardware fence. Producer completion must precede
// submission; this does not implement asynchronous GPU sync.
if(!activateOnlyPendingSwap(request))return;
validatedRequest=nullptr;
IOMobileFramebufferLegacy::completeCurrentSwap();
native_legacy_retirement_observed(this);
// The unchanged family has retired this generation and released
// its current reference. Permit the next independently validated
// single pending frame. Keep the latch set while completion can
// reenter this method, and whenever retirement remains disabled.
transferred=false;
}
}
IOReturn collectCompletionReports(uint32_t,void *,uint32_t *count,uint32_t) override {
// The original completion helper passes null optional outputs and
// ignores this IOReturn. GOP exposes no hardware completion telemetry.
if(count)*count=0;
return kIOReturnUnsupported;
}
bool validateSwap(const OriginalLegacySwapRequest *request) override {
// Qualified original request fields are packed; use memcpy for each
// access. Accept only one full-frame, untransformed, nonplanar BGRA
// surface with real original backing memory. No generic capability
// or success is supplied for other layer/format/geometry contracts.
auto bytes=reinterpret_cast<const unsigned char *>(request);
auto word=[&](unsigned offset){uint32_t value=0;memcpy(&value,bytes+offset,4);return value;};
OSObject *surface=nullptr;unsigned reason=1;bool accepted=false;
unsigned enabled=0;PE_parse_boot_argn("offline-legacy-validate",&enabled,sizeof(enabled));
if(enabled==1 && logicalPM && primary && bootMapping && request) {
reason=2;OSObject *surfaces[4]={};memcpy(surfaces,bytes+2624,sizeof(surfaces));surface=surfaces[0];
uint32_t width=0,height=0;getDisplayDimensions(&width,&height);
if(surface && surface->metaCast("IOSurface") && !surfaces[1] && !surfaces[2] && !surfaces[3] &&
native_legacy_single_layer_state(word(792),word(796),word(800))) {
reason=3;bool rectangles=true;
const unsigned rectOffsets[]={17,81};
for(unsigned offset: rectOffsets) {
uint32_t rect[4]={};memcpy(rect,bytes+offset,sizeof(rect));
rectangles=rectangles && rect[0]==0 && rect[1]==0 && rect[2]==width && rect[3]==height;
}
bool options=true;for(unsigned offset=804;offset<=812;offset+=4)options=options && word(offset)==0;
if(width && height && rectangles && options && word(816)==width && word(820)==height) {
reason=4;
IOMemoryDescriptor *memory=native_legacy_surface_descriptor(surface);
if(native_legacy_surface_width(surface)==width && native_legacy_surface_height(surface)==height &&
native_legacy_surface_format(surface)==0x42475241 && !native_legacy_surface_planes(surface) &&
memory && memory->metaCast("IOMemoryDescriptor") &&
native_legacy_surface_bounds(width,height,native_legacy_surface_stride(surface),
native_legacy_surface_allocation(surface),memory->getLength())) {
reason=0;accepted=true;
}
}
}
}
if(!accepted && reason==2 && !surface && enabled==1) {
// No surface in any slot and every updated layer disabled over black: present a blank screen.
OSObject *none[4]={};memcpy(none,bytes+2624,sizeof(none));
uint32_t width=0,height=0;getDisplayDimensions(&width,&height);
bool options=true;for(unsigned offset=804;offset<=812;offset+=4)options=options && word(offset)==0;
if(!none[0] && !none[1] && !none[2] && !none[3] && width && height && options &&
word(816)==width && word(820)==height && native_legacy_blank_state(word(792),word(796),word(800))) {
reason=5;accepted=true;
}
}
validatedRequest=accepted?request:nullptr;
native_legacy_swap_validation_observed(accepted,reason,request,surface);return accepted;
}
// The original slot-188 caller passes a uint32_t output and consumes an
// IOReturn. Its implementation assumes an AppleARMIODevice provider.
// Query our actual generic provider instead; GOP supplies no dot pitch.
IOReturn getDotPitch(uint32_t *pitch) override {
if(!pitch)return kIOReturnBadArgument;
*pitch=0;IOService *provider=getProvider();if(!provider)return kIOReturnNotReady;
OSObject *property=provider->getProperty("dot-pitch");
if(!property)return kIOReturnNotFound;
OSData *data=OSDynamicCast(OSData,property);
if(!data || data->getLength()!=sizeof(*pitch))return kIOReturnBadArgument;
memcpy(pitch,data->getBytesNoCopy(),sizeof(*pitch));return kIOReturnSuccess;
}
// Selector 74 enumerates optional frame-info trace keys, not display
// modes. This backend implements no trace-key subscription contract.
IOReturn supportedFrameInfo(uint32_t,uint32_t *count,char *key,uint32_t *mask) override {
if(!count || !key || !mask)return kIOReturnBadArgument;
*count=0;*mask=0;bzero(key,64);return kIOReturnUnsupported;
}
IOReturn newUserClient(task_t task,void *security,UInt32 type,IOUserClient **client) override {
native_legacy_client_enter(task,type);
IOReturn status=IOMobileFramebufferLegacy::newUserClient(task,security,type,client);
native_legacy_client_observed(status,status==kIOReturnSuccess && client?*client:nullptr);return status;
}
bool startHardware() override {
unsigned enabled=0;
PE_parse_boot_argn("offline-legacy-gop",&enabled,sizeof(enabled));
if(enabled!=1)return false;
PE_Video video={};IOService *provider=getProvider();
if(!provider || PE_current_console(&video) || !video.v_baseAddr ||
!video.v_width || !video.v_height || video.v_depth!=32 || video.v_rotate ||
video.v_width>16384 || video.v_height>16384 || video.v_rowBytes<video.v_width*4 ||
video.v_rowBytes>UINT64_MAX/video.v_height)return false;
IOMemoryMap *mapping=provider->mapDeviceMemoryWithIndex(0,kIOMapInhibitCache);
if(!mapping)return false;
bool valid=mapping->getVirtualAddress() && mapping->getPhysicalAddress()==video.v_baseAddr &&
mapping->getLength()==video.v_rowBytes*video.v_height;
if(valid) {
installBootDimensions((uint32_t)video.v_width,(uint32_t)video.v_height);
uint32_t width=0,height=0;getDisplayDimensions(&width,&height);
valid=width==video.v_width && height==video.v_height;
if(valid) {
OSSafeReleaseNULL(bootMapping);bootMapping=mapping;mapping=nullptr;primary=video.v_display==1;
valid=native_legacy_gop_mapping_observed(this,bootMapping,width,height);
}
}
OSSafeReleaseNULL(mapping);return valid;
}
#define LEAF(n) __attribute__((noreturn,noinline)) void opaque##n() override { native_legacy_backend_guard(n,this,__builtin_return_address(0)); }
LEAF(211)
LEAF(245)
LEAF(246)
LEAF(306)
LEAF(307)
LEAF(338)
LEAF(345)
#undef LEAF
};
OSDefineMetaClassAndStructors(NativeLegacyDisplay,IOMobileFramebufferLegacy)
IOService *native_legacy_display_allocate(void) {return new NativeLegacyDisplay;}