324 lines
21 KiB
C++
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;}
|