Result prepare(IO &io,bool admitted,const Wiring &w) {
+ Result r;
+ if(!admitted || !w.valid || !w.selected())return r;
+ AlderLakeN::DisplayReadout s;s.status=AlderLakeN::ReadoutStatus::Complete;
+ const auto add=[&](uint32_t reg) {
+ if(s.count>=160 || !io.readable(reg)){s.status=AlderLakeN::ReadoutStatus::Unavailable;return UINT32_MAX;}
+ const auto v=io.read(reg);s.registers[s.count++]={reg,v};
+ if(v==UINT32_MAX)s.status=AlderLakeN::ReadoutStatus::Unavailable;
+ return v;
+ };
+ const auto power=add(0x45404);
+ if(power==UINT32_MAX || !(power&1))return r;
+ const uint32_t common[] {0x45450,0x45454,0x45444,0x164280,0x1642BC,0x46010,0x46014,0x46018,0x46030};
+ for(auto reg:common)add(reg);
+ const unsigned ddis[] {0,3,4,5,6};
+ for(auto d:ddis)add(0x64000+0x100*d);
+ uint32_t ports[3] {UINT32_MAX,UINT32_MAX,UINT32_MAX},used=0;
+ for(unsigned p=0;p<3;++p) {
+ const uint32_t mask=p==0?1:p==1?0x15:0x55;
+ if((power&mask)!=mask)continue;
+ s.poweredPipes|=1U<>27)&15,protocol=(func>>24)&7;
+ if(!encoding || protocol>2 || ((func>>20)&7) || get(0x60404+bank))return r;
+ const auto d=encoding-1;Port port;
+ if(!Port::resolve(d,port) || !(w.selected()&(1U<>28)!=d+1 || (protocol==2 && (!(tp&0x80000000U) || (tp&(1U<<27)))))return r;
+ const auto geometry=XELPGeometry::linear(get(0x70180+bank),get(0x70188+bank),get(0x70190+bank),get(0x7018C+bank),get(0x701A4+bank));
+ if(!AlderLakeN::bootGeometry(s,p,geometry,get(0x60000+bank),get(0x6000C+bank)) ||
+ (get(0x7019C+bank)&4095) || get(0x7019C+bank)!=get(0x701AC+bank))return r;
+ }
+ uint32_t changed[32] {},count=0,current=UINT32_MAX;
+ const auto stable=[&] {
+ for(unsigned i=0;i=2 && x.offset==0x64000+0x100*ports[current] ? ~0x80U : UINT32_MAX;
+ if(!skip && ((io.read(x.offset)^x.value)&mask)){r.reg=x.offset;r.value=io.read(x.offset);return false;}
+ }
+ return true;
+ };
+ if(!stable()){r.status=Status::Drift;return r;}
+ const auto write=[&](uint32_t reg,uint32_t v) {
+ if(!stable()){r.status=Status::Drift;return false;}
+ bool seen=false;for(unsigned i=0;i=32)return false;changed[count++]=reg;}
+ io.write(reg,v);++r.writes;io.read(reg);return true;
+ };
+ const auto wait=[&](uint32_t reg,uint32_t mask,uint32_t value) {
+ r.reg=reg;
+ return XELPGOP::poll(io,stable,[&](uint32_t v){return (v&mask)==value;},
+ reg,r.value,r.status,Status::Drift);
+ };
+ for(unsigned p=r.primary+1;p<3;++p)if(r.active&(1U<=2 && uint32_t(v-frame)<0x10000000U;},
+ 0x70040+bank,now,r.status,Status::Drift))return r;
+ r.stage=2;
+ if(!write(0x70008+bank,get(0x70008+bank)&~0x80000000U) || !wait(0x70008+bank,0xC0000000U,0))return r;
+ r.stage=3;
+ if(!write(0x60400+bank,get(0x60400+bank)&~(0x80000000U|(15U<<27)|(7U<<24))) ||
+ !wait(0x60400+bank,0x80000000U,0))return r;
+ for(unsigned n=0;n<2;++n) {
+ const auto base=0x68170+0x800*p+0x100*n;
+ if(get(base+0x10)&0x80000000U) {
+ if(!write(base+0x10,0) || !wait(base+0x10,UINT32_MAX,0) || !write(base,0) || !write(base+4,0))return r;
+ }
+ }
+ r.stage=4;
+ if(!XELPGOP::stopBuffer(write,wait,bank,port.buffer,get(port.buffer),get(0x60540+bank),
+ ((get(0x60400+bank)>>24)&7)==2))return r;
+ r.stage=5;
+ if(!write(0x46140+4*p,get(0x46140+4*p)&0x0FFFFFFFU) || !wait(0x46140+4*p,0xF0000000U,0))return r;
+ r.parked|=1U<
#include "kern_display13_power_bridge.hpp"
#include "kern_display13_clock.hpp"
+#include "kern_display13_tc.hpp"
namespace AlderLakeN {
constexpr uint32_t Identity = 0x46D18086;
@@ -55,7 +56,8 @@ static_assert(sizeof(GTReadout)==60*4,"AlderLakeN GT readout layout");
// Linux v6.12 gen12 forcewake domains: fuses and BCS are GT, RCS is render.
// Only acquire an unowned kernel bit0. Never reset domains or alter engines.
-template GTReadout readGT(IO &io, bool admitted, uint32_t *edram=nullptr) {
+template GTReadout readGT(IO &io, bool admitted, uint32_t *edram=nullptr,
+ bool (*decode)(const uint32_t *,Resources &)=nullptr) {
GTReadout out;
if(!admitted)return out;
constexpr uint32_t request[] {0xA188,0xA278},ack[] {0x130044,0xD84};
@@ -95,8 +97,9 @@ template GTReadout readGT(IO &io, bool admitted, uint32_t *edram=nullp
if(out.fuses[i]==UINT32_MAX)out.status=GTStatus::Unavailable;
}
if(out.status==GTStatus::Complete &&
- (!resources(out.fuses[0],out.fuses[1],out.fuses[2],out.fuses[3],out.resources) ||
- out.fuses[4]!=0x000E00FE))out.status=GTStatus::ProfileMismatch;
+ !(decode ? decode(out.fuses,out.resources) :
+ (resources(out.fuses[0],out.fuses[1],out.fuses[2],out.fuses[3],out.resources) &&
+ out.fuses[4]==0x000E00FE)))out.status=GTStatus::ProfileMismatch;
if(edram) {
*edram=io.readable(0x120010) ? io.read(0x120010) : UINT32_MAX;
if(*edram==UINT32_MAX)out.status=GTStatus::Unavailable;
@@ -173,6 +176,12 @@ template DisplayReadout readDisplay(IO &io, bool admitted) {
for (auto offset : transport)
add(offset + 0x1000 * pipe);
add(0x46140 + 4 * pipe);
+ // Display11+ has two scalers per powered pipe (Linux display runtime).
+ // Capture the binding and destination, without touching power/coefficients.
+ for(unsigned scaler=0;scaler<2;++scaler) {
+ const auto base=0x68170+0x800*pipe+0x100*scaler;
+ add(base);add(base+4);add(base+0x10);
+ }
// Display13 DSC/joiner belongs to the corresponding pipe well.
add(0x78000 + 0x200 * pipe);add(0x78004 + 0x200 * pipe);
}
@@ -191,13 +200,23 @@ inline bool value(const DisplayReadout &r, uint32_t offset, uint32_t &out) {
// Source-side certificate for an already-running, uncompressed external SST
// GOP. This does not prove sink DPCD state, PLL rate or native AUX ownership.
-inline bool dpSource(const DisplayReadout &boot,unsigned pipe,unsigned ddi) {
- if(pipe>=3 || ddi>=2 || boot.status!=ReadoutStatus::Complete ||
+inline bool dpSource(const DisplayReadout &boot,unsigned pipe,unsigned ddi,const Display13TC::Boot *tc=nullptr) {
+ const bool typeC=ddi>=3 && ddi<=6;
+ if(pipe>=3 || (ddi>=2 && !typeC) || boot.status!=ReadoutStatus::Complete ||
!(boot.activePipes&boot.poweredPipes&(1U<valid() || tc->port.ddi!=ddi || tc->pipe!=pipe ||
+ !value(boot,0x45404,power) || power!=tc->power ||
+ !value(boot,0x45454,ddiPower) || ddiPower!=tc->ddiPower)return false;
+ buf=tc->buffer;
+ } else if(!value(boot,0x64000+ddi*0x100,buf))return false;
if(!value(boot,0x60400+bank,trans) || !value(boot,0x60540+bank,tp) ||
- !value(boot,0x64000+ddi*0x100,buf) || !value(boot,0x46140+4*pipe,clock) ||
+ !value(boot,0x46140+4*pipe,clock) ||
!value(boot,0x60404+bank,ctl2) || !value(boot,0x78000+pipe*0x200,dss1) ||
!value(boot,0x78004+pipe*0x200,dss2) || !value(boot,0x60030+bank,dm) ||
!value(boot,0x60034+bank,dn) || !value(boot,0x60040+bank,lm) ||
@@ -205,10 +224,16 @@ inline bool dpSource(const DisplayReadout &boot,unsigned pipe,unsigned ddi) {
const unsigned lanes=((trans>>1)&7)+1;
// Linux v6.12 i915_reg.h: SST, normal training, scrambling, no FEC/MST,
// port sync, HDCP, DSC or joined pipes. Enhanced framing may be on or off.
+ // intel_ddi_read_func_ctl classifies SST from MODE_SELECT, not the MST
+ // payload-allocation bit. Firmware may leave bit8 set on an SST GOP;
+ // preserve it and include it in the unchanged-source check, never clear it
+ // on an enabled transcoder or infer an MST transport from that bit alone.
return (trans&0xFF700000U)==(0x82000000U|((ddi+1)<<27)) &&
- !(trans&((1U<<15)|(1U<<9)|(1U<<8))) && !ctl2 &&
+ !(trans&((1U<<15)|(1U<<9))) && !ctl2 &&
(lanes==1 || lanes==2 || lanes==4) && ((buf>>1)&7)==lanes-1 &&
- (buf&0x800000C0U)==0x80000000U && (clock>>28)==ddi+1 &&
+ (buf&(typeC && tc->ownershipOptional?0x80000080U:0x800000C0U))==
+ (typeC && !tc->ownershipOptional?0x80000040U:0x80000000U) &&
+ (clock>>28)==ddi+(typeC?3:1) && (!typeC || clock==tc->transClock) &&
(tp&~(1U<<18))==0x80000300U &&
!(dss1&0xF0300000U) && !(dss2&0x80008000U) &&
!(dm&0x81000000U) && (dm&0xFFFFFF) &&
@@ -220,15 +245,23 @@ enum class GOPProtocol : uint32_t {TMDS,DP};
// This is an early, serialized bootstrap transaction, not a modeset or link
// adoption certificate. A changed link stops further writes, including rollback.
template bool dpSourceUnchanged(IO &io,const DisplayReadout &r,unsigned pipe,unsigned ddi,
- uint32_t addedAUXRequest=0) {
- if(pipe>=3 || ddi>=2 || (addedAUXRequest && addedAUXRequest!=(2U<<(2*ddi))))return false;
+ uint32_t addedAUXRequest=0,const Display13TC::Boot *tc=nullptr) {
+ // addedAUXRequest is the existing Combo boot-lease transition only.
+ // TC lifetime uses Mode's own AUX lease, not the Combo MainLinkAUX slot.
+ if(!dpSource(r,pipe,ddi,tc) || (addedAUXRequest && (ddi>=2 || addedAUXRequest!=(2U<<(2*ddi)))))return false;
+ if(ddi>=3 && !tc->unchanged(io))return false;
const uint32_t bank=pipe*0x1000;
- const uint32_t regs[] {0x60400+bank,0x60404+bank,0x60540+bank,0x60030+bank,0x60034+bank,
+ // Recheck power before touching pipe-local registers. A captured snapshot
+ // is not permission to read a bank whose well has since disappeared.
+ const uint32_t regs[] {0x45404,0x45454,0x45444,0x42000,
+ 0x60400+bank,0x60404+bank,0x60540+bank,0x60030+bank,0x60034+bank,
0x60040+bank,0x60044+bank,0x70008+bank,0x46140+4*pipe,0x64000+0x100*ddi,
- 0x78000+0x200*pipe,0x78004+0x200*pipe,0x45404,0x45454,0x45444,0x42000};
+ 0x78000+0x200*pipe,0x78004+0x200*pipe};
for(auto reg:regs) {
uint32_t expected=0;
- if(!value(r,reg,expected) || !io.readable(reg))return false;
+ if(ddi>=3 && reg==0x64000+0x100*ddi)expected=tc->buffer;
+ else if(!value(r,reg,expected))return false;
+ if(!io.readable(reg))return false;
if(reg==Display13Power::AUX)expected|=addedAUXRequest;
if(io.read(reg)!=expected)return false;
}
@@ -269,7 +302,7 @@ template Display13Power::Result adoptDPBootAUX(IO &io,const DisplayRea
}
enum class LinearStatus : uint32_t {
NotAdmitted, Ready, Success, NotSingleHDMI, NotLinearFHD, OtherPlaneActive,
- StateChanged, NoVblank, DisableTimeout, EnableTimeout, RollbackFailed, InvalidDPSource
+ StateChanged, NoVblank, DisableTimeout, EnableTimeout, RollbackFailed, InvalidDPSource, InvalidTCSource
};
struct LinearPlan {
uint32_t pipe {}, chicken {}, arb {}, control {}, stride {}, position {}, size {}, offset {}, surface {};
@@ -281,19 +314,60 @@ struct LinearResult {
uint32_t pipe {}, writes {}, framesBefore {}, framesAfter {}, restored {};
};
+// Qualify a firmware pipe scaler separately from an unscaled linear GOP.
+// PIPESRC is width:height minus one; scaler WIN_SZ is width:height, not
+// minus one. Only one pipe-bound scaler covering the complete output is
+// accepted. Plane-bound/planar/programmed-factor scaling is not this contract.
+inline bool bootGeometry(const DisplayReadout &r,unsigned pipe,const XELPGeometry::Linear &geometry,
+ uint32_t ht,uint32_t vt) {
+ if(XELPGeometry::timing(geometry,ht,vt))return true;
+ if(!geometry.valid || pipe>=3 || ht==UINT32_MAX || vt==UINT32_MAX ||
+ (ht&0xC000C000) || (vt&0xC000C000))return false;
+ uint32_t src=0;
+ if(!value(r,0x6001C+0x1000*pipe,src) ||
+ src!=(((geometry.width-1)<<16)|(geometry.height-1)))return false;
+ const auto width=(ht&0x3FFF)+1,height=(vt&0x3FFF)+1;
+ if(((ht>>16)&0x3FFF)+1>16)&0x3FFF)+1 bool bootGeometryUnchanged(IO &io,const DisplayReadout &r,unsigned pipe) {
+ // The accepted scaler stays live while the primary plane changes GGTT mode.
+ // Any captured source/timing/scaler drift invalidates the handoff.
+ const uint32_t regs[] {0x60000+0x1000*pipe,0x6000C+0x1000*pipe,0x6001C+0x1000*pipe,
+ 0x68170+0x800*pipe,0x68174+0x800*pipe,0x68180+0x800*pipe,
+ 0x68270+0x800*pipe,0x68274+0x800*pipe,0x68280+0x800*pipe};
+ for(auto reg:regs) {uint32_t v=0;if(value(r,reg,v) && io.read(reg)!=v)return false;}
+ return true;
+}
+
// First Display 13 operation: reinitialize an existing linear RGB primary
-// plane on the explicitly selected Combo A/B/C. The default remains B for
+// plane on the explicitly selected Combo, or fixed TC with a live link
+// certificate. The default remains B for
// the original standalone diagnostics. No new PLL/PHY/power-well cold
// initialization, a new GGTT mapping, full modeset or accelerated scanout.
-inline LinearStatus linearPlan(const DisplayReadout &r, LinearPlan &p,unsigned ddi=1,GOPProtocol protocol=GOPProtocol::TMDS) {
+inline LinearStatus linearPlan(const DisplayReadout &r, LinearPlan &p,unsigned ddi=1,GOPProtocol protocol=GOPProtocol::TMDS,
+ const Display13TC::Boot *tc=nullptr) {
p={};
- if (ddi>=3 || r.status!=ReadoutStatus::Complete || !r.activePipes || (r.activePipes&~7U) ||
+ if (ddi>=7 || r.status!=ReadoutStatus::Complete || !r.activePipes || (r.activePipes&~7U) ||
(r.activePipes & (r.activePipes-1)) || (r.activePipes&~r.poweredPipes) ||
(protocol!=GOPProtocol::TMDS && protocol!=GOPProtocol::DP) ||
(protocol==GOPProtocol::TMDS && ddi==1 && r.activePipes!=r.hdmiBPipes))
return LinearStatus::NotSingleHDMI;
while (!(r.activePipes & (1U<=3) return LinearStatus::NotSingleHDMI;
+ if(ddi>=3 && (!tc || !tc->valid() || tc->port.ddi!=ddi || tc->pipe!=p.pipe ||
+ (protocol!=GOPProtocol::DP && tc->ownershipOptional)))
+ return LinearStatus::InvalidTCSource;
const uint32_t delta=0x1000*p.pipe;
uint32_t trans {}, cursor {}, surfLive {}, plane {}, pipeConf {}, htotal {}, vtotal {};
if (!value(r,0x42084,p.chicken) || !value(r,0x70028+delta,p.arb) ||
@@ -306,11 +380,11 @@ inline LinearStatus linearPlan(const DisplayReadout &r, LinearPlan &p,unsigned d
if(!(trans&(1U<<31)) || ((trans>>27)&15)!=ddi+1 ||
(protocol==GOPProtocol::TMDS && ((trans>>24)&7)>1))
return LinearStatus::NotSingleHDMI;
- if(protocol==GOPProtocol::DP && !dpSource(r,p.pipe,ddi))return LinearStatus::InvalidDPSource;
+ if(protocol==GOPProtocol::DP && !dpSource(r,p.pipe,ddi,tc))return LinearStatus::InvalidDPSource;
p.geometry=XELPGeometry::linear(p.control,p.stride,p.size,p.position,p.offset);
if(!p.geometry.valid || (p.surface&4095) || p.surface!=surfLive ||
((trans>>20)&7)!=0 || (pipeConf&0xC0000000)!=0xC0000000 ||
- !XELPGeometry::timing(p.geometry,htotal,vtotal))return LinearStatus::NotLinearFHD;
+ !bootGeometry(r,p.pipe,p.geometry,htotal,vtotal))return LinearStatus::NotLinearFHD;
// DPT disable is global on Display 13. Exclude all other active planes
// and cursors, including planes on powered but inactive pipes.
for (unsigned pipe=0;pipe<3;++pipe) {
@@ -328,11 +402,11 @@ inline LinearStatus linearPlan(const DisplayReadout &r, LinearPlan &p,unsigned d
}
template LinearResult reinitializeLinear(IO &io, const DisplayReadout &r, bool admitted,unsigned ddi=1,
- GOPProtocol protocol=GOPProtocol::TMDS) {
+ GOPProtocol protocol=GOPProtocol::TMDS,const Display13TC::Boot *tc=nullptr) {
LinearResult out;
if (!admitted) return out;
LinearPlan p;
- out.status=linearPlan(r,p,ddi,protocol);
+ out.status=linearPlan(r,p,ddi,protocol,tc);
if(out.status!=LinearStatus::Ready)return out;
out.pipe=p.pipe;
const auto d=0x1000*p.pipe;
@@ -340,7 +414,9 @@ template LinearResult reinitializeLinear(IO &io, const DisplayReadout
const auto write=[&](uint32_t reg,uint32_t v){io.write(reg,v);++out.writes;io.read(reg);};
bool linkChanged=false;
const auto linkStable=[&] {
- if(protocol==GOPProtocol::DP && !dpSourceUnchanged(io,r,p.pipe,ddi))linkChanged=true;
+ if(!bootGeometryUnchanged(io,r,p.pipe))linkChanged=true;
+ if(protocol==GOPProtocol::DP && !dpSourceUnchanged(io,r,p.pipe,ddi,0,tc))linkChanged=true;
+ if(ddi>=3 && (!tc || !tc->unchanged(io)))linkChanged=true;
return !linkChanged;
};
// Check all captured words used by the transaction before the first write.
diff --git a/WhateverXe/kern_display13_boot.hpp b/WhateverXe/kern_display13_boot.hpp
index 07e28cd6..d048343b 100644
--- a/WhateverXe/kern_display13_boot.hpp
+++ b/WhateverXe/kern_display13_boot.hpp
@@ -1,29 +1,109 @@
-// Combo multi-GOP bootstrap: preserve pipe A, park other GOPs before native
+// Multi-GOP bootstrap: preserve pipe A, park other GOPs before native
// ownership exists. This does not pretend both GOPs were adopted by the donor.
#ifndef KERN_DISPLAY13_BOOT_HPP
#define KERN_DISPLAY13_BOOT_HPP
#include "kern_alderlake_n.hpp"
+#include "kern_xelp_gop_stop.hpp"
#include "kern_display13_fb_irq.hpp"
#include "kern_display13_fb_topology.hpp"
#include "kern_xelp_memory_layout.hpp"
+#include "kern_display13_tc_clock.hpp"
namespace Display13Boot {
-// Certificate for the existing linear TMDS boot path. Render qualification is
+// Certificate for the linear boot memory. Render qualification is
// separate; callers must also qualify the physical owner, BAR and memory layout.
-// DP/other-pipe adoption requires a separate boot contract, not a wider default.
+// DP requires an explicit source certificate; native link adoption remains a
+// separate contract. Existing runtime callers retain their TMDS default.
template bool reservedPrefix(IO &io,const AlderLakeN::DisplayReadout &display,
- bool bootstrapReady,unsigned ddi,const XELPMemory::Layout &memory) {
+ bool bootstrapReady,unsigned ddi,const XELPMemory::Layout &memory,
+ const Display13TC::Boot *tc=nullptr,
+ AlderLakeN::GOPProtocol protocol=AlderLakeN::GOPProtocol::TMDS) {
AlderLakeN::LinearPlan plan;
- if(!bootstrapReady || AlderLakeN::linearPlan(display,plan,ddi)!=AlderLakeN::LinearStatus::Ready ||
+ if(!bootstrapReady || AlderLakeN::linearPlan(display,plan,ddi,protocol,tc)!=AlderLakeN::LinearStatus::Ready ||
plan.pipe!=0 || !memory.range(plan.surface,AlderLakeN::ScanoutBytes,true))return false;
+ const auto stable=[&] {
+ return AlderLakeN::bootGeometryUnchanged(io,display,plan.pipe) &&
+ (ddi<3 || (tc && tc->unchanged(io))) &&
+ (protocol!=AlderLakeN::GOPProtocol::DP || AlderLakeN::dpSourceUnchanged(io,display,plan.pipe,ddi,0,tc));
+ };
+ if(!stable())return false;
for(unsigned i=0;i AlderLakeN::LinearResult prepareLinear(IO &io,const AlderLakeN::DisplayReadout &boot,
+ bool admitted,unsigned ddi,const XELPMemory::Layout &memory,const Display13TC::Boot *tc=nullptr,
+ AlderLakeN::GOPProtocol protocol=AlderLakeN::GOPProtocol::TMDS) {
+ using namespace AlderLakeN;
+ LinearResult r;
+ if(!admitted)return r;
+ if(!reservedPrefix(io,boot,true,ddi,memory,tc,protocol)){r.status=LinearStatus::StateChanged;return r;}
+ return reinitializeLinear(io,boot,true,ddi,protocol,tc);
}
enum class Status : uint32_t {Unavailable,Complete,Topology,Readback,Timeout,PeerChanged};
struct Result {
uint32_t version {1};Status status {Status::Unavailable};
uint32_t stage {},writes {},before {},after {},lastReg {},expected {},observed {},framesBefore {},framesAfter {};
};
+// Firmware can leave ownership on a selected fixed TC port that has no GOP.
+// Linux intel_tc_port_sanitize_mode disconnects unused PHYs. Here admit only
+// the already-cold case: disabled/idle buffer, stopped/gated PLL and clocks,
+// no transcoder route and no AUX command. Do not stop an unclassified link.
+struct UnusedTCResult {
+ uint32_t version {1};Status status {Status::Unavailable};
+ uint32_t cleared {},writes {},ddi {},buffer {},ready {},clock {},pll {},gate {};
+};
+template UnusedTCResult releaseUnusedTC(IO &io,const AlderLakeN::DisplayReadout &boot,
+ bool admitted,const Display13HDMIWiring &w) {
+ UnusedTCResult r;
+ if(!admitted)return r;
+ r.status=Status::Topology;
+ if(boot.status!=AlderLakeN::ReadoutStatus::Complete || boot.activePipes!=1 ||
+ !(boot.poweredPipes&1) || !Display13FBTopology::multiOutputWiring(w))return r;
+ uint32_t power=0,auxPower=0;
+ if(!AlderLakeN::value(boot,0x45404,power) || (power&5)!=5 ||
+ !AlderLakeN::value(boot,0x45444,auxPower))return r;
+ const auto stable=[&] {
+ for(unsigned i=0;i>27)&15)==d+1 || (clock>>28)==d+3)routed=true;
+ }
+ if(routed)continue;
+ Display13TC::Port port;Display13TC::Port::resolve(d,port);
+ r.ddi=d;r.buffer=io.read(port.buffer);
+ if(r.buffer==UINT32_MAX){r.status=Status::Readback;return r;}
+ if(!(r.buffer&64))continue;
+ r.ready=io.read(port.ready);r.clock=io.read(port.clock);r.pll=io.read(port.pll);
+ r.gate=io.read(Display13TC::Clock::Gate);
+ const auto aux=0x64310+0x100*(d-3);
+ if((r.buffer&0x800000C0U)!=0xC0 || r.ready==UINT32_MAX || (r.ready&7)!=4 ||
+ r.clock==UINT32_MAX || (r.clock&0xF0000000U) || r.pll==UINT32_MAX || (r.pll&0xCC000000U) ||
+ r.gate==UINT32_MAX || !(r.gate&Display13TC::Clock::gateBit(d-3)) ||
+ ((auxPower&(1U<<(2*d))) && (!io.readable(aux) || (io.read(aux)&0x80000000U))))return r;
+ if(!stable() || io.read(port.buffer)!=r.buffer || io.read(port.ready)!=r.ready ||
+ io.read(port.clock)!=r.clock || io.read(port.pll)!=r.pll) {r.status=Status::PeerChanged;return r;}
+ io.write(port.buffer,r.buffer&~64U);++r.writes;
+ if(io.read(port.buffer)!=(r.buffer&~64U) || !stable() || io.read(port.clock)!=r.clock ||
+ io.read(port.pll)!=r.pll) {r.status=Status::Readback;return r;}
+ r.cleared|=1U< Result normalize(IO &io,const AlderLakeN::DisplayReadout &boot,bool admitted,
const Display13HDMIWiring &w,const XELPMemory::Layout &memory) {
Result r;r.before=boot.activePipes;
@@ -33,6 +113,11 @@ template Result normalize(IO &io,const AlderLakeN::DisplayReadout &boo
if(!memory.valid || !Display13FBTopology::multiOutputWiring(w) || boot.status!=AlderLakeN::ReadoutStatus::Complete ||
!(active&1) || active==1 || (active&~7U) || (active&~boot.poweredPipes))return r;
uint32_t ports[3] {},clocks[3] {},transport[3] {},used=0,dpPipes=0;
+ Display13TC::Boot tc[3];
+ uint32_t power=0,ddiPower=0,auxPower=0,gate=0;
+ if(!AlderLakeN::value(boot,0x45404,power) || !AlderLakeN::value(boot,0x45454,ddiPower) ||
+ !AlderLakeN::value(boot,0x45444,auxPower) ||
+ !AlderLakeN::value(boot,Display13TC::Clock::Gate,gate))return r;
// Every active firmware pipe must be a distinct selected linear GOP.
// Pipe identity and physical DDI are independent. No foreign cursor/plane/DSB.
for(unsigned p=0;p<3;++p)if(boot.poweredPipes&(1U< Result normalize(IO &io,const AlderLakeN::DisplayReadout &boo
uint32_t ctl=0,trans=0,surface=0,live=0;
if(!AlderLakeN::value(boot,0x70180+bank,ctl) || !AlderLakeN::value(boot,0x60400+bank,trans))return r;
const auto port=Display13FBTopology::bootDDI(w,trans,true);
- if(port>=3 || (used&(1U<>24)&7)==2) {
- if(!AlderLakeN::dpSource(boot,p,port))return r;
+ if(port>=7 || (used&(1U<>24)&7)==2;
+ if(port>=3) {
+ tc[p]=Display13TC::Boot::capture(io,true,port,p,dp);
+ if(!tc[p].valid() || tc[p].power!=power || tc[p].ddiPower!=ddiPower)return r;
+ // A powered AUX controller must be idle before its PHY can be
+ // returned. An unpowered controller cannot have an active command.
+ const auto aux=0x64310+0x100*(port-3);
+ if((auxPower&(1U<<(2*port))) &&
+ (!io.readable(aux) || (io.read(aux)&0x80000000U)))return r;
+ }
+ if(dp) {
+ if(!AlderLakeN::dpSource(boot,p,port,port>=3?&tc[p]:nullptr))return r;
dpPipes|=1U< Result normalize(IO &io,const AlderLakeN::DisplayReadout &boo
(conf&0xC0000000)!=0xC0000000 || !AlderLakeN::value(boot,0x7019C+bank,surface) ||
(surface&4095) || !AlderLakeN::value(boot,0x701AC+bank,live) || live!=surface)return r;
const auto geometry=XELPGeometry::linear(ctl,stride,size,position,offset);
- if(!XELPGeometry::timing(geometry,ht,vt) || uint64_t(surface)+geometry.bytes>memory.usable())return r;
+ if(!AlderLakeN::bootGeometry(boot,p,geometry,ht,vt) || uint64_t(surface)+geometry.bytes>memory.usable())return r;
const auto clock=0x46140+4*p,buf=0x64000+0x100*port;
if(!io.readable(clock) || !io.readable(buf) || !(io.read(buf)&0x80000000) ||
!io.readable(0x60404+bank) || io.read(0x60404+bank))return r;
clocks[p]=io.read(clock);
- if(clocks[p]==UINT32_MAX || (clocks[p]>>28)!=port+1)return r;
+ if(clocks[p]==UINT32_MAX || (clocks[p]>>28)!=port+(port>=3?3:1))return r;
}
for(unsigned i=0;i=3 && !tc[p].unchanged(io)) {
+ r.status=Status::Readback;return r;
+ }
+ uint32_t parked=0,current=0,stoppingTC=0;
const auto peer=[&] {
+ // These are borrowed firmware requests. Keep them unchanged, including
+ // the stopped output's requests; native leases own subsequent changes.
+ if(io.read(0x45404)!=power || io.read(0x45454)!=ddiPower || io.read(0x45444)!=auxPower)return false;
for(unsigned p=0;p<3;++p)if((active&(1U< Result normalize(IO &io,const AlderLakeN::DisplayReadout &boo
for(unsigned p=1;p<3;++p)if((active&(1U<
=0x60000+bank && reg<0x61000+bank) ||
- (reg>=0x70000+bank && reg<0x71000+bank) || reg==0x64000+0x100*ports[p] ||
+ (reg>=0x70000+bank && reg<0x71000+bank) ||
+ (reg>=0x68170+0x800*p && reg<=0x68280+0x800*p) || reg==0x64000+0x100*ports[p] ||
reg==0x46140+4*p)mutableReg=true;
}
if(reg==0x70040 || reg==0x71040 || reg==0x72040)continue;
- if(!mutableReg && io.read(reg)!=pair.value) {
- r.lastReg=reg;r.expected=pair.value;r.observed=io.read(reg);return false;
+ const auto expected=reg==Display13TC::Clock::Gate?gate:pair.value;
+ if(!mutableReg && io.read(reg)!=expected) {
+ r.lastReg=reg;r.expected=expected;r.observed=io.read(reg);return false;
}
}
+ for(unsigned p=0;p<3;++p)if((active&(1U<
=3 && !(stoppingTC&(1U<
Result normalize(IO &io,const AlderLakeN::DisplayReadout &boo
AlderLakeN::value(boot,0x70008+bank,conf);AlderLakeN::value(boot,0x60400+bank,trans);buf=io.read(bufReg);
r.stage=1;r.framesBefore=io.read(0x70040+bank);
write(0x70180+bank,plane&~0x80000000U);write(0x7019C+bank,surface);
- for(unsigned n=0;n<1000;++n) {
- r.framesAfter=io.read(0x70040+bank);
- if(r.framesAfter==UINT32_MAX || !peer()) {r.status=Status::PeerChanged;return r;}
- if(uint32_t(r.framesAfter-r.framesBefore)>=2 && uint32_t(r.framesAfter-r.framesBefore)<0x10000000)break;
- if(n==999) {r.status=Status::Timeout;return r;}io.delay();
- }
+ if(!XELPGOP::poll(io,peer,[&](uint32_t v){return uint32_t(v-r.framesBefore)>=2 && uint32_t(v-r.framesBefore)<0x10000000U;},
+ 0x70040+bank,r.framesAfter,r.status,Status::PeerChanged,false))return r;
if(!wait(0x70180+bank,0x80000000,0))return r;
// Real frame latch, STOP_ACK and buffer idle precede DDB release.
- // Shared PLL/PHY/PW and predecessor mapping remain held.
+ // Keep link resources until consumers stop. The TC PLL/ownership is
+ // then returned below; shared power and predecessor memory stay held.
r.stage=2;write(0x70008+bank,conf&~0x80000000U);
if(!wait(0x70008+bank,0xC0000000,0))return r;
+ // Detach only enabled scalers on the stopped secondary pipe. Keep the
+ // primary pipe's firmware scaling untouched until native ownership.
+ for(unsigned scaler=0;scaler<2;++scaler) {
+ const auto base=0x68170+0x800*p+0x100*scaler;
+ uint32_t ctl=0;
+ if(AlderLakeN::value(boot,base+0x10,ctl) && (ctl&0x80000000U)) {
+ write(base+0x10,0);write(base,0);write(base+4,0);
+ if(!wait(base+0x10,UINT32_MAX,0) || !wait(base,UINT32_MAX,0) || !wait(base+4,UINT32_MAX,0))return r;
+ }
+ }
r.stage=3;write(0x60404+bank,0);write(0x60400+bank,trans&~(0x80000000U|(15U<<27)|(7U<<24)));
if(!wait(0x60400+bank,0x80000000,0))return r;
- r.stage=4;write(bufReg,buf&~0x80000000U);
- // Display12/13 disable_ddi_buf: buffer off, transport off, then idle.
- // Stop only this transcoder; preserve the adopted GOP's M/N and link.
- if(dpPipes&(1U<
=3) {
+ // The live GOP certificate authorized this bootstrap transaction.
+ // Reuse the runtime ownership and PLL operations after consumers
+ // stop. Leaving the firmware ownership bit set would make the
+ // later native Mode::connect reject this now-cold connector.
+ stoppingTC|=1U<
(&io) && object==reinterpret_cast(&boot) &&
+ d==boot.port.ddi && powerHeld();
+ }
+ bool quiescent(unsigned d) {
+ const auto bank=boot.pipe*0x1000;
+ return d==boot.port.ddi && !(read(0x70008+bank)&0xC0000000U) &&
+ !(read(0x60400+bank)&0x80000000U) && !(read(0x60540+bank)&0x80000000U) &&
+ !(read(0x46140+4*boot.pipe)&0xF0000000U) &&
+ (read(boot.port.buffer)&0x80000080U)==0x80;
+ }
+ bool inheritedVerified(uintptr_t owner,uintptr_t object,unsigned d) {
+ return powerHeld(owner,object,d) && quiescent(d);
+ }
+ bool owned(unsigned d){return d==boot.port.ddi && ownership.active;}
+ bool quiescent(uintptr_t owner,uintptr_t object,unsigned d) {
+ return powerHeld(owner,object,d) && quiescent(d) &&
+ !(read(boot.port.pll)&0xCC000000U) && !(read(boot.port.clock)&0xF0000000U) &&
+ (read(Display13TC::Clock::Gate)&Display13TC::Clock::gateBit(boot.port.tc));
+ }
+ } access{io,tc[p],r,peer,write};
+ const auto owner=reinterpret_cast(&io),object=reinterpret_cast(&tc[p]);
+ if(access.ownership.acquire(access,owner,object,ports[p],true).status!=Display13TC::Status::Complete ||
+ !Display13TC::Clock::disable(access,tc[p].port) ||
+ access.ownership.release(access,owner,object,ports[p]).status!=Display13TC::Status::Complete) {
+ if(r.status!=Status::PeerChanged)r.status=Status::Readback;return r;
+ }
+ }
r.stage=6;write(0x7027C+bank,0);write(0x7037C+bank,0);write(0x7017C+bank,0);
if(!wait(0x7027C+bank,UINT32_MAX,0) || !wait(0x7037C+bank,UINT32_MAX,0) || !wait(0x7017C+bank,UINT32_MAX,0))return r;
parked|=1U<>3)<<24);}
+// Linux GEN11_DE_HPD: TC1..6 at bits16..21, TBT1..6 at bits0..5.
+// Apply the same connector exclusion to both PCH and north HPD banks.
+// Reference: samranahmad09/WhateverXe-RaptorLakeP, 2195924, this header.
+constexpr uint32_t HPDBank=0x44478;
+constexpr uint32_t hpdExcluded(uint32_t excludedPCH) {
+ const uint32_t ports=(excludedPCH>>24)&0x3FU;return ports|(ports<<16);
+}
inline uint32_t ddiMask(const State &s,uint32_t value) {
uint32_t m=0;for(unsigned p=0;p<3;++p)if(s.ports&(1U<
Status enable(IO &io,State &s,uint32_t excludedPCH=0,uint32_t
if(s.faulted)return Status::Faulted;
if(!ports || (ports&~0x7FU) || (s.begun && ports!=s.ports))return fail(io,s,Status::Topology);
s.ports=ports;
- if((excludedPCH&pchOwned(s)) || (excludedPCH&~PCHTypeC) || (sharedGT && sharedGT!=NativeGT) || !expectedPipes || (expectedPipes&~7U))return fail(io,s,Status::Topology);
+ if((excludedPCH&pchOwned(s)) || (excludedPCH&~(PCHTypeC|PCHCombo)) || (sharedGT && sharedGT!=NativeGT) || !expectedPipes || (expectedPipes&~7U))return fail(io,s,Status::Topology);
if(!available(io) || ((ports&0x78) && !io.readable(TCControl)))return Status::Unavailable;
// Native repeated enable is idempotent. Do not drop a pending flip/DSB
// completion or undo hwSetInterruptForTransaction's VBL mask.
@@ -142,9 +149,15 @@ template Status enable(IO &io,State &s,uint32_t excludedPCH=0,uint32_t
configured|=1U< Status enable(IO &io,State &s,uint32_t excludedPCH=0,uint32_t
if((io.read(0xC400C)&excludedPCH) || (~io.read(0xC4004)&excludedPCH))
return fail(io,s,Status::Readback);
}
+ if(excludedHPD) {
+ // Keep pending north status and every unrelated source intact.
+ io.write(HPDBank-4,io.read(HPDBank-4)|excludedHPD);
+ io.write(HPDBank+4,io.read(HPDBank+4)&~excludedHPD);
+ if((io.read(HPDBank+4)&excludedHPD) || (~io.read(HPDBank-4)&excludedHPD))
+ return fail(io,s,Status::Readback);
+ }
s.excludedPCH=excludedPCH;
for(unsigned p=0;p<3;++p)if(configured&(1U<
#include "kern_xelp_memory_layout.hpp"
#include "kern_xelp_geometry.hpp"
+#include "kern_display13_tc.hpp"
+#include "kern_alderlake_n.hpp"
#include
#include
@@ -326,12 +328,21 @@ template SurfaceReadout inspectSurface(IO &io,const void *controller,s
template Readout inspect(IO &io,const void *controller,size_t controllerBytes,
const void *framebuffer,size_t framebufferBytes,
uint64_t apertureBytes,const XELPMemory::Layout &memory,bool bootScanout=true,uintptr_t expectedOwner=0,
- unsigned expectedDDI=1) {
+ unsigned expectedDDI=1,const Display13TC::Boot *tc=nullptr,
+ const AlderLakeN::DisplayReadout *dp=nullptr) {
Readout r;
if (!associations(controller,controllerBytes,framebuffer,framebufferBytes,expectedOwner)) {
r.status=Status::Association;return r;
}
- if(bootScanout && expectedDDI>=3) {r.status=Status::DisplayState;return r;}
+ if(bootScanout && (expectedDDI>=7 || (expectedDDI>=3 &&
+ (!tc || !tc->valid() || tc->port.ddi!=expectedDDI || tc->pipe!=0 ||
+ !(tc->buffer&64) || (!dp && tc->ownershipOptional) || !tc->unchanged(io))))) {
+ r.status=Status::DisplayState;return r;
+ }
+ if(bootScanout && dp && (dp->activePipes!=1 ||
+ !AlderLakeN::dpSourceUnchanged(io,*dp,0,expectedDDI,0,tc))) {
+ r.status=Status::DisplayState;return r;
+ }
r.pool=pool(controller);const auto &p=r.pool;
if (field(framebuffer,0x4288)!=0 || !field(controller,0xDF5) ||
field(controller,0xDF5)>3 || !field(controller,0xDF4) ||
@@ -362,7 +373,7 @@ template Readout inspect(IO &io,const void *controller,size_t controll
r.surface=io.read(0x7019C);r.control=io.read(0x70180);r.stride=io.read(0x70188);r.size=io.read(0x70190);
const auto geometry=XELPGeometry::linear(r.control,r.stride,r.size,position,offset);
if (!(dpt&(1U<<30)) || !(trans&(1U<<31)) || ((trans>>27)&15)!=expectedDDI+1 ||
- ((trans>>24)&7)>1 || ((trans>>20)&7) || (conf&0xC0000000)!=0xC0000000 ||
+ (dp ? ((trans>>24)&7)!=2 : ((trans>>24)&7)>1) || ((trans>>20)&7) || (conf&0xC0000000)!=0xC0000000 ||
!geometry.valid ||
position || offset || (r.surface&4095) ||
r.surface!=io.read(0x701AC) || r.surface Readout inspect(IO &io,const void *controller,size_t controll
(bootScanout && (io.read(0x42084)!=dpt || io.read(0x60400)!=trans || io.read(0x70008)!=conf ||
io.read(0x7018C)!=position || io.read(0x701A4)!=offset ||
io.read(0x7019C)!=r.surface || io.read(0x701AC)!=r.surface ||
- io.read(0x70180)!=r.control || io.read(0x70188)!=r.stride || io.read(0x70190)!=r.size))) {
+ io.read(0x70180)!=r.control || io.read(0x70188)!=r.stride || io.read(0x70190)!=r.size ||
+ (expectedDDI>=3 && !tc->unchanged(io)) ||
+ (dp && !AlderLakeN::dpSourceUnchanged(io,*dp,0,expectedDDI,0,tc))))) {
r.status=Status::StateChanged;return r;
}
r.status=Status::Complete;return r;
diff --git a/WhateverXe/kern_display13_fb_topology.hpp b/WhateverXe/kern_display13_fb_topology.hpp
index b27cb2de..19157990 100644
--- a/WhateverXe/kern_display13_fb_topology.hpp
+++ b/WhateverXe/kern_display13_fb_topology.hpp
@@ -1,4 +1,4 @@
-// Explicit Combo A/B/C wiring. Board wiring and native backend
+// Explicit Combo/TC wiring. Board wiring and native backend
// qualification are separate: describing a port does not attach unsupported resources.
#ifndef KERN_DISPLAY13_FB_TOPOLOGY_HPP
#define KERN_DISPLAY13_FB_TOPOLOGY_HPP
@@ -15,7 +15,16 @@ inline bool selectedWiring(const Display13OutputWiring &w) {
inline bool adlnWiring(const Display13OutputWiring &w) {return selectedWiring(w) && !(w.selected()&~3U);}
// DP topology is composable, but runtime admission stays closed until its
// complete signal/training/bootstrap operation contract is installed.
-inline bool supportedWiring(const Display13OutputWiring &w) {return adlnWiring(w) && !w.dp;}
+inline bool supportedWiring(const Display13OutputWiring &w,bool fixedTC=false) {
+ // Fixed HDMI is composable across the physical Combo A/B and TC1..4.
+ // TC provider capability, legacy mode and clock hooks are checked separately.
+ return !(w.dp&~0x78U) && w.described() && !(w.selected()&~(fixedTC ? Display13OutputWiring::Physical : 3U));
+}
+// A TC route uses the same validated handoff regardless of the GT family.
+// Keep the established Combo-only ADL-N startup ordering unchanged.
+inline bool deferredBootPrepare(const Display13OutputWiring &w,bool mobile) {
+ return mobile || (w.selected()&0x78);
+}
inline bool multiOutputWiring(const Display13OutputWiring &w) {
return selectedWiring(w) && (w.selected()&(w.selected()-1));
}
@@ -24,7 +33,7 @@ inline bool singleOutputWiring(const Display13OutputWiring &w) {
}
inline uint32_t singleDDI(const Display13OutputWiring &w) {
if(!singleOutputWiring(w))return UINT32_MAX;
- for(unsigned d=0;d<3;++d)if(w.selected()&(1U<>27)&15;
- if(selector<1 || selector>3)return UINT32_MAX;
+ if(selector<1 || selector>7)return UINT32_MAX;
const auto port=selector-1;
const auto mode=(trans>>24)&7;
- // Explicit source inspection only; runtime adoption keeps the TMDS default.
- if(dpSource && mode==2 && port<2 && (w.dp&(1U<=3) && (w.dp&(1U<1)return UINT32_MAX;
return (w.hdmi&(1U<(config)))return true;
return false;
}
@@ -82,29 +96,25 @@ inline bool selected(const void *platform,const void *config,const Display13Outp
// core below consumes only validated port identities, independent of transport.
inline XELP::NativePorts boundPorts(const void *platform,const Display13OutputWiring &w) {
if(!platform || !selectedWiring(w))return {};
- for(unsigned d=0;d<3;++d)if((w.selected()&(1U<(platform)+0x20+24*d,w))return {};
return {platform,w.selected()};
}
-// Same exact donor CPU defect corrected for MI646: the mode validator
-// asks a TMDS connector for DP lanes. Keep its remaining HDMI constraints.
-inline bool skipDPLanes(const void *platform,const void *config,const Display13OutputWiring &w,
- bool exactCaller,bool native,uint32_t available,uint32_t lanes) {
- return XELPDonor::skipHDMIDPLanes(selected(platform,config,w) && w.matchesHDMI(static_cast(config)),exactCaller,native,available,lanes);
-}
// ICP/TGP SDE bits24..29 are TC1..6 hotplug, not legacy GMBUS bit24.
-// Authorize masking only from the stable native connector policy: no TC
-// record can be enabled or have panel/connection flags on a Combo route.
-inline uint32_t excludedTypeC(const void *platform,uint32_t bytes,const Display13OutputWiring &w) {
+// Combo A..C hotplug uses bits16..18. Authorize exclusions only after
+// every unselected native record is disabled and has no connection flags.
+// Other PCH sources (including DDI D) remain outside this policy.
+inline uint32_t excludedHotplug(const void *platform,uint32_t bytes,const Display13OutputWiring &w) {
if(!platform || bytes(platform);
if(p[0x12]!=Ports)return 0;
- for(unsigned d=0;d<3;++d)if((w.selected()&(1U<(p+0x20+24*d);
- if(config[0]!=d || (d>=3 && (config[4]!=1 || config[5])))return 0;
+ if(config[0]!=d || (!(w.selected()&(1U<>3)<<24)) |
+ ((7U & ~w.selected())<<16);
}
}
#endif
diff --git a/WhateverXe/kern_display13_firmware.hpp b/WhateverXe/kern_display13_firmware.hpp
new file mode 100644
index 00000000..abbb98f1
--- /dev/null
+++ b/WhateverXe/kern_display13_firmware.hpp
@@ -0,0 +1,40 @@
+#ifndef KERN_DISPLAY13_FIRMWARE_HPP
+#define KERN_DISPLAY13_FIRMWARE_HPP
+#include "kern_vbt.hpp"
+#include "kern_display13_output_wiring.hpp"
+namespace XELP { namespace VBT {
+ // ADLP/ADLN VBT DDC1/2 => Combo pins1/2, DDC3..6 => TC pins9..12.
+ // Capability bits are not live modes. HDMI-only records may advertise
+ // USB-C/TBT capability; the TC runtime must separately prove legacy mode.
+ inline Display13OutputWiring display13HDMI(const Ports &ports,uint32_t backend) {
+ Display13OutputWiring w;w.valid=true;
+ if(!ports.valid())return w;
+ uint32_t used=0;
+ for(unsigned d=0;d<7;++d) {
+ if(!(ports.present&backend&Display13OutputWiring::Physical&(1U<=3 && selector<=6 ? selector+6 : selector<=2 ? selector : 0;
+ if(!pin || (d<3 ? pin>2 : pin<9) || (used&(1U<3 || (used&(1U<3) : (pin<9 || pin>12))return false;
+ if(used&(1U<=3?1U:0U) &&
+ c[4]==0x800 && (c[5]&0xB)==2;
+ }
+ bool usesDDC(uint32_t bus) const {
+ if(!described())return false;
+ for(unsigned d=0;d=3)return false;
- if(hdmi&(1U<=Ports)return false;
+ if(hdmi&(1U<(platform);if(p[0x12]!=Ports)return false;
+ for(unsigned d=0;d(p+0x20+24*d);
+ const uint32_t panel[]={0,0,0,0,2,0x18};
+ if(!d && !memcmp(c,panel,24))continue;
+ if(c[0]!=d || c[1]!=(d<3?d+1:d+6) || c[2]!=1 || c[3]!=(d<3?0U:1U) ||
+ c[4]!=0x400 || c[5]!=0x201)return false;
+ }
+ for(unsigned d=0;d(p+0x20+24*d);
+ const bool panel=!d && !c[2];
+ c[1]=ddc[d];c[2]=2;c[4]=0x800;c[5]=panel?0x202:(c[5]&~9U)|2U;
+ }
+ return TGLComboDP::apply(dp&3,platform,bytes);
}
};
#endif
diff --git a/WhateverXe/kern_display13_power.hpp b/WhateverXe/kern_display13_power.hpp
index 2685b86a..d036f4d9 100644
--- a/WhateverXe/kern_display13_power.hpp
+++ b/WhateverXe/kern_display13_power.hpp
@@ -34,9 +34,10 @@ constexpr uint32_t fuse(unsigned d) { return 1U << (26 - index(d)); }
// PW2 is not a prerequisite of PW_A in the XE_LPD power-domain map.
inline uint32_t dependencies(uint32_t domains) {
if (domains & ~All) return 0;
- // XE_LPD TC legacy AUX uses PW2 even while its scanout is pipe A.
- constexpr uint32_t tcAuxMask=bit(Domain::AUXTC1)|bit(Domain::AUXTC2)|bit(Domain::AUXTC3)|bit(Domain::AUXTC4);
- if (domains & (bit(Domain::PipeB) | bit(Domain::PipeC) | tcAuxMask)) domains |= bit(Domain::PW2);
+ // XE_LPD PW2 includes both TC DDI lanes and AUX, even on pipe A.
+ // Releasing an AUX transaction must not gate an active TMDS main link.
+ constexpr uint32_t tcMask=All&~((1U<<11)-1);
+ if (domains & (bit(Domain::PipeB) | bit(Domain::PipeC) | tcMask)) domains |= bit(Domain::PW2);
if (domains) domains |= bit(Domain::PW1);
return domains;
}
@@ -48,6 +49,7 @@ enum class Status : uint32_t {
struct Result {
Status status {Status::NotReady};
uint32_t domain {Count}, writes {}, rollbackFailed {}, forcedOn {};
+ bool isolated {}; // Failed new TC leaf, rollback and retained scopes proven.
};
struct State {
bool ready {}, faulted {}, suspended {};
@@ -155,7 +157,9 @@ template Status waitTCAuxHealth(IO &io,unsigned d) {
}
}
-template Result acquire(IO &io, State &s, Lease &lease, uint32_t domains) {
+// Isolation is opt-in for a caller that can propagate a failed acquisition.
+// Native void getPower wrappers must retain controller-wide failure behavior.
+template Result acquire(IO &io, State &s, Lease &lease, uint32_t domains,bool isolateTC=false) {
Result r;
if (!s.ready || s.faulted || s.suspended) return r;
if (lease.owner || !domains || (domains & ~s.allowed)) { r.status = Status::InvalidDomains; return r; }
@@ -172,6 +176,7 @@ template Result acquire(IO &io, State &s, Lease &lease, uint32_t domai
if ((domains & (1U << d)) && s.refs[d] == UINT32_MAX) {
r.status = Status::Overflow; r.domain = d; return r;
}
+ const uint32_t previousOwned=s.owned;
uint32_t added = 0;
r.status = Status::Complete;
for (unsigned d = 0; d < Count; ++d) {
@@ -210,10 +215,13 @@ template Result acquire(IO &io, State &s, Lease &lease, uint32_t domai
r.domain = Count;
return r;
}
- // Failure is latched; no caller may continue using partially acquired
- // domains. Roll back only requests made by this transaction, in reverse
- // dependency order. Stop at a failed release to retain its prerequisites.
- s.faulted = true;
+ // A newly requested, unshared TC leaf may fail without invalidating other
+ // outputs. Only isolate bounded ack/health timeouts after proving rollback
+ // and every previously held scope. Lost/borrowed/shared resources, unknown
+ // MMIO and failed rollback still poison the controller.
+ const bool local=isolateTC && (tcDDI(r.domain)||tcAux(r.domain)) && !s.refs[r.domain] &&
+ (added&(1U< 0;) {
if (!(added & (1U << d))) continue;
if (clear(io, s, d, r) != Status::Complete) {
@@ -221,6 +229,11 @@ template Result acquire(IO &io, State &s, Lease &lease, uint32_t domai
break;
}
}
+ uint32_t retained=0;
+ for(unsigned d=0;d bool ddiHeld(IO &io,unsigned port) const {
return retainsDDI(port) && held(io,state,ddiDomain(port));
}
- template bool outputHeld(IO &io,unsigned port,unsigned pipe) const {
- if(pipe>=3 || !retainsDDI(port))return false;
+ template bool pipeHeld(IO &io,unsigned pipe) const {
+ if(pipe>=3)return false;
const auto domain=bit(static_cast(static_cast(Domain::PipeA)+pipe));
bool retained=false;
for(unsigned pg=0;pg<4;++pg)if(pgRefs[pg] && pgLease[pg].owner==&state &&
pgLease[pg].domains==dependencies(nativeScope(pg+1)) && (pgLease[pg].domains&domain))retained=true;
- return retained && held(io,state,domain|ddiDomain(port));
+ return retained && held(io,state,domain);
+ }
+ template bool outputHeld(IO &io,unsigned port,unsigned pipe) const {
+ return pipeHeld(io,pipe) && ddiHeld(io,port);
}
template Result begin(IO &io, unsigned port=1, uint32_t ports=2) {
diff --git a/WhateverXe/kern_display13_retirement.hpp b/WhateverXe/kern_display13_retirement.hpp
new file mode 100644
index 00000000..a6bcb17e
--- /dev/null
+++ b/WhateverXe/kern_display13_retirement.hpp
@@ -0,0 +1,25 @@
+// Exact c004fc1a Display13 retirement boundary; native startup stays intact.
+#ifndef KERN_DISPLAY13_RETIREMENT_HPP
+#define KERN_DISPLAY13_RETIREMENT_HPP
+#include
+#include
+namespace Display13Retirement {
+constexpr uint32_t Loop=0x67ebd,Continue=0x67eec,Abort=0x67f85;
+constexpr uint8_t Before[]={0x48,0xff,0x5,0x64,0xc6,0x7,0x0,0x83,0xbb,0xe8,0x49,0x0,0x0,0x0,0x74,0x1f,0x48,0xff,0x5,0xf4,0xc5,0x7,0x0,0x48,0xff,0x5,0x25,0xc8,0x7,0x0,0x48,0x8b,0xb3,0xf0,0x49,0x0,0x0,0x48,0x89,0xdf,0xe8,0xc4,0xb,0x0,0x0,0xeb,0xd1};
+// AGDC's void free call now returns status in EAX. Keep its native argument
+// validation, call, and epilogue; remove only the unconditional success write.
+constexpr uint32_t ReleaseCall=0x6ba3a,ReleaseResult=0x6ba3f;
+constexpr uint8_t ReleaseBefore[]={0xe8,0x4f,0xc4,0xff,0xff,0x31,0xc0};
+constexpr uint8_t ReleaseAfter[]={0xe8,0x4f,0xc4,0xff,0xff,0x90,0x90};
+inline void patch(uint8_t (&out)[sizeof(Before)],uintptr_t handler) {
+ memset(out,0x90,sizeof(out));
+ // RBX is the retained FB. Keep native StopTransactions/disableDisplay and
+ // post-retirement cleanup; refusal exits before scanout/logical cleanup.
+ const uint8_t code[]={0x48,0x89,0xdf,0x48,0xb8,0,0,0,0,0,0,0,0,0xff,0xd0,
+ 0x85,0xc0,0x0f,0x85,0,0,0,0,0xe9,0,0,0,0};
+ memcpy(out,code,sizeof(code));memcpy(out+5,&handler,8);
+ const int32_t abortDelta=Abort-(Loop+23),doneDelta=Continue-(Loop+28);
+ memcpy(out+19,&abortDelta,4);memcpy(out+24,&doneDelta,4);
+}
+}
+#endif
diff --git a/WhateverXe/kern_display13_tc.hpp b/WhateverXe/kern_display13_tc.hpp
index f8dd5a99..c30d1ac0 100644
--- a/WhateverXe/kern_display13_tc.hpp
+++ b/WhateverXe/kern_display13_tc.hpp
@@ -1,4 +1,4 @@
-// ADL-N/Twin Lake fixed TC HDMI/DP coordinates and PHY ownership.
+// ADL-P/N fixed TC HDMI/DP coordinates and PHY ownership.
// Linux v6.12 intel_tc.c/i915_reg.h; T8 Pro TC1 Linux values are an
// independent live fixture. These are not TGL FIA ownership registers.
#ifndef KERN_DISPLAY13_TC_HPP
@@ -24,8 +24,55 @@ struct Port {
};
enum class Status : uint32_t {Invalid,NoPower,NeedsAdoption,InUse,Unavailable,UnsupportedMode,NotReady,Readback,Complete};
struct Result {Status status {Status::Invalid};uint32_t before {},after {},writes {},elapsedUs {};};
-// Caller keeps lifecycle/port exclusion, live mapping, and separate DDI,
-// lane/PW2 and qualified legacy AUX leases for the entire transaction.
+// Read-only physical certificate for an inherited fixed link. This never takes
+// ownership or programs the PLL. Callers must select the physical Display13
+// backend before reading TCSS; TGL uses a different ownership mechanism.
+// Protocol/source, sink and PLL frequency are separate certificates. An
+// enabled/locked PLL alone does not prove a valid DP link or authorize adoption.
+struct Boot {
+ Port port;unsigned pipe {3};
+ uint32_t power {},ddiPower {},ready {},buffer {},clock {},pll {},transClock {};
+ // Linux intel_tc_port_init_mode reconnects an enabled fixed legacy port
+ // left unowned by firmware. Only explicit DP source validation may request
+ // this witness; the later Mode acquisition must still take ownership.
+ bool ownershipOptional {};
+ bool valid() const {
+ const auto powered=pipe<3 ? 5U|(1U<<(10+2*pipe)) : 0U;
+ return port.ddi>=3 && port.ddi<=6 && pipe<3 &&
+ power!=UINT32_MAX && (power&powered)==powered && ddiPower!=UINT32_MAX &&
+ (ddiPower&(1U<<(2*port.ddi))) && ready!=UINT32_MAX && (ready&7)==4 &&
+ buffer!=UINT32_MAX && (buffer&0x80000080U)==0x80000000U && (ownershipOptional || (buffer&64)) &&
+ clock!=UINT32_MAX && (clock>>28)==8 && pll!=UINT32_MAX &&
+ (pll&0xCC000000U)==0xCC000000U && transClock!=UINT32_MAX && (transClock>>28)==port.ddi+3;
+ }
+ template static Boot capture(IO &io,bool admitted,unsigned ddi,unsigned pipe,bool ownershipOptional=false) {
+ Boot b;
+ if(!admitted || pipe>=3 || !Port::resolve(ddi,b.port))return b;
+ b.pipe=pipe;b.ownershipOptional=ownershipOptional;
+ if(!io.readable(0x45404) || !io.readable(0x45454))return {};
+ b.power=io.read(0x45404);b.ddiPower=io.read(0x45454);
+ const auto powered=5U|(1U<<(10+2*pipe));
+ if(b.power==UINT32_MAX || (b.power&powered)!=powered || b.ddiPower==UINT32_MAX ||
+ !(b.ddiPower&(1U<<(2*ddi))))return {};
+ const uint32_t regs[]={b.port.ready,b.port.buffer,b.port.clock,b.port.pll,0x46140+4*pipe};
+ uint32_t *values[]={&b.ready,&b.buffer,&b.clock,&b.pll,&b.transClock};
+ for(unsigned i=0;i<5;++i) {
+ if(!io.readable(regs[i]))return {};
+ *values[i]=io.read(regs[i]);
+ }
+ if(io.read(0x45404)!=b.power || io.read(0x45454)!=b.ddiPower || !b.valid())return {};
+ return b;
+ }
+ template bool unchanged(IO &io) const {
+ if(!valid())return false;
+ const auto now=capture(io,true,port.ddi,pipe,ownershipOptional);
+ return now.valid() && now.power==power && now.ddiPower==ddiPower && now.ready==ready &&
+ now.buffer==buffer && now.clock==clock && now.pll==pll && now.transClock==transClock;
+ }
+};
+// Caller keeps lifecycle/port exclusion, live mapping and lane/PW2 access.
+// ADLP ownership precedes AUX cold blocking and DDI IO acquisition. Requiring
+// those later scopes here creates an inverted dependency on a cold port.
// A matching owner identity is insufficient without that power witness.
struct Ownership {
uintptr_t owner {},identity {};Port port;bool active {};
@@ -35,14 +82,9 @@ struct Ownership {
if(owner && (owner!=who || identity!=object || port.ddi!=ddi)){r.status=Status::InUse;return r;}
if(!io.powerHeld(who,object,ddi)){r.status=Status::NoPower;return r;}
if(!io.readable(p.ready) || !io.readable(p.buffer)){r.status=Status::Unavailable;return r;}
- for(;;) {
- const auto ready=io.read(p.ready);
- if(ready==UINT32_MAX){r.status=Status::Unavailable;return r;}
- if(ready&3){r.status=Status::UnsupportedMode;return r;} // DP-alt/TBT excluded here.
- if(ready&4)break;
- if(r.elapsedUs==500000){r.status=Status::NotReady;return r;}
- io.delayUs(100);r.elapsedUs+=100;
- }
+ const auto initialReady=io.read(p.ready);
+ if(initialReady==UINT32_MAX){r.status=Status::Unavailable;return r;}
+ if(initialReady&3){r.status=Status::UnsupportedMode;return r;}
r.before=io.read(p.buffer);if(r.before==UINT32_MAX){r.status=Status::Unavailable;return r;}
if(owner && active && !(r.before&64)) {r.status=Status::Readback;return r;}
if(!owner && ((r.before&0x80000040U)!=0) && (!adopt || !io.inheritedVerified(who,object,ddi))) {
@@ -55,6 +97,16 @@ struct Ownership {
r.after=io.read(p.buffer);
if(r.after==UINT32_MAX){r.status=Status::Unavailable;return r;}
if(r.after!=(r.before|64)){r.status=Status::Readback;return r;}
+ // Linux adlp_tc_phy_connect takes ownership before testing ready.
+ // Retain the identity on timeout so cleanup can release only our bit.
+ for(;;) {
+ const auto ready=io.read(p.ready);
+ if(ready==UINT32_MAX){r.status=Status::Unavailable;return r;}
+ if(ready&3){r.status=Status::UnsupportedMode;return r;}
+ if(ready&4)break;
+ if(r.elapsedUs==500000){r.status=Status::NotReady;return r;}
+ io.delayUs(100);r.elapsedUs+=100;
+ }
active=true;r.status=Status::Complete;return r;
}
template Result release(IO &io,uintptr_t who,uintptr_t object,unsigned ddi) {
diff --git a/WhateverXe/kern_display13_tc_clock.hpp b/WhateverXe/kern_display13_tc_clock.hpp
new file mode 100644
index 00000000..d97c8e04
--- /dev/null
+++ b/WhateverXe/kern_display13_tc_clock.hpp
@@ -0,0 +1,186 @@
+// Fixed TMDS on ADLP Dekel PHY. Equations/register fields checked against
+// Intel display definitions and Linux v6.12; independent integer implementation.
+#ifndef KERN_DISPLAY13_TC_CLOCK_HPP
+#define KERN_DISPLAY13_TC_CLOCK_HPP
+#include "kern_display13_tc.hpp"
+namespace Display13TC {
+struct TMDSClock {
+ struct Word {uint32_t reg,mask,value;};
+ uint32_t rate {},reference {},dco {},hs {},ds {};
+ Word words[8] {};
+ bool valid() const {return rate && reference && dco && hs && ds;}
+ static TMDSClock calculate(uint32_t rate,uint32_t reference) {
+ TMDSClock p;
+ if(rate<25000 || rate>600000 || (reference!=19200 && reference!=24000 && reference!=38400))return p;
+ const uint32_t high[]={7,5,3,2};
+ for(auto h:high) {
+ for(unsigned d=10;d;--d) {
+ const auto oscillator=uint64_t(rate)*5*h*d;
+ if(oscillator<7992000 || oscillator>10000000)continue;
+ const auto divider=reference*2,integer=uint32_t(oscillator/divider),remainder=uint32_t(oscillator%divider);
+ if(!integer || integer>255)return {};
+ const auto fraction=uint32_t((uint64_t(remainder)<<22)/divider);
+ const uint32_t integral=oscillator>=9000000?10:8,proportional=integral/2;
+ const auto target=uint32_t((2000000000ULL/(132*reference)+5)/10);
+ const auto gain=remainder?uint32_t(200000000ULL/(oscillator*3/10)):0;
+ if(target>255 || gain>255)return {};
+ const uint32_t ratio=h==7?3:h==5?2:h==3?1:0;
+ p.rate=rate;p.reference=reference;p.dco=uint32_t(oscillator);p.hs=h;p.ds=d;
+ p.words[0]={0x212c,0x700,0x100};
+ p.words[1]={0x20d8,0xff00,0x500};
+ p.words[2]={0x20d4,0x1ff00,0x10000|(d>=2?0x4000U:0U)|(ratio<<12)|(d<<8)};
+ // Preserve firmware AFC startup unless VBT supplies an explicit policy.
+ p.words[3]={0x2200,0x1fffff,(integral<<16)|(proportional<<12)|0x200|integer};
+ p.words[4]={0x2204,0x1f00ff,((reference==24000?25U:28U)<<16)|target};
+ p.words[5]={0x2210,0xe0ff3a00,(reference==38400?0x40000000U:0x20000000U)|0x2000};
+ p.words[6]={0x2214,0x7fffff00,(fraction?0x40000000U:0U)|(fraction<<8)};
+ p.words[7]={0x2218,0xffff,gain};
+ return p;
+ }
+ }
+ return {};
+ }
+ // Exact donor ClockParams is DCO integer/fraction15, P/Q/K. Its
+ // calculatePllDivisors uses 19.2 MHz for both DSSM codes1 and2, and
+ // 24 MHz for code0. Recover TMDS rate before encoding the real Dekel PLL.
+ static uint32_t nativeRate(const uint32_t *params,uint32_t refCode) {
+ if(!params || refCode>2 || !params[0] || params[0]>1023 || params[1]>32767 ||
+ !params[3] || params[3]>255 || !params[4] || params[4]>3)return 0;
+ const auto p=params[2];if(p!=2 && p!=3 && p!=5 && p!=7)return 0;
+ const uint64_t denominator=uint64_t(5)*p*params[3]*params[4]*32768;
+ const auto rate=(uint64_t(refCode?19200:24000)*(uint64_t(params[0])*32768+params[1])+denominator/2)/denominator;
+ return rate>=25000 && rate<=600000 ? uint32_t(rate) : 0;
+ }
+ static uint32_t frequency(uint32_t reference,uint32_t div0,uint32_t bias,uint32_t hsctl) {
+ if((reference!=19200 && reference!=24000 && reference!=38400) ||
+ div0==UINT32_MAX || bias==UINT32_MAX || hsctl==UINT32_MAX)return 0;
+ const uint32_t high[]={2,3,5,7};const auto h=high[(hsctl>>12)&3],d=(hsctl>>8)&15;
+ const auto pre=(div0>>8)&15,integer=div0&255,fraction=(bias&0x40000000)?((bias>>8)&0x3fffff):0;
+ if(!d || !pre || !integer)return 0;
+ return uint32_t((uint64_t(reference)*pre*((uint64_t(integer)<<22)+fraction)+(uint64_t(5*h*d)<<21))/(uint64_t(5*h*d)<<22));
+ }
+};
+// One masked bank transaction. The caller holds the native port/lifecycle
+// mutex and all leases; HIP has its own IRQ-safe lock shared across ports.
+struct PHY {
+ template static bool access(IO &io,const Port &p,uint32_t internal,uint32_t mask,uint32_t &value,bool write) {
+ Port checked;if(!Port::resolve(p.ddi,checked) || p.phy!=checked.phy || p.hip!=checked.hip || p.shift!=checked.shift ||
+ internal>0x2fff || (internal&3) || !io.powerHeld() || !io.readable(p.hip) || !io.readable(p.phyRegister(internal)))return false;
+ const auto irq=io.lock();const auto prior=io.read(p.hip);bool changed=false,ok=prior!=UINT32_MAX;
+ if(ok) {
+ const auto bank=p.bank(prior,internal);changed=bank!=prior;
+ if(changed)io.write(p.hip,bank);
+ ok=io.read(p.hip)==bank;
+ if(ok) {
+ const auto reg=p.phyRegister(internal),old=io.read(reg);ok=old!=UINT32_MAX;
+ if(ok && write) {const auto desired=(old&~mask)|(value&mask);io.write(reg,desired);ok=io.read(reg)==desired;value=desired;}
+ else if(ok)value=old;
+ }
+ if(changed) {io.write(p.hip,prior);if(io.read(p.hip)!=prior){ok=false;io.bankFault();}}
+ }
+ io.unlock(irq);return ok;
+ }
+};
+struct TMDSSignal {
+ // Public Intel HDMI preset coefficients: vswing/preshoot/de-emphasis.
+ // ADLP lane0 uses LOADGENSELECT 0/2, lane1 3/3. This is independent of
+ // the electrical preset and must not inherit DP's 0/0 settings.
+ template static bool program(IO &io,const Port &p,uint32_t preset) {
+ if(!io.powerHeld() || !io.owned(p.ddi) || !io.quiescent(p.ddi))return false;
+ if(preset==UINT32_MAX)preset=9;
+ if(preset>=10)return false;
+ const uint32_t swing[]={7,6,4,2,0,0,0,0,0,0};
+ const uint32_t emphasis[]={0,0,0,0,0,5,6,7,8,10};
+ for(unsigned lane=0;lane<2;++lane) {
+ const auto bank=0x1000*lane;
+ // Fixed legacy HDMI occupies both lane pairs (X2 in each bank).
+ uint32_t mode=0x80;
+ if(!PHY::access(io,p,bank+0xa0,0xc0,mode,true))return false;
+ uint32_t wake=0;
+ if(!PHY::access(io,p,bank+0xd00,UINT32_MAX,wake,true))return false;
+ for(unsigned tx=0;tx<2;++tx) {
+ uint32_t value=swing[preset]|(emphasis[preset]<<8);
+ if(!PHY::access(io,p,bank+0x2c0+4*tx,0x3ff07,value,true))return false;
+ }
+ uint32_t load=lane?0x78:0x40;
+ if(!PHY::access(io,p,bank+0x2c8,0x7c,load,true))return false;
+ }
+ return true;
+ }
+};
+
+struct Clock {
+ static constexpr uint32_t Gate=0x164280,Enable=1U<<31,Locked=1U<<30,Power=1U<<27,Powered=1U<<26;
+ static uint32_t gateBit(unsigned tc) {return tc<3?1U<<(12+tc):tc==3?1U<<21:0;}
+ // Runtime IO validates the immutable owner/config/path bindings. A ready
+ // register alone never authorizes touching an active/foreign clock.
+ template static bool preflight(IO &io,const Port &p) {
+ Port checked;
+ if(!Port::resolve(p.ddi,checked) || p.pll!=checked.pll || p.clock!=checked.clock || p.ready!=checked.ready ||
+ p.buffer!=checked.buffer || p.tc!=checked.tc || p.phy!=checked.phy || p.hip!=checked.hip || p.shift!=checked.shift ||
+ !io.powerHeld() || !io.owned(p.ddi) || !io.quiescent(p.ddi))return false;
+ const uint32_t registers[]={p.pll,p.clock,p.ready,p.buffer,Gate};
+ for(auto reg:registers)if(!io.readable(reg) || io.read(reg)==UINT32_MAX)return false;
+ return (io.read(p.ready)&7)==4 && (io.read(p.buffer)&0x80000040)==64;
+ }
+ template static bool rmw(IO &io,uint32_t reg,uint32_t mask,uint32_t value) {
+ const auto irq=io.lock();const auto old=io.read(reg);
+ bool ok=old!=UINT32_MAX;
+ if(ok) {const auto desired=(old&~mask)|(value&mask);io.write(reg,desired);ok=(io.read(reg)&mask)==(desired&mask);}
+ io.unlock(irq);return ok;
+ }
+ template static bool wait(IO &io,uint32_t reg,uint32_t mask,uint32_t expected) {
+ for(unsigned elapsed=0;;elapsed+=10) {
+ const auto v=io.read(reg);if(v==UINT32_MAX)return false;
+ if((v&mask)==expected)return true;
+ if(elapsed==1000)return false;io.delayUs(10);
+ }
+ }
+ template static bool disable(IO &io,const Port &p) {
+ if(!preflight(io,p))return false;
+ return rmw(io,Gate,gateBit(p.tc),gateBit(p.tc)) && rmw(io,p.clock,0xf0000000,0) &&
+ rmw(io,p.pll,Enable,0) && wait(io,p.pll,Locked,0) &&
+ rmw(io,p.pll,Power,0) && wait(io,p.pll,Powered,0);
+ }
+ template static bool enable(IO &io,const Port &p,const TMDSClock &plan) {
+ if(!plan.valid() || !preflight(io,p))return false;
+ // No reprogramming of running PLLs. Preserve an enabled GOP until
+ // its native path has explicitly quiesced and disabled the clock.
+ if((io.read(p.pll)&(Enable|Locked)) || !(io.read(Gate)&gateBit(p.tc)) || (io.read(p.clock)&0xf0000000))return false;
+ if(!rmw(io,p.pll,Power,Power) || !wait(io,p.pll,Powered,Powered))return false;
+ for(const auto &word:plan.words) {
+ auto value=word.value;if(!PHY::access(io,p,word.reg,word.mask,value,true))return false;
+ }
+ return rmw(io,p.pll,Enable,Enable) && wait(io,p.pll,Locked,Locked) &&
+ rmw(io,p.clock,0xf0000000,0x80000000) && rmw(io,Gate,gateBit(p.tc),0);
+ }
+ // ADLP uses modular FIA, two TC ports per instance (intel_tc.c).
+ // Fixed HDMI uses all four main-link lanes. PHY ownership registers are
+ // generation-specific, but the FIA lane-enable register is still required.
+ static uint32_t laneRegister(unsigned tc) {return tc<2?0x1638c0:0x16e8c0;}
+ static uint32_t laneMask(unsigned tc) {return 15U<<(4*(tc&1));}
+ template static bool lanes(IO &io,const Port &p,unsigned count=4) {
+ return (count==1 || count==2 || count==4) && preflight(io,p) && io.readable(laneRegister(p.tc)) &&
+ rmw(io,laneRegister(p.tc),laneMask(p.tc),((1U< static bool startLink(IO &io,const Port &p,const TMDSClock &plan,uint32_t preset,LinkStage *stage=nullptr) {
+ auto step=[&](LinkStage s){if(stage)*stage=s;};
+ step(LinkStage::Preflight);if(!plan.valid() || !preflight(io,p))return false;
+ step(LinkStage::ReleaseDDI);if(!io.linkPower(false))return false;
+ step(LinkStage::StopPLL);if(!disable(io,p))return false;
+ step(LinkStage::Lanes);if(!lanes(io,p))return false;
+ step(LinkStage::PLL);if(!enable(io,p,plan))return false;
+ step(LinkStage::DDI);if(!io.linkPower(true))return false;
+ step(LinkStage::Signal);if(!TMDSSignal::program(io,p,preset))return false;
+ step(LinkStage::Complete);return true;
+ }
+ template static bool stopLink(IO &io,const Port &p) {
+ return io.unstarted() || (preflight(io,p) && io.linkPower(false) && disable(io,p));
+ }
+
+};
+}
+#endif
diff --git a/WhateverXe/kern_display13_tc_dp.hpp b/WhateverXe/kern_display13_tc_dp.hpp
new file mode 100644
index 00000000..228fecad
--- /dev/null
+++ b/WhateverXe/kern_display13_tc_dp.hpp
@@ -0,0 +1,99 @@
+// Fixed SST DP on Display13 Dekel. Linux v6.12 intel_dpll_mgr.c,
+// intel_ddi.c and ADLP DKL signal tables; no board-specific coefficients.
+#ifndef KERN_DISPLAY13_TC_DP_HPP
+#define KERN_DISPLAY13_TC_DP_HPP
+#include "kern_display13_tc_clock.hpp"
+namespace Display13TC {
+struct DPClock : TMDSClock {
+ static DPClock calculate(uint32_t linkRate,uint32_t ref) {
+ DPClock p;
+ if((linkRate!=6 && linkRate!=10 && linkRate!=20 && linkRate!=30) ||
+ (ref!=19200 && ref!=24000 && ref!=38400))return p;
+ const auto clock=linkRate*27000;
+ const unsigned dividers[]={7,5,3,2};
+ for(unsigned h:dividers)for(unsigned d=10;d;--d) {
+ if(uint64_t(clock)*5*h*d!=8100000)continue;
+ const auto integer=8100000/(ref*2),rem=8100000%(ref*2);
+ const auto fraction=uint32_t((uint64_t(rem)<<22)/(ref*2));
+ const auto target=uint32_t((2000000000ULL/(132*ref)+5)/10);
+ const auto ratio=h==7?3:h==5?2:h==3?1:0;
+ p.rate=clock;p.reference=ref;p.dco=8100000;p.hs=h;p.ds=d;
+ p.words[0]={0x212c,0x700,0x100};
+ p.words[1]={0x20d8,0xff00,(d>=2?10U:5U)<<8};
+ p.words[2]={0x20d4,0x1ff00,(d>=2?0x4000U:0U)|(ratio<<12)|(d<<8)};
+ p.words[3]={0x2200,0x1fffff,0x84200|integer};
+ p.words[4]={0x2204,0x1f00ff,((ref==24000?25U:28U)<<16)|target};
+ p.words[5]={0x2210,0xe0ff3a00,(ref==38400?0x40000000U:0x20000000U)|0x2000};
+ p.words[6]={0x2214,0x7fffff00,(fraction?0x40000000U:0U)|(fraction<<8)};
+ p.words[7]={0x2218,0xffff,rem?82U:0U}; // 200000000 / (8100000 * 3/10)
+ return p;
+ }
+ return {};
+ }
+};
+// Retain only a link programmed by this owner, with matching live PLL/FIA.
+// AGDC success leaves it enabled before native adoption and LightUp.
+struct DPRetainedLink {
+ uint32_t rate {},reference {},lanes {};
+ template bool matches(IO &io,const Port &p,const TMDSClock &plan,unsigned count) const {
+ if(!plan.valid() || !rate || rate!=plan.rate || reference!=plan.reference || lanes!=count ||
+ (count!=1 && count!=2 && count!=4) || !io.owned(p.ddi) || !io.linkHeld())return false;
+ const uint32_t registers[]={p.pll,p.clock,Clock::Gate,Clock::laneRegister(p.tc)};
+ for(auto reg:registers)if(!io.readable(reg) || io.read(reg)==UINT32_MAX)return false;
+ if((io.read(p.pll)&0xcc000000U)!=0xcc000000U || (io.read(p.clock)&0xf0000000U)!=0x80000000U ||
+ (io.read(Clock::Gate)&Clock::gateBit(p.tc)) ||
+ (io.read(Clock::laneRegister(p.tc))&Clock::laneMask(p.tc))!=((1U<3 || emphasis>3 || swing+emphasis>3)return false;
+ constexpr uint8_t starts[]={0,4,7,9},voltage[]={7,5,2,0,5,2,0,2,0,0};
+ constexpr uint8_t low[]={1,6,11,23,0,8,20,0,11,0},high[]={0,4,10,24,0,6,20,0,9,0};
+ const auto index=starts[swing]+emphasis;
+ value=voltage[index]|(uint32_t(rate<=10?low[index]:high[index])<<8);return true;
+ }
+ template static bool mode(IO &io,const Port &p,unsigned lanes) {
+ if(!io.powerHeld() || !io.owned(p.ddi) || !io.quiescent(p.ddi) || (lanes!=1 && lanes!=2 && lanes!=4))return false;
+ for(unsigned group=0;group<2;++group) {
+ uint32_t value=lanes==1?(group?0x40U:0U):0x80U;
+ if(!PHY::access(io,p,0x1000*group+0xa0,0xc0,value,true))return false;
+ }
+ return true;
+ }
+ // The donor negotiates a common swing/emphasis for the active lanes.
+ // Training can change the electrical level while the DDI buffer is on.
+ template static bool program(IO &io,const Port &p,unsigned rate,unsigned swing,unsigned emphasis) {
+ uint32_t selected;
+ if(!io.powerHeld() || !io.owned(p.ddi) || !coefficients(rate,swing,emphasis,selected))return false;
+ for(unsigned group=0;group<2;++group) {
+ const auto bank=0x1000*group;uint32_t zero=0;
+ if(!PHY::access(io,p,bank+0xd00,UINT32_MAX,zero,true))return false;
+ for(unsigned tx=0;tx<2;++tx) {
+ auto value=selected;
+ if(!PHY::access(io,p,bank+0x2c0+4*tx,0x3ff07,value,true))return false;
+ }
+ zero=0;if(!PHY::access(io,p,bank+0x2c8,0x7c,zero,true))return false;
+ }
+ return true;
+ }
+ template static bool startLink(IO &io,const Port &p,const DPClock &plan,unsigned lanes,Clock::LinkStage *stage=nullptr) {
+ auto step=[&](Clock::LinkStage s){if(stage)*stage=s;};
+ step(Clock::LinkStage::Preflight);
+ if(!plan.valid() || (lanes!=1 && lanes!=2 && lanes!=4) || !Clock::preflight(io,p))return false;
+ step(Clock::LinkStage::ReleaseDDI);if(!io.linkPower(false))return false;
+ step(Clock::LinkStage::StopPLL);if(!Clock::disable(io,p))return false;
+ step(Clock::LinkStage::Lanes);if(!Clock::lanes(io,p,lanes))return false;
+ step(Clock::LinkStage::PLL);if(!Clock::enable(io,p,plan))return false;
+ step(Clock::LinkStage::DDI);if(!io.linkPower(true))return false;
+ step(Clock::LinkStage::Signal);
+ if(!mode(io,p,lanes) || !program(io,p,plan.rate/27000,0,0))return false;
+ step(Clock::LinkStage::Complete);return true;
+ }
+};
+}
+#endif
diff --git a/WhateverXe/kern_display13_tc_lightup.hpp b/WhateverXe/kern_display13_tc_lightup.hpp
new file mode 100644
index 00000000..d2438a2c
--- /dev/null
+++ b/WhateverXe/kern_display13_tc_lightup.hpp
@@ -0,0 +1,49 @@
+// Fixed-TMDS ordering for the exact Display13 native LightUpTMDS adapter.
+// The caller's hwSetMode performs disableDisplay on any returned error.
+#ifndef KERN_DISPLAY13_TC_LIGHTUP_HPP
+#define KERN_DISPLAY13_TC_LIGHTUP_HPP
+#include
+namespace Display13TC {
+enum class LightStage : uint32_t {Binding,Clocks,TransClock,Plane,Scaler,Pipe,Signal,Port,Packets,Complete,Training};
+struct LightResult {LightStage stage {LightStage::Binding};uint32_t status {0xe00002d8U};};
+template LightResult lightUpTMDS(IO &io,unsigned ddi,unsigned pipe) {
+ LightResult r;
+ if(ddi<3 || ddi>6 || pipe>=3 || !io.ready(false))return r;
+ r.stage=LightStage::Clocks;io.clocks();
+ if(!io.ready(true))return r;
+ r.stage=LightStage::TransClock;
+ if(!io.transClock(0x46140+4*pipe,(ddi+3)<<28))return r;
+ r.stage=LightStage::Plane;io.plane();if(!io.ready(true))return r;
+ r.stage=LightStage::Scaler;io.scaler();if(!io.ready(true))return r;
+ r.stage=LightStage::Pipe;io.pipe();if(!io.ready(true))return r;
+ r.stage=LightStage::Signal;io.signal();if(!io.ready(true))return r;
+ r.stage=LightStage::Port;
+ const auto enabled=io.port();if(enabled){r.status=enabled;return r;}
+ if(!io.ready(true))return r;
+ r.stage=LightStage::Packets;io.packets();if(!io.ready(true))return r;
+ r.stage=LightStage::Complete;r.status=0;return r;
+}
+// Fixed SST retains native training and SDP setup, but stops on failure
+// instead of the donor's unconditional success/pipe programming afterward.
+template LightResult lightUpDP(IO &io,unsigned ddi,unsigned pipe) {
+ LightResult r;
+ if(ddi<3 || ddi>6 || pipe>=3 || !io.ready(false))return r;
+ r.stage=LightStage::Clocks;io.clocks();if(!io.ready(true))return r;
+ r.stage=LightStage::Training;
+ const auto trained=io.train();if(trained){r.status=trained;return r;}
+ if(!io.ready(true))return r;
+ r.stage=LightStage::TransClock;if(!io.transClock(0x46140+4*pipe,(ddi+3)<<28))return r;
+ r.stage=LightStage::Plane;io.plane();if(!io.ready(true))return r;
+ r.stage=LightStage::Scaler;io.scaler();if(!io.ready(true))return r;
+ r.stage=LightStage::Pipe;io.pipe();if(!io.ready(true))return r;
+ r.stage=LightStage::Packets;io.packets();if(!io.ready(true))return r;
+ r.stage=LightStage::Complete;r.status=0;return r;
+}
+// Native hwSetMode sends the begin notification before the path call. Its
+// error branch skips post-mode writes and the normal end notification;
+// balance that notification without replacing the returned error.
+template uint32_t finishMode(Run run,Notify notify) {
+ const auto result=run();if(result)notify();return result;
+}
+}
+#endif
diff --git a/WhateverXe/kern_display13_tc_mode.hpp b/WhateverXe/kern_display13_tc_mode.hpp
new file mode 100644
index 00000000..1b181f88
--- /dev/null
+++ b/WhateverXe/kern_display13_tc_mode.hpp
@@ -0,0 +1,83 @@
+// Fixed legacy TC lifetime. The host serializes native clients and proves
+// firmware adoption/quiescence; this core never executes the TGL FIA setter.
+#ifndef KERN_DISPLAY13_TC_MODE_HPP
+#define KERN_DISPLAY13_TC_MODE_HPP
+#include "kern_display13_tc.hpp"
+#include "kern_display13_power_bridge.hpp"
+namespace Display13TC {
+struct Mode {
+ enum class Phase : uint32_t {Idle,Active,Cleanup};
+ struct Change {
+ Status status {Status::Invalid};Result phy;
+ Display13Power::Result power;uint32_t native {};
+ };
+ uintptr_t controller {},object {};unsigned ddi {};
+ Phase phase {Phase::Idle};bool disconnectPending {};
+ Ownership ownership;
+ // ADLP: register/lane access -> ownership -> AUX cold block.
+ // DDI IO belongs to the clock lifecycle, after the PLL is running.
+ // An already-powered firmware link is not evidence that these scopes can
+ // be requested together on an unowned, cold PHY.
+ Display13Power::Lease accessLease,auxLease;
+ static uint32_t accessDomains() {return Display13Power::bit(Display13Power::Domain::PW2);}
+ Mode()=default;Mode(const Mode&)=delete;Mode& operator=(const Mode&)=delete;
+ static uint32_t domains(unsigned d) {
+ Port p;return Port::resolve(d,p) ? Display13Power::ddiDomain(d)|
+ Display13Power::auxDomain(d)|Display13Power::bit(Display13Power::Domain::PW2) : 0;
+ }
+ template Change connect(IO &io,uintptr_t owner,uintptr_t identity,unsigned d) {
+ Change r;
+ if(!owner || !identity || !domains(d))return r;
+ if(controller && (owner!=controller || identity!=object || d!=ddi)){r.status=Status::InUse;return r;}
+ if(!io.admissionReady()){r.status=Status::NotReady;return r;}
+ if(phase==Phase::Cleanup){r.status=Status::InUse;return r;}
+ if(phase==Phase::Active) {
+ if(io.nativeMode()!=4){r.status=Status::Readback;return r;}
+ r.phy=ownership.acquire(io,owner,identity,d);
+ r.status=r.phy.status;if(r.status==Status::Complete)disconnectPending=false;
+ return r;
+ }
+ if(io.nativeMode()!=0){r.status=Status::NeedsAdoption;return r;}
+ // Keep failed acquisitions bound, including their exact power/PHY
+ // cleanup obligations. Never replace them with another native object.
+ controller=owner;object=identity;ddi=d;phase=Phase::Cleanup;
+ r.power=io.acquireDomains(accessLease,accessDomains());
+ if(r.power.status!=Display13Power::Status::Complete){r.status=Status::NoPower;return r;}
+ const bool firmware=io.inheritedVerified(owner,identity,d);
+ r.phy=ownership.acquire(io,owner,identity,d,firmware);
+ if(r.phy.status!=Status::Complete){r.status=r.phy.status;return r;}
+ r.power=io.acquireDomains(auxLease,Display13Power::auxDomain(d));
+ if(r.power.status!=Display13Power::Status::Complete){r.status=Status::NoPower;return r;}
+ r.native=io.nativeSetMode(4);
+ if(r.native || io.nativeMode()!=4){r.status=Status::Readback;return r;}
+ phase=Phase::Active;disconnectPending=false;r.status=Status::Complete;return r;
+ }
+ template Change disconnect(IO &io,uintptr_t owner,uintptr_t identity,unsigned d) {
+ Change r;
+ if(!owner || !identity || !domains(d))return r;
+ if(!controller){r.status=io.nativeMode()==0 ? Status::Complete : Status::NeedsAdoption;return r;}
+ if(owner!=controller || identity!=object || d!=ddi){r.status=Status::InUse;return r;}
+ disconnectPending=true;
+ if(!io.quiescent(owner,identity,d)){r.status=Status::InUse;return r;}
+ phase=Phase::Cleanup;
+ if(io.nativeMode()!=0) {
+ r.native=io.nativeSetMode(0);
+ if(r.native || io.nativeMode()!=0){r.status=Status::Readback;return r;}
+ }
+ if(auxLease.owner) {
+ r.power=io.releaseDomains(auxLease);
+ if(r.power.status!=Display13Power::Status::Complete){r.status=Status::NoPower;return r;}
+ }
+ if(ownership.owner) {
+ r.phy=ownership.release(io,owner,identity,d);
+ if(r.phy.status!=Status::Complete){r.status=r.phy.status;return r;}
+ }
+ if(accessLease.owner) {
+ r.power=io.releaseDomains(accessLease);
+ if(r.power.status!=Display13Power::Status::Complete){r.status=Status::NoPower;return r;}
+ }
+ controller=object=0;ddi=0;phase=Phase::Idle;disconnectPending=false;r.status=Status::Complete;return r;
+ }
+};
+}
+#endif
diff --git a/WhateverXe/kern_display13_tc_native.hpp b/WhateverXe/kern_display13_tc_native.hpp
new file mode 100644
index 00000000..2fad5123
--- /dev/null
+++ b/WhateverXe/kern_display13_tc_native.hpp
@@ -0,0 +1,70 @@
+// Exact c004fc1a entry/ABI and failure branches for fixed TC outputs.
+#ifndef KERN_DISPLAY13_TC_NATIVE_HPP
+#define KERN_DISPLAY13_TC_NATIVE_HPP
+#include
+namespace Display13TC { namespace Native {
+enum EntryIndex {Mode,BaseMode,Clocks,DisableClocks,LightUp,SetMode,Notify,FullClocks,Plane,Scaler,Pipe,Packets,DPLightUp,Training,SDPs,PhyGating,LaneMask,PortWrite};
+struct Entry {const char *symbol;uint32_t offset;uint8_t prefix[20];};
+static constexpr Entry entries[]={
+ {"__ZN17AppleIntelPortHAL11setPortModeEN14AppleIntelPort8PortModeE",0x5fcb8,{0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec,0x18,0x41,0x89,0xf7}},
+ {"__ZN14AppleIntelPort11setPortModeENS_8PortModeE",0x1b4c8,{0x55,0x48,0x89,0xe5,0x41,0x56,0x53,0x89,0xf3,0x49,0x89,0xfe,0x48,0xff,0x05,0x45,0xae,0x0b,0x00,0x85}},
+ {"__ZN31AppleIntelFramebufferController12enableClocksEP14AppleIntelPortS1_P11ClockParams",0x6ce10,{0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec,0x28,0x49,0x89,0xcf}},
+ {"__ZN31AppleIntelFramebufferController13disableClocksEP14AppleIntelPortS1_",0x6dae6,{0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x54,0x53,0x49,0x89,0xd7,0x48,0x89,0xf3,0x49,0x89,0xfe}},
+ {"__ZN31AppleIntelFramebufferController11LightUpTMDSEP21AppleIntelFramebufferP21AppleIntelDisplayPathPK29IODetailedTimingInformationV2",0x50b22,{0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x54,0x53,0x49,0x89,0xcc,0x48,0x89,0xd3,0x49,0x89,0xf6}},
+ {"__ZN31AppleIntelFramebufferController9hwSetModeEP21AppleIntelFramebufferPK29IODetailedTimingInformationV2",0x693ec,{0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec,0x28,0x48,0x89,0x55}},
+ {"__ZN31AppleIntelFramebufferController16sendNotificationEP21AppleIntelFramebufferNS_17ControllerEvent_tE",0x52f60,{0x55,0x48,0x89,0xe5,0x48,0xff,0x05,0x45,0xd0,0x08,0x00,0x83,0xfa,0x01,0x74,0x0d,0x85,0xd2,0x75,0x1c}},
+ {"__ZN31AppleIntelFramebufferController12enableClocksEP21AppleIntelFramebufferP21AppleIntelDisplayPath",0x6ccb4,{0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x54,0x53,0x48,0xff,0x05,0x12,0x87,0x07,0x00,0x48,0xff}},
+ {"__ZN15AppleIntelPlane11enablePlaneEb",0x1108,{0x55,0x48,0x89,0xe5,0x41,0x56,0x53,0x48,0xff,0x05,0x5a,0xf8,0x0c,0x00,0x48,0x8b,0x47,0x68,0x83,0xb8}},
+ {"__ZN16AppleIntelScaler17programPipeScalerEP21AppleIntelDisplayPath",0x65fd4,{0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec,0x18,0x48,0xff,0x05}},
+ {"__ZN31AppleIntelFramebufferController10enablePipeEP21AppleIntelFramebufferP21AppleIntelDisplayPathPK29IODetailedTimingInformationV2",0x48bb6,{0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec,0x38,0x48,0x8b,0x05}},
+ {"__ZN31AppleIntelFramebufferController23enableDataIslandPacketsEP21AppleIntelFramebufferP21AppleIntelDisplayPath",0x52106,{0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x53,0x50,0x48,0xff,0x05,0x11,0xdd,0x08,0x00,0x49,0x89,0xd6}},
+ {"__ZN31AppleIntelFramebufferController9LightUpDPEP21AppleIntelFramebufferP21AppleIntelDisplayPathPK29IODetailedTimingInformationV2",0x508c4,{0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x50,0x49,0x89,0xce,0x48,0x89,0xd3}},
+ {"__ZN14AppleIntelPort12linkTrainingEP18AGDCDPPortConfig_t",0x1ba02,{0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec,0x48,0x48,0xff,0x5}},
+ {"__ZN31AppleIntelFramebufferController20configureSDPsForHDMIEP21AppleIntelFramebufferP21AppleIntelDisplayPathPK29IODetailedTimingInformationV2",0x51f56,{0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x50,0x48,0xff,0x5,0x45,0xde,0x8}},
+ {"__ZN17AppleIntelPortHAL17setPhyClockGatingEb",0x606b4,{0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x50,0x48,0xff,0x5,0x9f,0x25,0x8}},
+ {"__ZN17AppleIntelPortHAL26getDklPhyLaneConfigBitMaskEv",0x60160,{0x55,0x48,0x89,0xe5,0x41,0x56,0x53,0x48,0xff,0x5,0x6a,0x29,0x8,0x0,0x48,0xff,0x5,0xb3,0x29,0x8}},
+ {"__ZN14AppleIntelPort15writeRegister32Ejj",0x1fea8,{0x55,0x48,0x89,0xe5,0x48,0xff,0x5,0x9d,0x73,0xb,0x0,0x48,0x8b,0xbf,0x78,0x5,0x0,0x0,0x89,0xf6}},
+
+};
+// Native clock replacements retain these DDI reference boundaries.
+// Boot Mode is acquired in PowerWell::init: native ports must already exist.
+// Full modesets stop the inherited link before LightUpDP programs its PLL.
+static constexpr uint32_t bootPortInitOffset=0x3fdd0;
+static constexpr uint8_t bootPortInit[]={0x89,0xdf,0xe8,0xc5,0xb3,0xfd,0xff,0x49,0x8b,0xb5,0x40,0x0d,0x00,0x00,0x4c,0x01,0xe6,0x48,0x8b,0x08,0x48,0x89,0xc7,0xff,0x91,0x18,0x01,0x00,0x00,0x84,0xc0,0x0f,0x84,0x9a,0x01,0x00,0x00,0x48,0xff,0x05,0x8c,0xd6,0x09,0x00,0x48,0xff,0xc3,0x48,0x8d,0x05,0xb2,0x8c,0x0f,0x00,0x8b,0x00,0x49,0x83,0xc4,0x18,0x48,0x39,0xc3,0x72,0xb8};
+static constexpr uint32_t bootPowerCallOffset=0x3fe36;
+static constexpr uint8_t bootPowerCall[]={0x48,0x89,0xdf,0x4c,0x89,0xee,0xe8,0x47,0xa1,0x01,0x00};
+static constexpr uint32_t bootSetupCallOffset=0x3ff41;
+static constexpr uint8_t bootSetupCall[]={0x4c,0x89,0xef,0xe8,0xc1,0x36,0x00,0x00};
+static constexpr uint32_t modeStopCallOffset=0x4febb;
+static constexpr uint8_t modeStopCall[]={0x0f,0xb6,0xcb,0x4c,0x89,0xe7,0x4c,0x89,0xf6,0x4c,0x89,0xfa,0xe8,0x80,0xd4,0xff,0xff};
+static constexpr uint32_t dpLightCallOffset=0x4ffc4;
+static constexpr uint8_t dpLightCall[]={0x48,0x8d,0x8d,0xf8,0xfe,0xff,0xff,0x4c,0x89,0xe7,0x4c,0x89,0xf6,0x4c,0x89,0xfa,0xe8,0xeb,0x08,0x00,0x00};
+static constexpr uint32_t ddiEnableTailOffset=0x6d7a8;
+static constexpr uint8_t ddiEnableTail[]={0xe9,0x51,0x27,0xfb,0xff};
+static constexpr uint32_t ddiDisableCallOffset=0x6db04;
+static constexpr uint8_t ddiDisableCall[]={0xe8,0x4b,0x24,0xfb,0xff};
+static constexpr uint32_t lightBodyOffset=0x50b22;
+static constexpr uint8_t lightBody[]={0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x54,0x53,0x49,0x89,0xcc,0x48,0x89,0xd3,0x49,0x89,0xf6,0x49,0x89,0xff,0x48,0xff,0x05,0xd8,0xef,0x08,0x00,0xe8,0x6f,0xc1,0x01,0x00,0x49,0x8b,0xbf,0x40,0x0c,0x00,0x00,0x48,0xff,0x05,0x65,0xdb,0x08,0x00,0x8b,0x83,0x40,0x36,0x00,0x00,0x8d,0x34,0x85,0x40,0x61,0x04,0x00,0x8b,0x93,0x28,0x01,0x00,0x00,0x48,0x8b,0x07,0xff,0x90,0x30,0x01,0x00,0x00,0x48,0x8b,0xbb,0xc8,0x32,0x00,0x00,0x31,0xf6,0xe8,0x8b,0x05,0xfb,0xff,0x48,0x8b,0xbb,0xc0,0x32,0x00,0x00,0x48,0x89,0xde,0xe8,0x48,0x54,0x01,0x00,0x4c,0x89,0xff,0x4c,0x89,0xf6,0x48,0x89,0xda,0x4c,0x89,0xe1,0xe8,0x19,0x80,0xff,0xff,0x48,0xff,0x05,0x94,0xda,0x08,0x00,0x48,0x8b,0xbb,0x48,0x36,0x00,0x00,0x48,0x8b,0x07,0x31,0xf6,0x31,0xd2,0x31,0xc9,0xff,0x90,0x40,0x01,0x00,0x00,0x48,0xff,0x05,0x77,0xda,0x08,0x00,0x48,0x8b,0xbb,0x48,0x36,0x00,0x00,0x48,0x8b,0x07,0xff,0x90,0x28,0x01,0x00,0x00,0x4c,0x89,0xff,0x4c,0x89,0xf6,0x48,0x89,0xda,0xe8,0x27,0x15,0x00,0x00,0x31,0xc0,0x5b,0x41,0x5c,0x41,0x5e,0x41,0x5f,0x5d,0xc3};
+static constexpr uint32_t innerBeforeOffset=0x4ffd9;
+static constexpr uint8_t innerBefore[]={0x45,0x31,0xed};
+static constexpr uint32_t outerBeforeOffset=0x695ac;
+static constexpr uint8_t outerBefore[]={0x41,0xf6,0x86,0x5c,0x0c,0x00,0x00,0x01,0x74,0x46,0x48,0xff,0x05,0x73,0xb3,0x07,0x00,0x48,0xff,0x05,0xec,0xae,0x07,0x00};
+static constexpr uint32_t modeExitOffset=0x698ed;
+static constexpr uint8_t modeExit[]={0x48,0x83,0xc4,0x28,0x5b,0x41,0x5c,0x41,0x5d,0x41,0x5e,0x41,0x5f,0x5d,0xc3};
+static constexpr uint32_t innerCallOffset=0x4ffb6;
+static constexpr uint8_t innerCall[]={0xe8,0x67,0x0b,0x00,0x00,0xeb,0x1c};
+static constexpr uint32_t outerCallOffset=0x695a7;
+static constexpr uint8_t outerCall[]={0xe8,0x28,0x64,0xfe,0xff};
+static constexpr uint32_t modeBeginOffset=0x6940e;
+static constexpr uint8_t modeBegin[]={0x45,0x31,0xff,0x31,0xd2,0xe8,0x48,0x9b,0xfe,0xff};
+static constexpr uint32_t modeEndOffset=0x698db;
+static constexpr uint8_t modeEnd[]={0x4c,0x89,0xf7,0x4c,0x89,0xe6,0xba,0x01,0x00,0x00,0x00,0xe8,0x75,0x96,0xfe,0xff,0x31,0xc0};
+// Preserve the returned LightUp status so native hwSetMode disables the path.
+static constexpr uint8_t innerAfter[]={0x41,0x89,0xc5};
+// Replace two profiling counters with a returned-status branch. On success,
+// retain the original configuration-bit test and both of its destinations.
+// Error jumps to the stack epilogue (preserving eax); the C++ outer adapter
+// balances the begin notification. It does not execute post-mode MMIO.
+static constexpr uint8_t outerAfter[]={0x85,0xc0,0x0f,0x85,0x39,0x03,0x00,0x00,0x41,0xf6,0x86,0x5c,0x0c,0x00,0x00,0x01,0x74,0x3e,0x90,0x90,0x90,0x90,0x90,0x90};
+}}
+#endif
diff --git a/WhateverXe/kern_display13_wm.hpp b/WhateverXe/kern_display13_wm.hpp
index caca62b9..abbe3772 100644
--- a/WhateverXe/kern_display13_wm.hpp
+++ b/WhateverXe/kern_display13_wm.hpp
@@ -289,9 +289,15 @@ inline bool clockFits(const Snapshot &s,uint64_t nativeHz) {
(!s.cursorActive || (s.cursor && s.cursor->status==Status::Complete &&
s.cursor->pixelHz && s.cursor->pixelHz<=nativeHz/2));
}
+enum class LatchCheck : uint32_t {None,Controls,OtherPlane,DSB,Clock,Registers,Live,Frame,AwaitingScanout};
struct Latch {
Status status {Status::Unavailable};
uint32_t frameBefore {},frameAfter {},waitUs {},budgetUs {},lastReg {},expected {},observed {};
+ // Reuse the values already read by the proof. No diagnostic MMIO reads,
+ // retries, or relaxed acceptance; preserve the first failed check in IORegistry.
+ LatchCheck check {LatchCheck::None};
+ uint32_t pipeControl {},planeControl {},cursorControl {},checkRegister {},checkValue {};
+ uint64_t clockHz {};
};
template bool compare(IO &io,Latch &r,uint32_t reg,uint32_t expected) {
if(!io.readable(reg)) {r.status=Status::Unavailable;return false;}
@@ -338,23 +344,35 @@ template Latch latch(IO &io,const Snapshot &s) {
const auto power=io.read(0x45404);
if(!Display13FBIRQ::powered(power,s.pipeIndex)) {r.status=Status::Power;return r;}
const auto pipe=io.read(0x70008+bank),plane=io.read(0x70180+bank),cursor=io.read(0x70080+bank);
+ r.check=LatchCheck::Controls;r.pipeControl=pipe;r.planeControl=plane;r.cursorControl=cursor;
+ // Native enablePlane may return before any scanout is attached, while
+ // programPipeScaler still flushes a zero SURF trigger via armUpdate.
+ // A disabled primary with no intended surface is not a live latch to
+ // prove yet. Keep WM/DDB pending; never retire storage or certify the
+ // shared clock until a later real plane/surface update passes below.
+ const bool preparing=plane!=UINT32_MAX && !(plane&Enabled) && !s.primarySurface && !cursor && !s.cursorActive;
if(pipe==UINT32_MAX || plane==UINT32_MAX || cursor==UINT32_MAX || (pipe&0xC0000000)!=0xC0000000 ||
- !(plane&Enabled) || !supportedRGBControl(plane) || (((plane>>10)&7)==4)!=s.primary->yTiled ||
+ (!preparing && (!(plane&Enabled) || !supportedRGBControl(plane) || (((plane>>10)&7)==4)!=s.primary->yTiled)) ||
bool(cursor&0x27)!=s.cursorActive || (s.cursorActive && !cursorMatches(cursor,s.cursor->width))) {r.status=Status::Conflict;return r;}
// No autonomous DSB writer is admitted by this direct-MMIO proof.
for(unsigned p=1;p<5;++p) {
const auto reg=0x70180+bank+0x100*p;
if(!io.readable(reg))return r;
const auto ctrl=io.read(reg);
+ r.check=LatchCheck::OtherPlane;r.checkRegister=reg;r.checkValue=ctrl;
if(ctrl==UINT32_MAX || (ctrl&Enabled)) {r.status=Status::Conflict;return r;}
}
for(unsigned d=0;d<3;++d) {
const auto reg=Display13FBIRQ::DSBControl(s.pipeIndex,d);
if(!io.readable(reg))return r;
const auto ctrl=io.read(reg);
+ r.check=LatchCheck::DSB;r.checkRegister=reg;r.checkValue=ctrl;
if(ctrl==UINT32_MAX || (ctrl&(Enabled|1))) {r.status=Status::Conflict;return r;}
}
- if(io.nativeClockHz()!=s.nativeClockHz) {r.status=Status::Conflict;return r;}
+ r.check=LatchCheck::Clock;r.clockHz=io.nativeClockHz();
+ if(r.clockHz!=s.nativeClockHz) {r.status=Status::Conflict;return r;}
+ if(preparing) {r.check=LatchCheck::AwaitingScanout;return r;}
+ r.check=LatchCheck::Registers;
if(!compare(io,r,0x60000+bank,ht) || !compare(io,r,0x6000C+bank,vt) || !compare(io,r,0x45270+4*s.pipeIndex,s.pipeLineTime) ||
!compare(io,r,0x70190+bank,((s.primary->height-1)<<16)|(s.primary->width-1)) ||
!compare(io,r,0x7027C+bank,s.primaryDDB) || !compare(io,r,0x7017C+bank,s.cursorDDB) ||
@@ -365,12 +383,14 @@ template Latch latch(IO &io,const Snapshot &s) {
r.waitUs=static_cast(io.nowUs()-start);
if(r.frameAfter==UINT32_MAX) {r.status=Status::Readback;return r;}
if(r.frameAfter!=r.frameBefore) {
+ r.check=LatchCheck::Frame;
if(uint32_t(r.frameAfter-r.frameBefore)>=0x80000000U) {r.status=Status::Conflict;return r;}
// The frame counter can advance at VBlank before the live-surface
// readback catches up (observed for a native direct cursor swap).
// Keep every programmed/control/clock check on each poll; qualify
// retirement only after both actual live surfaces match. A stable
// mismatch at the existing deadline remains a Readback failure.
+ r.check=LatchCheck::Live;
const bool live=compare(io,r,0x701AC+bank,s.primarySurface) &&
(!s.cursorActive || compare(io,r,0x700AC+bank,s.cursorSurface));
if(live) {r.status=Status::Complete;return r;}
diff --git a/WhateverXe/kern_firmware.cpp b/WhateverXe/kern_firmware.cpp
index 60c553ba..751eb82f 100644
--- a/WhateverXe/kern_firmware.cpp
+++ b/WhateverXe/kern_firmware.cpp
@@ -10,7 +10,8 @@ static void publishBytes(IOPCIDevice *pci,const char *key,const void *bytes,unsi
// Firmware is read once, before topology/hook selection. No PCI BAR/ROM scan,
// power acquisition, or MMIO probing occurs here. Explicit properties win later.
void XE::xelpReadFirmware(IOPCIDevice *pci) {
- if(!pci || !xeProfile.admitted())return;
+ // Wiring is needed by framebuffer-only profiles before Render is admitted.
+ if(!pci || !xeProfile.displayAdmitted())return;
const uint32_t asls=pci->configRead32(0xFC);
uint32_t source=0;
if(asls && asls<=0xFFFFE000U) {
diff --git a/WhateverXe/kern_framebuffer_donor.hpp b/WhateverXe/kern_framebuffer_donor.hpp
new file mode 100644
index 00000000..fcc40aa3
--- /dev/null
+++ b/WhateverXe/kern_framebuffer_donor.hpp
@@ -0,0 +1,54 @@
+// Object contracts of the c004fc1a framebuffer donor, independent of Display IP.
+#ifndef KERN_FRAMEBUFFER_DONOR_HPP
+#define KERN_FRAMEBUFFER_DONOR_HPP
+#include
+#include
+#include
+namespace FramebufferDonor {
+// Native entry admission is closed before controller/pool destruction. The
+// low bits count in-flight opens, training and resource adoption; stop never
+// holds the pool lock while draining them. Closing is terminal for this owner.
+class Call {
+ static constexpr uint32_t Closed=1U<<31;
+ uint32_t *state {};
+public:
+ explicit Call(uint32_t &value) {
+ auto old=__atomic_load_n(&value,__ATOMIC_ACQUIRE);
+ while(!(old&Closed) && old!=Closed-1) {
+ if(__atomic_compare_exchange_n(&value,&old,old+1,false,__ATOMIC_ACQ_REL,__ATOMIC_ACQUIRE)) {state=&value;break;}
+ }
+ }
+ ~Call(){if(state)__atomic_fetch_sub(state,1,__ATOMIC_RELEASE);}
+ Call(const Call &)=delete;Call &operator=(const Call &)=delete;
+ explicit operator bool() const {return state!=nullptr;}
+ static void close(uint32_t &value){__atomic_fetch_or(&value,Closed,__ATOMIC_ACQ_REL);}
+ static bool drained(uint32_t &value){return (__atomic_load_n(&value,__ATOMIC_ACQUIRE)&~Closed)==0;}
+};
+template T field(const void *object,size_t offset) {
+ T value {};if(object)memcpy(&value,static_cast(object)+offset,sizeof(value));return value;
+}
+// Native FB init leaves borrowed MMIO-manager fields empty. Validate the whole
+// controller-owned graph before changing any reference. Never retain/release a
+// borrowed manager, overwrite a foreign owner, or partly bind a rejected graph.
+template bool bindManagers(Objects &objects,void *fb,void *controller,uint32_t index) {
+ if(index>=3 || objects.size(fb)<0x4B90 || objects.size(controller)<0x1C20 ||
+ field(fb,0x1D0)!=controller || field(fb,0x1DC)!=index)return false;
+ auto manager=field(controller,0xC40);
+ if(objects.size(manager)<0x68 || !field(manager,0x50) || field(manager,0x60)<=0x60404)return false;
+ const size_t arrays[] {0xF58,0xF28},identities[] {0x7C,0x1C},slots[] {0x90,0x28};
+ const unsigned counts[] {15,6},minimum[] {0x1C8,0x58};
+ for(unsigned group=0;group<2;++group)for(unsigned i=0;i(controller,arrays[group]+sizeof(void *)*i);
+ if(objects.size(object)(object,identities[group])!=i)return false;
+ auto stored=field(object,slots[group]);
+ if(stored && stored!=manager)return false;
+ }
+ auto stored=field(fb,0x4A40);if(stored && stored!=manager)return false;
+ for(unsigned group=0;group<2;++group)for(unsigned i=0;i(controller,arrays[group]+sizeof(void *)*i);
+ memcpy(object+slots[group],&manager,sizeof(manager));
+ }
+ memcpy(static_cast(fb)+0x4A40,&manager,sizeof(manager));return true;
+}
+}
+#endif
diff --git a/WhateverXe/kern_framebuffer_training_native.hpp b/WhateverXe/kern_framebuffer_training_native.hpp
new file mode 100644
index 00000000..b0b7c726
--- /dev/null
+++ b/WhateverXe/kern_framebuffer_training_native.hpp
@@ -0,0 +1,35 @@
+// Exact c004fc1a donor interfaces for pre-modeset SST training and adoption.
+#ifndef KERN_FRAMEBUFFER_TRAINING_NATIVE_HPP
+#define KERN_FRAMEBUFFER_TRAINING_NATIVE_HPP
+#include
+namespace FramebufferTrainingNative {
+enum Index {Begin,Train,End,AllocateAny,AllocateFixed,Free,Add,Resources,Stop,PortTrain,Lookup,Port};
+struct Entry {const char *symbol;uint32_t offset;uint8_t prefix[16];};
+static constexpr Entry entries[]={
+ {"__ZN31AppleIntelFramebufferController19doLinkTrainingBeginEP25AGDCDPLinkTrainingBegin_t",0x6aae0,{0x55,0x48,0x89,0xe5,0x53,0x50,0x48,0xff,0x05,0xd3,0xa1,0x07,0x00,0x48,0x85,0xf6}},
+ {"__ZN31AppleIntelFramebufferController14doLinkTrainingEP18AGDCDPPortConfig_tS1_",0x6ab7e,{0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x54,0x53,0x48,0xff,0x05,0x68,0xa1}},
+ {"__ZN31AppleIntelFramebufferController17doLinkTrainingEndEP23AGDCDPLinkTrainingEnd_t",0x6ad06,{0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x54,0x53,0x48,0xff,0x05,0x28,0xa0}},
+ {"__ZN21AppleIntelDisplayPath12allocatePathEv",0x9188a,{0x55,0x48,0x89,0xe5,0x48,0xff,0x05,0x23,0xa7,0x05,0x00,0xe8,0x4e,0x9a,0xf8,0xff}},
+ {"__ZN21AppleIntelDisplayPath12allocatePathEj",0x91950,{0x55,0x48,0x89,0xe5,0x48,0xff,0x05,0x8d,0xa6,0x05,0x00,0xe8,0x64,0xfe,0xff,0xff}},
+ {"__ZN21AppleIntelDisplayPath8freePathEv",0x91d88,{0x55,0x48,0x89,0xe5,0x48,0xff,0x05,0x85,0xa3,0x05,0x00,0x5d,0xe9,0xb3,0xf7,0xff}},
+ {"__ZN21AppleIntelFramebuffer14addDisplayPathEj",0x68d3e,{0x55,0x48,0x89,0xe5,0x41,0x56,0x53,0x48,0xff,0x05,0xd4,0xb9,0x07,0x00,0x48,0x8b}},
+ {"__ZN21AppleIntelFramebuffer28allocateDisplayPathResourcesEjj",0x679a6,{0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x54,0x53,0x48,0xff,0x05,0x38,0xca}},
+ {"__ZN31AppleIntelFramebufferController4stopEP9IOService",0x44982,{0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x54,0x53,0x49,0x89,0xf6,0x49,0x89}},
+ {"__ZN14AppleIntelPort12linkTrainingEP18AGDCDPPortConfig_t",0x1ba02,{0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec}},
+ {"__ZN21AppleIntelDisplayPath18getFirstPathByPortEP14AppleIntelPort",0x91bca,{0x55,0x48,0x89,0xe5,0x53,0x50,0x48,0xff,0x05,0xc1,0xa4,0x05,0x00,0x48,0x85,0xff}},
+ {"__ZN14AppleIntelPort12getPortByDDIEj",0x1b1ea,{0x55,0x48,0x89,0xe5,0x48,0xff,0x05,0x53,0xb0,0x0b,0x00,0x44,0x8b,0x05,0xbc,0xd8}},
+};
+struct Span {uint32_t offset;uint8_t length;uint8_t bytes[40];};
+static constexpr Span spans[]={
+ {0x6ac66,12,{0x48,0x8d,0x73,0x08,0x4c,0x89,0xff,0xe8,0xaa,0x08,0xfb,0xff}},
+ {0x6ac7d,15,{0x4c,0x89,0xe7,0x4c,0x89,0xfe,0x31,0xd2,0x31,0xc9,0xe8,0x84,0x21,0x00,0x00}},
+ {0x6acc1,11,{0x4c,0x89,0xff,0x4c,0x89,0xf6,0xe8,0x36,0x0d,0xfb,0xff}},
+ {0x6ad44,28,{0x48,0x8b,0x00,0x4c,0x89,0xff,0xff,0x90,0x30,0x01,0x00,0x00,0x45,0x31,0xe4,0x4c,0x89,0xf7,0x4c,0x89,0xfe,0x31,0xd2,0xe8,0x86,0x2d,0x00,0x00}},
+ {0x67a2d,8,{0x48,0x89,0xdf,0xe8,0x09,0x13,0x00,0x00}},
+ {0x67a75,22,{0x44,0x89,0xe7,0xe8,0x1f,0x37,0xfb,0xff,0x48,0xff,0x05,0xec,0xcc,0x07,0x00,0x49,0x89,0x86,0x48,0x36,0x00,0x00}},
+ {0x1e328,39,{0x41,0xc6,0x47,0x10,0x00,0x41,0x38,0x57,0x0e,0x75,0x0e,0x48,0xff,0x05,0xb6,0x89,0x0b,0x00,0x41,0x80,0x7f,0x0d,0x00,0x74,0x0e,0x48,0xff,0x05,0xa0,0x89,0x0b,0x00,0xc0,0xe8,0x07,0x41,0x88,0x47,0x10}},
+ {0x1b607,20,{0x0f,0xb6,0x43,0x1e,0x48,0x01,0x05,0x66,0xad,0x0b,0x00,0x34,0x01,0x88,0x83,0x1a,0x01,0x00,0x00,0x0f}},
+ {0x4d980,16,{0x8d,0x34,0x85,0x40,0x61,0x04,0x00,0x48,0x8b,0x07,0xff,0x90,0x30,0x01,0x00,0x00}},
+};
+}
+#endif
diff --git a/WhateverXe/kern_host.cpp b/WhateverXe/kern_host.cpp
index f4eb7fff..a0b5de2c 100644
--- a/WhateverXe/kern_host.cpp
+++ b/WhateverXe/kern_host.cpp
@@ -31,7 +31,9 @@ void XeHost::processKernel(KernelPatcher &patcher) {
// Match WEG ordering: PCI config reads require a published IOPCIDevice.
const bool published=gpu && WIOKit::awaitPublishing(gpu);
const auto profile=published ? XELP::deviceProfile(gpu) : XELP::Profile {};
- active=profile.admitted();
+ // The host must dispatch framebuffer patches and accelerator rejection
+ // hooks even when the selected profile deliberately has no Render backend.
+ active=profile.displayAdmitted();
const auto display=XELP::displayAdapter(profile.family);
backlightEnabled=active && (display==XELP::DisplayAdapter::TGL || display==XELP::DisplayAdapter::ADLS) &&
info->firmwareVendor!=DeviceInfo::FirmwareVendor::Apple;
@@ -44,7 +46,8 @@ void XeHost::processKernel(KernelPatcher &patcher) {
if(!gpu->getProperty("model"))if(const auto name=XELP::model(profile))gpu->setProperty("model",const_cast(name),strlen(name)+1);
}
// Capture the unmodified firmware/GT state before admitting a new backend.
- if(!active && profile.family==XELP::Family::AlderLakeS)xe.processKernel(patcher,info);
+ if(!active && (profile.family==XELP::Family::AlderLakeS ||
+ profile.family==XELP::Family::AlderLakeP))xe.processKernel(patcher,info);
if(active) {
XeDataAccess::processKernel(patcher);
// Xe admission reads physical PCI identity. Do not install WEG's ID spoof hooks.
diff --git a/WhateverXe/kern_tgl_native_fia.hpp b/WhateverXe/kern_tgl_native_fia.hpp
index 487142f4..3b03a38f 100644
--- a/WhateverXe/kern_tgl_native_fia.hpp
+++ b/WhateverXe/kern_tgl_native_fia.hpp
@@ -5,7 +5,7 @@
#include
struct TGLNativeFiaContext {
- uintptr_t owner {}; void *port {}; uint32_t ddi {};
+ uintptr_t owner {}; void *port {}; uint32_t ddi {}; bool failed {};
bool matches(uintptr_t thread,void *p) const {
return thread && __atomic_load_n(&owner,__ATOMIC_ACQUIRE)==thread && port==p;
}
@@ -13,21 +13,23 @@ struct TGLNativeFiaContext {
// The existing recursive mode mutex serializes writers. Only the owning
// thread reads port/ddi; other threads stop at the atomic owner check.
struct TGLNativeFiaScope {
- TGLNativeFiaContext &ctx; uintptr_t previousOwner; void *previousPort; uint32_t previousDDI; bool active;
+ TGLNativeFiaContext &ctx; uintptr_t previousOwner; void *previousPort; uint32_t previousDDI; bool previousFailed,active;
TGLNativeFiaScope(TGLNativeFiaContext &c,uintptr_t thread,void *p,uint32_t d,bool enable):
- ctx(c),previousOwner(0),previousPort(nullptr),previousDDI(0),active(enable) {
+ ctx(c),previousOwner(0),previousPort(nullptr),previousDDI(0),previousFailed(false),active(enable) {
if(!active)return;
- previousOwner=__atomic_load_n(&ctx.owner,__ATOMIC_RELAXED);previousPort=ctx.port;previousDDI=ctx.ddi;
+ previousOwner=__atomic_load_n(&ctx.owner,__ATOMIC_RELAXED);previousPort=ctx.port;previousDDI=ctx.ddi;previousFailed=ctx.failed;ctx.failed=false;
ctx.port=p;ctx.ddi=d;__atomic_store_n(&ctx.owner,thread,__ATOMIC_RELEASE);
}
+ bool failed()const{return active && ctx.failed;}
void finish(){if(!active)return;__atomic_store_n(&ctx.owner,0,__ATOMIC_RELEASE);
- ctx.port=previousPort;ctx.ddi=previousDDI;__atomic_store_n(&ctx.owner,previousOwner,__ATOMIC_RELEASE);active=false;}
+ ctx.port=previousPort;ctx.ddi=previousDDI;ctx.failed=previousFailed;__atomic_store_n(&ctx.owner,previousOwner,__ATOMIC_RELEASE);active=false;}
~TGLNativeFiaScope(){finish();}
};
struct TGLNativeFia {
TGLFiaPort fia {}; uint32_t donorMask {};
- // The caller has already observed modular FIA in the powered mode4 probe.
+ // Initial modular coordinates; the host resolves the live layout at the
+ // donor's powered access before using them. No boot-probe prerequisite.
// TC5/6 remain excluded: the native ready switch has no case for them.
bool resolve(uint32_t ddi){TGLTypeCPort tc;
if(ddi<3 || ddi>6 || !TGLTypeCPort::resolve(ddi,tc) || !tc.resolveFia(0x10,fia))return false;
diff --git a/WhateverXe/kern_vbt.hpp b/WhateverXe/kern_vbt.hpp
index 1294f998..a8022a21 100644
--- a/WhateverXe/kern_vbt.hpp
+++ b/WhateverXe/kern_vbt.hpp
@@ -15,12 +15,14 @@ inline uint64_t u64(const uint8_t *p) {return uint64_t(u32(p)) | uint64_t(u32(p+
enum class Status : uint32_t { Missing, Valid, Header, Checksum, Blocks, Version, Children, Conflict, Platform };
struct Port {
uint32_t type {},dvo {},ddc {},aux {},flags {},capabilities {};
+ uint32_t hdmiPreset {UINT32_MAX}; // BDB child byte7, table index; absent firmware uses default.
// flags preserves byte23 (lane reversal, LSPCON, HPD polarity, lane count).
// capabilities preserves byte33 (USB-C/TBT capabilities, NOT current mode).
bool dp() const {return type&4;}
bool internal() const {return type&0x1000;}
- bool fixedTMDS() const {return (type&0x12)==0x12 && !(type&(4|0x1000|0x400|0x20|1)) &&
- !(flags&0x17) && !(capabilities&3);}
+ bool tmdsOnly() const {return (type&0x12)==0x12 && !(type&(4|0x1000|0x400|0x20|1)) &&
+ !(flags&0x17);}
+ bool fixedTMDS() const {return tmdsOnly() && !(capabilities&3);}
};
struct Ports {
Status status {Status::Missing};
@@ -37,6 +39,8 @@ struct Ports {
} panel;
Port port[PortCount];
bool valid() const {return status==Status::Valid;}
+ // Linux direct_aux_ch_map: A=0x40, B/C/D=0x10/0x20/0x30, E..I=0x50..0x90.
+ static uint32_t tglTCAux(unsigned ddi) {return ddi>=3 && ddi=5 ? p-2 : PortCount;
+ if((adapter==DisplayAdapter::ADLN || adapter==DisplayAdapter::ADLP))return p<2 ? p : p>=5 ? p-2 : PortCount;
if(adapter==DisplayAdapter::ADLS)return p==0 ? 0 : p<5 ? p+2 : PortCount;
}
return PortCount;
@@ -184,7 +188,7 @@ inline Ports parse(const void *data,size_t length,Family family) {
const auto d=ddi(family,c[16]);
if(d==PortCount){++r.unmapped;continue;}
if(r.present&(1U<(current_thread()) && t.access==access;
+}
// Immutable first 16 snapshots per cursor lifecycle event; publish on demand.
// No MMIO writes, table repair, allocations or registry publication in hooks.
struct TGLCursorLifetimeRecord { uint64_t q[96]; volatile uint32_t ready; };
@@ -3239,6 +3268,14 @@ bool tglMEILiveSerialize(void *, void *, OSSerialize *serializer) {
return result;
}
+// Bounded main-link AUX decisions. Copy only values already inspected by the
+// lifetime guard; collection does not issue MMIO or native power operations.
+struct TGLComboAUXDecision { uint64_t ns; uint32_t word[14]; };
+static TGLComboAUXDecision tglComboAUXFirst[64] {},tglComboAUXLast[64] {};
+static uint32_t tglComboAUXCount {};
+static void tglComboAUXDecisionRecord(uint32_t *word);
+static bool tglComboAUXDecisionSerialize(void *,void *,OSSerialize *);
+
struct TGLAUXRecord {
uint64_t ns, thread;
uint32_t word[28];
@@ -3252,6 +3289,27 @@ uint32_t tglAUXCounts[9] {};
TGLAUXRecord tglAUXControl[9][64] {};
uint32_t tglAUXControlCounts[9] {};
+static void tglComboAUXDecisionRecord(uint32_t *word) {
+ if(!tglAUXLock)return;
+ TGLComboAUXDecision r {};uint64_t ticks;clock_get_uptime(&ticks);absolutetime_to_nanoseconds(ticks,&r.ns);
+ memcpy(r.word,word,sizeof(r.word));
+ IOSimpleLockLock(tglAUXLock);r.word[0]=++tglComboAUXCount;
+ if(r.word[0]<=64)tglComboAUXFirst[r.word[0]-1]=r;
+ tglComboAUXLast[(r.word[0]-1)%64]=r;IOSimpleLockUnlock(tglAUXLock);
+}
+static bool tglComboAUXDecisionSerialize(void *,void *,OSSerialize *serializer) {
+ if(!tglAUXLock)return false;
+ constexpr size_t size=16+sizeof(tglComboAUXFirst)+sizeof(tglComboAUXLast);
+ auto copy=static_cast(IOMalloc(size));if(!copy)return false;
+ auto h=reinterpret_cast(copy);h[0]=1;h[2]=64;h[3]=sizeof(TGLComboAUXDecision);
+ IOSimpleLockLock(tglAUXLock);h[1]=tglComboAUXCount;
+ memcpy(copy+16,tglComboAUXFirst,sizeof(tglComboAUXFirst));
+ memcpy(copy+16+sizeof(tglComboAUXFirst),tglComboAUXLast,sizeof(tglComboAUXLast));
+ IOSimpleLockUnlock(tglAUXLock);
+ auto data=OSData::withBytes(copy,size);IOFree(copy,size);if(!data)return false;
+ const bool ok=data->serialize(serializer);data->release();return ok;
+}
+
// Bounded lifecycle evidence, separate from bulk AUX. Capture arguments and
// existing native results only; never perform diagnostic hardware accesses.
struct TGLTransitionRecord { uint64_t q[16]; };
@@ -3742,19 +3800,29 @@ void XE::selectProfile(const XELP::Profile &profile) {
adlnNativeRCSProfileRequested=false;
adlnRequested=profile.admitted() && profile.family==XELP::Family::AlderLakeN &&
profile.display==XELP::Display::XeLPD && profile.gt==XELP::GT::ADLNSmall;
- adlnDetected=adlnRequested;
- modDVMTCalcFix.available=profile.admitted();
+ modDVMTCalcFix.available=profile.displayAdmitted();
const bool nativeOS=XELP::kernelOS(version)!=XELP::OS::Unknown && XELP::kernelOS(version)!=XELP::OS::Donor;
- display13FramebufferRequested=profile.admitted() && profile.display==XELP::Display::XeLPD &&
- XELP::displayAdapter(profile.family)==XELP::DisplayAdapter::ADLN && nativeOS;
+ adlpDisplaySelected=profile.mobileDisplay13() && nativeOS;
+ // Preserve the accepted Display13/TC handoff when enabling Render.
+ // noaccel still selects the exact FB-only rejection path.
+ adlpRenderRequested=adlpDisplaySelected && !checkKernelArgument("-wex-noaccel");
+ adlnRequested=adlnRequested || adlpRenderRequested;
+ adlnDetected=adlnRequested;
+ display13FramebufferRequested=profile.displayAdmitted() && profile.display==XELP::Display::XeLPD &&
+ (XELP::displayAdapter(profile.family)==XELP::DisplayAdapter::ADLN || adlpDisplaySelected) && nativeOS;
display13TraceEnabled=display13FramebufferRequested && checkKernelArgument("-wex-display13-trace");
+ display13Offline=display13FramebufferRequested && checkKernelArgument("-wex-display13-offline");
if(display13FramebufferRequested)currentFramebuffer=&kextIntelTGLFb;
+ if(adlpDisplaySelected && !adlpRenderRequested) {
+ tglRequested=tglAcceleratorRequested=false;currentGraphics=&kextIntelTGL;return;
+ }
if(adlnRequested) {
adlnKernelReady=nativeOS;
adlnNativeRequested=adlnKernelReady;
adlnNativeRCSProfileRequested=adlnNativeRequested && !checkKernelArgument("-wex-adln-kernel-ctx");
if(adlnNativeRequested) {
- tglRequested=tglAcceleratorRequested=true;currentGraphics=&kextIntelTGL;
+ tglRequested=true;tglAcceleratorRequested=!checkKernelArgument("-wex-noaccel");
+ currentGraphics=&kextIntelTGL;
}
return;
}
@@ -3774,11 +3842,26 @@ void XE::selectProfile(const XELP::Profile &profile) {
void XE::deinit() {}
+Display13OutputWiring XE::display13BoardWiring(IOService *provider) {
+ auto property=provider->getProperty("xelp-hdmi-wiring");
+ if(!property)property=provider->getProperty("tgl-hdmi-wiring");
+ if(!xeProfile.displayAdmitted() || xeProfile.display!=XELP::Display::XeLPD)return {};
+ // Display13 physical capacity is shared by ADL-N and mobile ADL/RPL.
+ // VBT chooses board routing; a family name must not exclude TC PHYs.
+ if(!property)return XELP::VBT::display13Fixed(firmwarePorts,Display13OutputWiring::Physical);
+ // Preserve the older ADL-N Combo property contract. It cannot select TC.
+ if(adlpDisplaySelected)return {};
+ auto wiring=OSDynamicCast(OSData,property);
+ return XELP::VBT::selectHDMI(true,wiring?wiring->getBytesNoCopy():nullptr,
+ wiring?wiring->getLength():0,firmwarePorts,3);
+}
+
void XE::adlnBootstrap(IOPCIDevice *pci) {
pci->setProperty("N97BringupVersion", 1ULL, 32);
pci->setProperty("N97NativeStartAllowed", false);
const auto revision = pci->configRead8(8);
- const bool renderRequested = adlnRequested && adlnKernelReady && xelpAlderLakeNDevice(pci);
+ const bool renderRequested = adlnRequested && adlnKernelReady && xelpDisplay13RenderDevice(pci) &&
+ !checkKernelArgument("-wex-noaccel");
const bool displayRequested = display13FramebufferRequested && xelpDisplay13Device(pci);
const bool admitted = renderRequested || displayRequested;
pci->setProperty("N97Admission", admitted);
@@ -3790,16 +3873,12 @@ void XE::adlnBootstrap(IOPCIDevice *pci) {
pci->setProperty("N97PhysicalMemoryLayout",memory,sizeof(memory));
// Bootstrap runs before framebuffer start loads its board policy. Native
// GOP qualification must already use that physical port, not assume B.
- auto property=pci->getProperty("xelp-hdmi-wiring");
- if(!property)property=pci->getProperty("tgl-hdmi-wiring");
- auto wiring=OSDynamicCast(OSData,property);
- const auto board=XELP::VBT::selectHDMI(property!=nullptr,wiring?wiring->getBytesNoCopy():nullptr,
- wiring?wiring->getLength():0,firmwarePorts,3);
- if(displayRequested && !Display13FBTopology::supportedWiring(board)) {
+ const auto board=display13BoardWiring(pci);adlnOutputWiring=board;
+ if(displayRequested && !Display13FBTopology::supportedWiring(board,xeProfile.display==XELP::Display::XeLPD)) {
pci->setProperty("N97NativeRoutingUnavailable",true);return;
}
auto bootDDI=displayRequested ? UINT32_MAX : 1U;
- uint64_t connector[] {1,displayRequested,wiring!=nullptr,board.valid,board.hdmi,bootDDI};
+ uint64_t connector[] {1,displayRequested,pci->getProperty("xelp-hdmi-wiring")!=nullptr,board.valid,board.hdmi,bootDDI};
pci->setProperty("N97NativeBootConnector",connector,sizeof(connector));
pci->setProperty("N97DisplayVersion", AlderLakeN::DisplayVersion, 32);
pci->setProperty("N97GTStep", revision==0 ? "A0" : "unknown");
@@ -3819,6 +3898,7 @@ void XE::adlnBootstrap(IOPCIDevice *pci) {
uint32_t read(uint32_t offset) const { return *reinterpret_cast(base + offset); }
void write(uint32_t offset,uint32_t value) { *reinterpret_cast(base + offset)=value; }
void delay() { IODelay(100); }
+ void delayUs(unsigned us) { IODelay(us); }
uint64_t readPTE(uint32_t offset) { return *reinterpret_cast(base+offset); }
void writePTE(uint32_t offset,uint64_t value) { *reinterpret_cast(base+offset)=value; }
uint32_t apertureRead(uint32_t offset) { return *reinterpret_cast(aperture+offset); }
@@ -3840,6 +3920,20 @@ void XE::adlnBootstrap(IOPCIDevice *pci) {
pci->setProperty("N97StolenReservation",reservation,sizeof(reservation));
auto result = AlderLakeN::readDisplay(io, barReady);
pci->setProperty("N97Display13Boot", &result, sizeof(result));
+ // Bounded pre-native evidence for selected fixed TC ports, including ports
+ // which firmware owns without routing a transcoder to them.
+ uint32_t bootTC[1+4*8] {1},bootPower=0;
+ if(barReady && AlderLakeN::value(result,0x45404,bootPower) && (bootPower&5)==5) {
+ for(unsigned d=3;d<7;++d)if(board.selected()&(1U<setProperty("WEXDisplay13TCBootPorts",bootTC,sizeof(bootTC));
+ }
+
bool bootstrapReady=true;
if(displayRequested && linearTest && (result.activePipes&(result.activePipes-1))) {
auto parked=Display13Boot::normalize(io,result,barReady && Display13FBTopology::multiOutputWiring(board) && xelpMemoryMatches(pci),board,xelpMemory);
@@ -3848,17 +3942,27 @@ void XE::adlnBootstrap(IOPCIDevice *pci) {
pci->setProperty("N97DualGOPAfter",&result,sizeof(result));
bootstrapReady=parked.status==Display13Boot::Status::Complete;
}
+ if(displayRequested && linearTest && bootstrapReady && Display13FBTopology::multiOutputWiring(board)) {
+ auto unused=Display13Boot::releaseUnusedTC(io,result,barReady && xelpMemoryMatches(pci),board);
+ pci->setProperty("WEXDisplay13UnusedTC",&unused,sizeof(unused));
+ bootstrapReady=unused.status==Display13Boot::Status::Complete;
+ }
if(displayRequested && bootstrapReady) {
uint32_t trans=UINT32_MAX;
if(AlderLakeN::value(result,0x60400,trans))bootDDI=Display13FBTopology::bootstrapDDI(board,
- result.status==AlderLakeN::ReadoutStatus::Complete,result.poweredPipes,result.activePipes,trans);
+ result.status==AlderLakeN::ReadoutStatus::Complete,result.poweredPipes,result.activePipes,trans,true);
connector[5]=bootDDI;pci->setProperty("N97NativeBootConnector",connector,sizeof(connector));
}
pci->setProperty("N97DisplayReadoutStatus", static_cast(result.status), 32);
SYSLOG("igfx", "AlderLakeN Display13 readout status=%u powered=%x active=%x hdmiB=%x; donor blocked",
static_cast(result.status), result.poweredPipes, result.activePipes, result.hdmiBPipes);
- if (linearTest) {
- auto linear=AlderLakeN::reinitializeLinear(io,result,barReady && xelpMemoryMatches(pci),bootDDI);
+ // Mobile and TC configurations adopt a validated firmware surface at
+ // controller start. Preserve the established ADL-N Combo bootstrap.
+ if (linearTest && !Display13FBTopology::deferredBootPrepare(board,adlpDisplaySelected)) {
+ auto tc=Display13TC::Boot::capture(io,barReady && bootDDI>=3 && xelpMemoryMatches(pci),bootDDI,0);
+ if(bootDDI>=3)pci->setProperty("WEXDisplay13TCBoot",&tc,sizeof(tc));
+ auto linear=AlderLakeN::reinitializeLinear(io,result,barReady && xelpMemoryMatches(pci),bootDDI,
+ AlderLakeN::GOPProtocol::TMDS,bootDDI>=3 ? &tc : nullptr);
pci->setProperty("N97Display13Linear",&linear,sizeof(linear));
auto after=AlderLakeN::readDisplay(io,barReady);
pci->setProperty("N97Display13After",&after,sizeof(after));
@@ -3961,7 +4065,7 @@ void XE::adlnBootstrap(IOPCIDevice *pci) {
if (gtTest) {
uint32_t edram=UINT32_MAX;
auto gt=AlderLakeN::readGT(io,barReady && xelpMemoryMatches(pci),
- adlnNativeRequested ? &edram : nullptr);
+ adlnNativeRequested ? &edram : nullptr,adlpRenderRequested ? XELP::decodeADLPGT : nullptr);
if (adlnNativeRequested) {
adlnNativeSubslices=gt.resources.nativeSubslices; adlnL3Banks=gt.resources.l3Banks; adlnNativeEUs=gt.resources.eus;
renderReady=gt.status==AlderLakeN::GTStatus::Complete && !(edram&1);
@@ -3983,16 +4087,27 @@ void XE::adlnBootstrap(IOPCIDevice *pci) {
((!optionProperty || opts) && !mapperOptions && !systemMapper);
uint64_t mapperAdmission[] {1,mapperOptions,systemMapper,physicalOptions};
IORegistryEntry::getRegistryRoot()->setProperty("N97NativeMapperAdmission",mapperAdmission,sizeof(mapperAdmission));
- const bool prefix=Display13Boot::reservedPrefix(io,display,bootstrapReady,bootDDI,xelpMemory);
+ const bool dp=bootDDI<7 && (board.dp&(1U<=3 && xelpMemoryMatches(pci),bootDDI,0,dp);
+ if(bootDDI>=3 && bootDDI<7)pci->setProperty("WEXDisplay13TCBoot",&tc,sizeof(tc));
+ AlderLakeN::LinearPlan bootPlan;
+ uint32_t sourceProof[]={1,bootDDI,uint32_t(protocol),uint32_t(AlderLakeN::linearPlan(
+ display,bootPlan,bootDDI,protocol,bootDDI>=3?&tc:nullptr)),tc.valid()};
+ pci->setProperty("N97NativeBootSource",sourceProof,sizeof(sourceProof));
+ const bool prefix=Display13Boot::reservedPrefix(io,display,bootstrapReady,bootDDI,xelpMemory,
+ bootDDI>=3 ? &tc : nullptr,protocol);
auto aperture=pci->getDeviceMemoryWithRegister(0x18);
const bool memoryReady=
aperture && aperture->getLength()==0x10000000 && page_size==4096 &&
physicalOptions && (pci->configRead16(4)&6)==6 &&
checkKernelArgument("-allow3d") && checkKernelArgument("-disablegfxfirmware") &&
!checkKernelArgument("-wex-adln-scanout") && !checkKernelArgument("-wex-adln-gpu");
- // This shared admission remains the conjunction of all three
- // certificates; selecting a Display adapter never admits a GT.
- adlnNativeReady=renderReady && prefix && memoryReady;
+ // FB can adopt its boot memory without initializing a render engine.
+ // Keep the stricter GT certificate for every accelerator consumer.
+ display13BootReady=prefix && memoryReady;
+ adlnNativeReady=renderReady && display13BootReady;
+ pci->setProperty("WEXDisplay13BootReady",display13BootReady);
pci->setProperty("N97NativePreflight",adlnNativeReady);
pci->setProperty("N97NativeReservedGGTT",prefix);
}
@@ -4047,7 +4162,7 @@ void XE::adlnBootstrap(IOPCIDevice *pci) {
}
bool XE::xelpBindProbe(void *controller) {
- if(!xelpProviderProbe || !xelpProviderConfigureProbe || !xeProfile.admitted())return false;
+ if(!xelpProviderProbe || !xelpProviderConfigureProbe || !xeProfile.displayAdmitted())return false;
XELP::ProbeConfig config;config.profile=xeProfile;
if(adlsDisplaySelected) {
if(!tglHDMIWiringApplied || !adlsWiring.valid || !adlsWiring.selected())return false;
@@ -4060,9 +4175,11 @@ bool XE::xelpBindProbe(void *controller) {
config.legacyBackend=tglAUXParentMask;
} else {
if(!display13FBTopologyHooksReady || controller!=adlnTopologyController ||
- !Display13FBTopology::adlnWiring(adlnOutputWiring) ||
- (adlnOutputWiring.dp && (!display13DPProbeReady || !display13FBSSTHooksReady || !display13FBAUXHooksReady)))return false;
- config.combo=adlnOutputWiring.selected();config.comboBackend=3;
+ !Display13FBTopology::supportedWiring(adlnOutputWiring,xeProfile.display==XELP::Display::XeLPD) ||
+ ((adlnOutputWiring.selected()&0x78) && (!display13TCProbeReady || !display13FBTCHooksReady)) ||
+ (adlnOutputWiring.dp && (!display13TCDPProbeReady || !display13FBSSTHooksReady || !display13FBAUXHooksReady)))return false;
+ config.combo=adlnOutputWiring.selected()&3;config.comboBackend=3;
+ config.legacy=adlnOutputWiring.selected()&0x78;config.legacyBackend=config.legacy?0x78:0;
}
const bool bound=config.valid() && reinterpret_cast(xelpProviderConfigureProbe)(controller,&config);
IORegistryEntry::getRegistryRoot()->setProperty("XELPSharedProbeBound",bound);
@@ -4095,6 +4212,56 @@ void XE::processKernel(KernelPatcher &patcher, DeviceInfo *info) {
// This must precede tglLoadHDMIWiring, tglPrepareMMIOUC and TGL FIA reads.
auto physicalGPU = OSDynamicCast(IOPCIDevice, info->videoBuiltin);
selectProfile(XELP::deviceProfile(physicalGPU));
+ // ADLP_LOAD_ONLY_KERNEL: do not enter TGL MMIO/FIA, bootstrap or VM setup.
+ const auto physicalEntry=XELP::deviceEntry(xeProfile.identity);
+ adlpLoadOnly=physicalEntry && xeProfile.mobileDisplay13() &&
+ XELP::hostOS(XELP::kernelOS(version)) && checkKernelArgument("-wex-adlp-load-only");
+ if(adlpLoadOnly) {
+ IORegistryEntry::getRegistryRoot()->setProperty("ADLPLoadOnlyKernel",true);
+ SYSLOG("igfx","ADL-P load-only: native FB/accelerator probes will be rejected; no GPU initialization");
+ return;
+ }
+ // ADLP_LOAD_ONLY_KERNEL_END
+ // Explicit diagnostic keeps both native drivers blocked even after the
+ // mobile family is admitted automatically. Only the bounded readout runs.
+ if(xeProfile.mobileDisplay13() && physicalGPU && checkKernelArgument("-wex-adlp-readout") &&
+ XELP::hostOS(XELP::kernelOS(version))) {
+ adlpLoadOnly=true;
+ if(!(physicalGPU->configRead16(4)&2))return;
+ auto map=physicalGPU->mapDeviceMemoryWithRegister(0x10,kIOMapInhibitCache);
+ if(map && map->getVirtualAddress() && map->getLength()==0x1000000) {
+ struct IO {
+ volatile uint8_t *base;
+ bool readable(uint32_t o) const {return o<=0xFFFFFC;}
+ uint32_t read(uint32_t o) {return *reinterpret_cast(base+o);}
+ void write(uint32_t o,uint32_t v) {*reinterpret_cast(base+o)=v;}
+ void delay() {IODelay(100);}
+ } io {reinterpret_cast(map->getVirtualAddress())};
+ uint32_t edram=UINT32_MAX;
+ auto gt=AlderLakeN::readGT(io,true,&edram,XELP::decodeADLPGT);
+ physicalGPU->setProperty("ADLPGTBoot",>,sizeof(gt));
+ physicalGPU->setProperty("ADLPEDRAM",edram,32);
+ uint32_t registers[][2] {
+ {0x42000,0},{0x45404,0},{0x45450,0},{0x45454,0},
+ {0x46000,0},{0x46004,0},{0x46070,0},{0x46078,0},
+ {0x46038,0},{0x46040,0},{0x46048,0},{0x46050,0},
+ {0x4610C,0},{0x46110,0},{0x46114,0},{0x46118,0},
+ {0xC4000,0},{0xC4030,0},{0xC5014,0},{0x44470,0},
+ {0x64000,0},{0x64100,0},{0x64300,0},{0x64400,0},{0x64500,0},{0x64600,0},
+ {0x161500,0},{0x161504,0},{0x161508,0},{0x16150C,0},
+ {0x1010A0,0},{0x1082C0,0},{0x1082C4,0}
+ };
+ for(auto ®:registers)reg[1]=io.read(reg[0]);
+ physicalGPU->setProperty("ADLPDisplayBoot",registers,sizeof(registers));
+ auto display=AlderLakeN::readDisplay(io,true);
+ physicalGPU->setProperty("ADLPScanoutBoot",&display,sizeof(display));
+ SYSLOG("igfx","ADL-P readout status=%u fuses=%x/%x/%x/%x/%x release=%x/%x; donor blocked",
+ static_cast(gt.status),gt.fuses[0],gt.fuses[1],gt.fuses[2],gt.fuses[3],gt.fuses[4],
+ gt.releasedMask,gt.releaseFailedMask);
+ }
+ if(map)map->release();
+ return;
+ }
if(display12SmallGTDetected && XELP::display12SmallGTDevice(xeProfile.identity) &&
!xeProfile.overridden() && physicalGPU &&
XELP::hostOS(XELP::kernelOS(version)) && (physicalGPU->configRead16(4)&2)) {
@@ -4139,7 +4306,8 @@ void XE::processKernel(KernelPatcher &patcher, DeviceInfo *info) {
}
if(map)map->release();
}
- if(!xeProfile.admitted())return;
+ if(!xeProfile.displayAdmitted())return;
+ SYSLOG("igfx","Xe display bootstrap physical=%x FB-only=%u",xeProfile.identity,adlpDisplaySelected);
xelpSelectedGPU=physicalGPU;xelpSelectedProfile=xeProfile;
if (adlnDetected || display13FramebufferRequested) {
xelpReadFirmware(physicalGPU);
@@ -4147,7 +4315,8 @@ void XE::processKernel(KernelPatcher &patcher, DeviceInfo *info) {
modDVMTCalcFix.processKernel(patcher,info);
xelpLoadDisplayPool(physicalGPU);
adlnBootstrap(physicalGPU);
- if (adlnNativeRequested && adlnNativeReady) {
+ SYSLOG("igfx","Display13 bootstrap ready=%u",display13BootReady);
+ if (display13BootReady) {
tglVMPrepare(patcher,physicalGPU);
tglPrepareMMIOUC(patcher,physicalGPU);
auto map=physicalGPU->mapDeviceMemoryWithRegister(0x10);
@@ -4203,6 +4372,12 @@ void XE::processKernel(KernelPatcher &patcher, DeviceInfo *info) {
}
bool XE::processKext(KernelPatcher &patcher, size_t index, mach_vm_address_t address, size_t size) {
+ // No full donor patch set or native display/GT dispatcher in this stage.
+ if(adlpLoadOnly) {
+ if(index==kextIntelTGL.loadIndex || index==kextIntelTGLFb.loadIndex)
+ return processADLPLoadOnly(patcher,index,address,size);
+ return false;
+ }
if((tglRequested || display13FramebufferRequested) && kextAppleIntelTGLCompat.loadIndex==index) {
const auto probe=patcher.solveSymbol(index,"__ZN17AppleIntelPortHAL13probePortModeEv",address,size);
patcher.clearError();
@@ -4216,16 +4391,26 @@ bool XE::processKext(KernelPatcher &patcher, size_t index, mach_vm_address_t add
patcher.clearError();
display13DPProbeReady=query>=address && query-address(query));
+ display13TCProbeReady=query>=address && query-address(query));
+ display13TCDPProbeReady=query>=address && query-address(query));
}
IORegistryEntry::getRegistryRoot()->setProperty("XELPDisplay13DPProbeAvailable",display13DPProbeReady);
+ IORegistryEntry::getRegistryRoot()->setProperty("XELPDisplay13TCProbeAvailable",display13TCProbeReady);
IORegistryEntry::getRegistryRoot()->setProperty("XELPSharedProbeAvailable",xelpProviderProbe && xelpProviderConfigureProbe);
if(adlnRequested)IORegistryEntry::getRegistryRoot()->setProperty("N97FBSharedProbeAvailable",xelpProviderProbe && xelpProviderConfigureProbe);
return true;
}
// Display dispatch is independent of the selected Render implementation.
// Keep both adapters outside the large Display12 framebuffer patch frame.
- if(display13FramebufferRequested && currentFramebuffer && currentFramebuffer->loadIndex==index)
- return processDisplay13Framebuffer(patcher,index,address,size);
+ if(currentFramebuffer && currentFramebuffer->loadIndex==index &&
+ (display13FramebufferRequested || (tglRequested && tglActive)))
+ return processFramebufferKext(patcher,index,address,size);
+ if(adlpDisplaySelected && !adlpRenderRequested) {
+ if(index==kextIntelTGL.loadIndex)return processADLPLoadOnly(patcher,index,address,size);
+ return false;
+ }
if (adlnRequested || adlnDetected) {
if (!adlnDetected || !adlnNativeRequested || !currentGraphics || currentGraphics->loadIndex != index) return false;
// Matching is persistent. A rejected bootstrap must not leave the
@@ -4278,6 +4463,14 @@ struct Display13FBPipeContext {
uint64_t watermarkClockHz {};
};
struct Display13FBPowerContext {
+ IOSimpleLock *tcBankLock {};
+ bool tcSupported {},tcBankFaulted {};
+ Display13TC::Mode tcMode[4];
+ OSObject *tcObject[4] {};
+ uint32_t tcBusy[4] {};
+ bool tcSignalReady[4] {};
+ bool bootDP {}; // Board protocol captured with the protected boot reference.
+ Display13TC::DPRetainedLink tcDPLink[4];
Display13FBPipeContext pipe[3];
Display13Power::NativeBridge bridge;
Display13Power::MainLinkAUX mainLink[2];
@@ -4535,7 +4728,45 @@ static void adlnIRQRecord(uint64_t (&r)[32],unsigned event) {
r[29]=reinterpret_cast(c.lock);r[30]=c.handlers;r[31]=c.quiescing|(c.blockedPipes<<8)|(uint64_t(c.softwareHandlers)<<32);
}
+// These operations run under the display owner lock. AUX power qualification
+// needs the TC bank lock but never enters the native port or power callbacks.
+struct Display13FBTCAuxIO {
+ IOMemoryMap *map;unsigned tc;
+ bool admitted(unsigned candidate) const {
+ auto &c=display13FBPower;
+ return candidate==tc && tc<4 && c.tcSupported && c.tcBankLock && !c.tcBankFaulted &&
+ c.bridge.begun && (c.bridge.selectedPorts&(1U<<(tc+3))) && map==c.map &&
+ map && map->getLength()==0x1000000 && map->getVirtualAddress();
+ }
+ bool readable(uint32_t r) const {return admitted(tc) && !(r&3) && r<=0xfffffc;}
+ uint32_t read(uint32_t r) const {return *reinterpret_cast(map->getVirtualAddress()+r);}
+ void write(uint32_t r,uint32_t v) {*reinterpret_cast(map->getVirtualAddress()+r)=v;}
+ bool lanesPowered() const {
+ if(!admitted(tc))return false;
+ const auto pw=read(Display13Power::Driver),buffer=read(0x64300+0x100*tc),mode=read(0x161500+4*tc);
+ return pw!=UINT32_MAX && buffer!=UINT32_MAX && mode!=UINT32_MAX && (pw&5)==5 &&
+ (buffer&64) && (mode&7)==4;
+ }
+ bool auxPowered(unsigned candidate) const {
+ if(!admitted(candidate))return false;const auto value=read(Display13Power::AUX);
+ return value!=UINT32_MAX && (value&(1U<<(2*(tc+3))));
+ }
+ IOInterruptState lock(){return IOSimpleLockLockDisableInterrupt(display13FBPower.tcBankLock);}
+ void unlock(IOInterruptState irq){IOSimpleLockUnlockEnableInterrupt(display13FBPower.tcBankLock,irq);}
+ void bankFault(){display13FBPower.tcBankFaulted=true;}
+};
+static bool display13TCAuxSupported() {return display13FBPower.tcSupported && display13FBPower.tcBankLock && !display13FBPower.tcBankFaulted;}
+static bool display13TCPrepareAUX(IOMemoryMap *map,unsigned tc) {
+ Display13FBTCAuxIO io{map,tc};return Display13TC::Aux::prepare(io,tc).status==Display13TC::Status::Complete;
+}
+static bool display13TCHealthyAUX(IOMemoryMap *map,unsigned tc) {
+ Display13FBTCAuxIO io{map,tc};bool healthy=false;
+ return Display13TC::Aux::health(io,tc,healthy).status==Display13TC::Status::Complete && healthy;
+}
struct Display13FBPowerIO {
+ bool tcAuxSupported(){return display13TCAuxSupported();}
+ bool prepareTCAux(unsigned tc){return display13TCPrepareAUX(map,tc);}
+ bool tcAuxHealthy(unsigned tc){return display13TCHealthyAUX(map,tc);}
IOMemoryMap *map;
bool readable(uint32_t r) const {
return map && map->getVirtualAddress() && map->getLength()==0x1000000 &&
@@ -4561,6 +4792,70 @@ struct Display13FBBootIO : Display13FBPowerIO {
return map && map->getVirtualAddress() && map->getLength()==0x1000000 && r<=0x1000000-4;
}
};
+struct Display13FBTCIO : Display13FBTCAuxIO {
+ OSObject *object;Display13Power::Result linkResult;
+ Display13FBTCIO(OSObject *p,unsigned t):Display13FBTCAuxIO{display13FBPower.map,t},object(p) {}
+ bool accessHeld() {
+ if(!admitted(tc))return false;auto &c=display13FBPower;const auto &mode=c.tcMode[tc];
+ if(!object || c.tcObject[tc]!=object || mode.controller!=reinterpret_cast(c.controller) ||
+ mode.object!=reinterpret_cast(object) || mode.accessLease.owner!=&c.bridge.state ||
+ mode.accessLease.domains!=Display13Power::dependencies(Display13TC::Mode::accessDomains()))return false;
+ Display13FBPowerIO io{map};return Display13Power::held(io,c.bridge.state,mode.accessLease.domains);
+ }
+ bool powerHeld() {
+ if(!accessHeld())return false;auto &c=display13FBPower;const auto &mode=c.tcMode[tc];
+ if(mode.auxLease.owner!=&c.bridge.state ||
+ mode.auxLease.domains!=Display13Power::dependencies(Display13Power::auxDomain(tc+3)))return false;
+ Display13FBPowerIO io{map};return Display13Power::held(io,c.bridge.state,mode.auxLease.domains);
+ }
+ bool linkHeld() {
+ Display13FBPowerIO io{map};return powerHeld() && display13FBPower.bridge.ddiHeld(io,tc+3);
+ }
+ // Native enableClocks/disableClocks also own the DDI reference. Our
+ // replacement must reproduce that contract after/before the PLL change.
+ bool linkPower(bool enable) {
+ auto &c=display13FBPower;Display13FBPowerIO io{map};const auto ddi=tc+3;
+ if(!Display13Power::objectAssociations(c.power,0x80,c.controller,0xC60))return false;
+ auto &r=linkResult;r.status=Display13Power::Status::Complete;
+ if(enable) {
+ if(!c.bridge.retainsDDI(ddi))r=c.bridge.getDDI(io,ddi);
+ } else if(c.bridge.nativeDDIRefs(ddi)) {
+ if(c.bridge.nativeDDIRefs(ddi)!=1)return false;
+ // The protected GOP ref is transferred by adoptBootPath, not us.
+ if(!(c.bridge.bootPort==ddi && c.bridge.bootDDIProtected))r=c.bridge.putDDI(io,ddi);
+ }
+ return r.status==Display13Power::Status::Complete &&
+ Display13Power::publishObject(c.bridge,c.power,0x80,c.controller,0xC60);
+ }
+ bool unstarted() {
+ // A failed mode acquisition never started a PLL. Cleanup must not
+ // demand full output power or turn that known local failure global.
+ auto &c=display13FBPower;Display13TC::Port p;
+ if(!accessHeld() || !Display13TC::Port::resolve(tc+3,p) || c.bridge.nativeDDIRefs(tc+3) ||
+ !quiescent(tc+3))return false;
+ const auto pll=read(p.pll);
+ return pll!=UINT32_MAX && !(pll&0xcc000000U) && !c.tcSignalReady[tc];
+ }
+ bool owned(unsigned ddi) {
+ const auto &m=display13FBPower.tcMode[tc];
+ return ddi==tc+3 && powerHeld() && m.phase==Display13TC::Mode::Phase::Active &&
+ m.ownership.active && m.ownership.identity==reinterpret_cast(object) &&
+ Display13FBMemory::field(object,0x550)==4;
+ }
+ bool quiescent(unsigned ddi) {
+ if(ddi!=tc+3 || !accessHeld())return false;
+ const auto buffer=read(0x64000+0x100*ddi),power=read(Display13Power::Driver);
+ if(buffer==UINT32_MAX || power==UINT32_MAX || (buffer&0x80000000))return false;
+ for(unsigned pipe=0;pipe<3;++pipe)if(power&(1U<<(10+2*pipe))) {
+ const auto trans=read(0x60400+0x1000*pipe),conf=read(0x70008+0x1000*pipe),clock=read(0x46140+4*pipe);
+ if(trans==UINT32_MAX || conf==UINT32_MAX || clock==UINT32_MAX)return false;
+ if(((trans>>27)&15)==ddi+1 && (trans&0x80000000))return false;
+ if((clock>>28)==ddi+3 && (conf&0xc0000000))return false;
+ }
+ return true;
+ }
+ void delayUs(unsigned us){IODelay(us);}
+};
// Only used after native object/BAR binding and the power lease proof. The
// common SST/signal sequencers choose all addresses; no caller register input.
struct Display13FBSSTIO {
@@ -4772,8 +5067,12 @@ static Display13FBMemory::Readout display13FBInspectMemory(IOService *fb,bool bo
uint32_t read(uint32_t reg) const {return *reinterpret_cast(map->getVirtualAddress()+reg);}
uint64_t readPTE(uint32_t reg) const {return *reinterpret_cast(map->getVirtualAddress()+reg);}
} io {c.map};
+ const auto tc=Display13TC::Boot::capture(io,bootScanout && c.bridge.bootPort>=3,c.bridge.bootPort,0);
+ const bool dp=bootScanout && c.bootDP;
+ const auto source=AlderLakeN::readDisplay(io,dp);
return Display13FBMemory::inspect(io,controller,controller->getMetaClass()->getClassSize(),
- fb,fb->getMetaClass()->getClassSize(),aperture->getLength(),xelpMemory,bootScanout,display13FBExpectedOwner(controller),c.bridge.bootPort);
+ fb,fb->getMetaClass()->getClassSize(),aperture->getLength(),xelpMemory,bootScanout,display13FBExpectedOwner(controller),c.bridge.bootPort,
+ c.bridge.bootPort>=3 ? &tc : nullptr,dp?&source:nullptr);
}
}
@@ -4787,19 +5086,17 @@ bool XE::processDisplay13Framebuffer(KernelPatcher &patcher,size_t index,mach_vm
"__ZN19AppleIntelPowerWell18enablePowerWellDDIEj",
"__ZN19AppleIntelPowerWell19disablePowerWellDDIEj",
"__ZN19AppleIntelPowerWell18enablePowerWellAuxEj",
- "__ZN19AppleIntelPowerWell19disablePowerWellAuxEj",
- "__ZN31AppleIntelFramebufferController5startEP9IOService"
+ "__ZN19AppleIntelPowerWell19disablePowerWellAuxEj"
};
- static const uintptr_t offsets[] {0x59F88,0x5BEDC,0x5B366,0x5BFA8,0x5AA86,0x5C4BA,0x5AE0A,0x3F1FC};
- static const uint8_t prefixes[8][24] {
+ static const uintptr_t offsets[] {0x59F88,0x5BEDC,0x5B366,0x5BFA8,0x5AA86,0x5C4BA,0x5AE0A};
+ static const uint8_t prefixes[7][24] {
{0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec,0x18,0x48,0xff,0x05,0xe8,0x76,0x08,0x00},
{0x55,0x48,0x89,0xe5,0x53,0x50,0x48,0x89,0xfb,0x48,0xff,0x05,0xac,0x5e,0x08,0x00,0x85,0xf6,0x74,0x18,0xb8,0x01,0x00,0x00},
{0x55,0x48,0x89,0xe5,0x53,0x50,0x48,0x89,0xfb,0x48,0xff,0x05,0x5a,0x67,0x08,0x00,0x85,0xf6,0x74,0x23,0xb8,0x01,0x00,0x00},
{0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x50,0x48,0xff,0x05,0x23,0x5e,0x08,0x00,0x89,0xf0,0x8b},
{0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec,0x18,0x48,0xff,0x05,0x92,0x6e,0x08,0x00},
{0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec,0x48,0x48,0xff,0x05,0x26,0x5a,0x08,0x00},
- {0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec,0x28,0x48,0xff,0x05,0xb6,0x6b,0x08,0x00},
- {0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec,0x38,0x49,0x89,0xf6,0x49,0x89,0xfd,0x48}
+ {0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec,0x28,0x48,0xff,0x05,0xb6,0x6b,0x08,0x00}
};
bool known=display13FramebufferRequested && size>0x5C4D2;
for(unsigned i=0;i(address+0x3FE26),ownerStore,sizeof(ownerStore));
if(!known)return false;
if(!display13FBPower.lock)display13FBPower.lock=IOLockAlloc();
- KernelPatcher::RouteRequest start(symbols[7],wrapDisplay13FramebufferStart,orgDisplay13FramebufferStart);
- const bool startRouted=patcher.routeMultiple(index,&start,1,address,size);patcher.clearError();
- if(!startRouted)return false;
// Apply DVMT calculation before routing FBMemMgr_Init: the common
// module scans its native entry, not our later trampoline. Startup is
// already guarded, so missing opt-in/failed patch cannot run the bad formula.
@@ -4979,11 +5273,13 @@ bool XE::processDisplay13Framebuffer(KernelPatcher &patcher,size_t index,mach_vm
display13FBIRQReady=processDisplay13FramebufferIRQ(patcher,index,address,size);
IORegistryEntry::getRegistryRoot()->setProperty("N97FBIRQHooksReady",display13FBIRQReady);
- display13FBABIReady=processDisplay13FramebufferABI(patcher,index,address,size);
+ display13FBABIReady=framebufferDonorCommonReady;
IORegistryEntry::getRegistryRoot()->setProperty("N97FBCommonABIReady",display13FBABIReady);
display13FBLifecycleHooksReady=processDisplay13FramebufferLifecycle(patcher,index,address,size);
IORegistryEntry::getRegistryRoot()->setProperty("N97FBLifecycleHooksReady",display13FBLifecycleHooksReady);
display13FBOutputHooksReady=processDisplay13FramebufferOutput(patcher,index,address,size);
+ display13FBTCHooksReady=processDisplay13FramebufferTC(patcher,index,address,size);
+ IORegistryEntry::getRegistryRoot()->setProperty("WEXDisplay13TCHooksReady",display13FBTCHooksReady);
IORegistryEntry::getRegistryRoot()->setProperty("N97FBOutputHooksReady",display13FBOutputHooksReady);
display13FBGTTHooksReady=processDisplay13FramebufferGTT(patcher,index,address,size);
IORegistryEntry::getRegistryRoot()->setProperty("N97FBGTTHooksReady",display13FBGTTHooksReady);
@@ -5066,7 +5362,127 @@ bool XE::processDisplay13FramebufferLifecycle(KernelPatcher &patcher,size_t inde
!memcmp(reinterpret_cast(address+0x68C38),removeCall,sizeof(removeCall));
KernelPatcher::RouteRequest pathRoute {pathSymbol,wrapAlderLakeNFreePath,orgAlderLakeNFreePath};
const bool pathRouted=disableRouted && pathKnown && patcher.routeMultipleShort(index,&pathRoute,1,address,size);
- patcher.clearError();return pathRouted;
+ patcher.clearError();
+ return pathRouted && (!Display13FBTopology::multiOutputWiring(adlnOutputWiring) ||
+ processDisplay13Retirement(patcher,index,address,size));
+}
+
+// These hooks are installed only for Display13's existing multi-output owner.
+// Keep native startup, StopTransactions, disableDisplay and successful cleanup.
+bool XE::processDisplay13Retirement(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) {
+ using namespace Display13Retirement;
+ const char *freeAll="__ZN21AppleIntelFramebuffer20freeDisplayResourcesEv";
+ const char *allocate="__ZN21AppleIntelFramebuffer24allocateDisplayResourcesEv";
+ const uint8_t freeEntry[]={0x55,0x48,0x89,0xe5,0x53,0x50};
+ const uint8_t allocateEntry[]={0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec,0x18};
+ const auto f=patcher.solveSymbol(index,freeAll,address,size);patcher.clearError();
+ const auto a=patcher.solveSymbol(index,allocate,address,size);patcher.clearError();
+ if(size<=ReleaseCall+sizeof(ReleaseBefore) || f!=address+0x67e8e || a!=address+0x67272 ||
+ memcmp(reinterpret_cast(f),freeEntry,sizeof(freeEntry)) ||
+ memcmp(reinterpret_cast(a),allocateEntry,sizeof(allocateEntry)) ||
+ memcmp(reinterpret_cast(address+Loop),Before,sizeof(Before)) ||
+ memcmp(reinterpret_cast(address+ReleaseCall),ReleaseBefore,sizeof(ReleaseBefore)))return false;
+ uint8_t after[sizeof(Before)];patch(after,reinterpret_cast(retireDisplay13Paths));
+ const struct {uint32_t offset;const uint8_t *before,*after;size_t bytes;} changes[]={
+ {Loop,Before,after,sizeof(Before)},
+ {ReleaseCall,ReleaseBefore,ReleaseAfter,sizeof(ReleaseBefore)}};
+ for(const auto &change:changes) {
+ KernelPatcher::LookupPatch edit{currentFramebuffer,change.before,change.after,change.bytes,1};
+ patcher.applyLookupPatch(&edit,reinterpret_cast(address+change.offset),change.bytes+1);
+ const bool ok=patcher.getError()==KernelPatcher::Error::NoError &&
+ !memcmp(reinterpret_cast(address+change.offset),change.after,change.bytes);
+ patcher.clearError();if(!ok)return false;
+ }
+ // Short prologues end before the native RIP-relative counters.
+ KernelPatcher::RouteRequest routes[]={
+ {freeAll,wrapDisplay13FreeResources,orgDisplay13FreeResources},
+ {allocate,wrapDisplay13AllocateResources,orgDisplay13AllocateResources}};
+ const bool ready=patcher.routeMultipleShort(index,routes,arrsize(routes),address,size);patcher.clearError();
+ IORegistryEntry::getRegistryRoot()->setProperty("WEXDisplay13RetirementReady",ready);
+ return ready;
+}
+
+bool XE::display13RetirementHealthy() {
+ if(!callbackXE->display13FramebufferRequested ||
+ !Display13FBTopology::multiOutputWiring(callbackXE->adlnOutputWiring))return true;
+ auto &c=display13FBPower;if(!c.lock)return false;
+ IOLockLock(c.lock);const bool ok=!c.memoryFaulted && !c.dbuf.faulted;IOLockUnlock(c.lock);
+ return ok;
+}
+
+bool XE::wrapDisplay13AllocateResources(void *fb) {
+ // Failed retirement retains the native allocated flag/path and WEX owner.
+ // Do not start another allocation after a partial native memory cleanup.
+ if(!display13RetirementHealthy())return false;
+ return FunctionCast(wrapDisplay13AllocateResources,callbackXE->orgDisplay13AllocateResources)(fb);
+}
+
+uint32_t XE::retireDisplay13Paths(void *raw) {
+ auto fb=static_cast(raw);auto &c=display13FBPower;
+ const auto fail=[&]() {
+ if(c.lock){IOLockLock(c.lock);adlnFault(c.memoryFaulted,__LINE__);IOLockUnlock(c.lock);}
+ return kIOReturnNotReady;
+ };
+ if(!fb || fb->getMetaClass()->getClassSize()<0x4b90 ||
+ Display13FBMemory::field(fb,0x1d0)!=c.controller)return fail();
+ auto mutex=Display13FBMemory::field(fb,0x4220);if(!mutex)return fail();
+ // The free wrapper already holds controller+0x9b8. Acquire FB only after
+ // native StopTransactions/disableDisplay (whose callbacks also take it).
+ IORecursiveLockLock(mutex);
+ uint32_t error=0;
+ for(unsigned n=0;n<2;++n) {
+ const auto count=Display13FBMemory::field(fb,0x49e8);
+ if(!count)break;
+ auto path=Display13FBMemory::field(fb,0x49f0);
+ if(count>2 || !path || path->getMetaClass()->getClassSize()<0x3730 ||
+ Display13FBMemory::field(path,0x32b0)!=fb ||
+ Display13FBMemory::field(path,0x3644)==2) {error=fail();break;}
+ error=releaseDisplay13Path(fb,path);
+ if(error || Display13FBMemory::field(fb,0x49e8)!=count-1) {error=fail();break;}
+ }
+ if(!error && Display13FBMemory::field(fb,0x49e8))error=fail();
+ IORecursiveLockUnlock(mutex);return error;
+}
+
+uint32_t XE::wrapDisplay13FreeResources(void *raw) {
+ auto fb=static_cast(raw);auto &c=display13FBPower;
+ if(!fb || fb->getMetaClass()->getClassSize()<0x4b90 || !c.controller ||
+ Display13FBMemory::field(fb,0x1d0)!=c.controller)return kIOReturnNotReady;
+ auto mutex=Display13FBMemory::field(c.controller,0x9b8);
+ if(!mutex)return kIOReturnNotReady;
+ IORecursiveLockLock(mutex);
+ const auto before=Display13FBMemory::field(fb,0x49e8);
+ reinterpret_cast(callbackXE->orgDisplay13FreeResources)(raw);
+ const auto count=Display13FBMemory::field(fb,0x49e8);
+ const auto allocated=Display13FBMemory::field(fb,0x1e7);
+ const auto result=display13RetirementHealthy() && !count && !allocated ? 0U : uint32_t(kIOReturnNotReady);
+ if(result && c.lock){IOLockLock(c.lock);adlnFault(c.memoryFaulted,__LINE__);IOLockUnlock(c.lock);}
+ // The native abort epilogue preserves allocated/path count. The memory
+ // adapter also keeps a sticky fault if suffix cleanup could not finish.
+ IORecursiveLockUnlock(mutex);
+ uint32_t record[]={1,Display13FBMemory::field(fb,0x1dc),before,count,allocated,result};
+ IORegistryEntry::getRegistryRoot()->setProperty("WEXDisplay13Retirement",record,sizeof(record));
+ return result;
+}
+
+bool XE::retireDisplay13Controller(void *raw) {
+ auto s=callbackXE;
+ if(!s->display13FramebufferRequested || !Display13FBTopology::multiOutputWiring(s->adlnOutputWiring))return true;
+ auto controller=static_cast(raw);
+ if(!controller || controller!=display13FBPower.controller || controller->getMetaClass()->getClassSize()<0x1c20 ||
+ !s->orgDisplay13FreeResources || !display13RetirementHealthy())return false;
+ const auto count=Display13FBMemory::field(controller,0xd5c);if(count>3)return false;
+ for(unsigned i=0;i(controller,0xd60+8*i);
+ if(!fb)continue;
+ if(fb->getMetaClass()->getClassSize()<0x4b90 || Display13FBMemory::field(fb,0x1d0)!=raw)return false;
+ if((Display13FBMemory::field(fb,0x1e7) || Display13FBMemory::field(fb,0x49e8)) &&
+ wrapDisplay13FreeResources(fb))return false;
+ }
+ auto &c=display13FBPower;IOLockLock(c.lock);bool clear=!c.memoryFaulted;
+ for(const auto &pipe:c.pipe)clear=clear && !pipe.memoryFramebuffer && !pipe.publishedScanout;
+ for(const auto &slot:display13FBSysMem.slot)clear=clear && !slot.buffer;
+ IOLockUnlock(c.lock);return clear;
}
bool XE::processDisplay13FramebufferDBUF(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) {
@@ -5378,7 +5794,7 @@ IOReturn XE::wrapDisplay13FBEnableIRQ(IOService *controller) {
if(!callbackXE->display13FBIRQReady || !adlnIRQOwner(controller))return kIOReturnNotReady;
auto &c=display13FBIRQ;uint64_t record[32] {},check[42] {3};Display13FBIRQIO io {c.base};
const auto excluded=controller==callbackXE->adlnTopologyController ?
- Display13FBTopology::excludedTypeC(callbackXE->adlnPlatform,sizeof(callbackXE->adlnPlatform),callbackXE->adlnOutputWiring) : 0;
+ Display13FBTopology::excludedHotplug(callbackXE->adlnPlatform,sizeof(callbackXE->adlnPlatform),callbackXE->adlnOutputWiring) : 0;
const auto saved=IOSimpleLockLockDisableInterrupt(c.lock);
// Preserve enable evidence even when start rejects its unpublished
// controller. Inspect only pipe banks already qualified as powered.
@@ -5450,7 +5866,7 @@ void XE::wrapDisplay13FBConfigureVBL(IOService *controller,IOService *fb,OSObjec
if(!Display13FBTopology::multiOutputWiring(callbackXE->adlnOutputWiring) && enabled && c.state.begun && c.state.enabled && !c.state.active && !c.state.faulted &&
Display13FBIRQ::powered(io.read(Display13FBIRQ::Driver),pipe) && (io.read(0x70008+0x1000*pipe)&Display13FBIRQ::Enable)) {
const auto excluded=controller==callbackXE->adlnTopologyController ?
- Display13FBTopology::excludedTypeC(callbackXE->adlnPlatform,sizeof(callbackXE->adlnPlatform),callbackXE->adlnOutputWiring) : 0;
+ Display13FBTopology::excludedHotplug(callbackXE->adlnPlatform,sizeof(callbackXE->adlnPlatform),callbackXE->adlnOutputWiring) : 0;
c.status=Display13FBIRQ::enable(io,c.state,excluded,adlnSharedGT(controller),1,callbackXE->adlnOutputWiring.selected());
}
if(!c.state.faulted)c.status=Display13FBIRQ::vblank(io,c.state,pipe,enabled);
@@ -5777,7 +6193,7 @@ uint32_t XE::adlnRequestedSharedReservation(IOService *controller) {
const auto prefix=(uint64_t(Display13FBMemory::field(controller,0xDF8))+0xFFF)&~0xFFFULL;
return prefix<0x10000000 ? static_cast(prefix) : 0;
}
- if(!Display13FBTopology::adlnWiring(callbackXE->adlnOutputWiring))return 0;
+ if(!Display13FBTopology::xeLPDWiring(callbackXE->adlnOutputWiring))return 0;
// Allocation/free may change descriptor/state fields later. The validated
// fixed layout's registration promise is stable across those transitions.
const auto prefix=__atomic_load_n(&display13FBGTT.sharedPoolPrefix,__ATOMIC_ACQUIRE);
@@ -5791,9 +6207,14 @@ IOReturn XE::wrapDisplay13FramebufferPlatformCall(IOService *controller,const OS
controller->getMetaClass()->getClassSize()>=0x1C20 && name &&
name==Display13FBMemory::field(controller,0x1560);
if(!fixed)return FunctionCast(wrapDisplay13FramebufferPlatformCall,self->orgDisplay13FramebufferPlatformCall)(controller,name,wait,arg1,arg2,arg3,arg4);
- const auto layout=Display13FBMemory::poolLayout(controller,controller->getMetaClass()->getClassSize(),0x10000000);
+ // Disabled SysMem windows are native descriptions, not aperture allocations.
+ // A large stolen prefix can place their unused tail beyond the aperture.
+ // sharedReservation rejects every occupied secondary window and bounds the
+ // actual promised prefix to the aperture before any native registration.
+ const auto layout=Display13FBMemory::poolLayout(controller,controller->getMetaClass()->getClassSize(),
+ adlnFixedSysMemEnabled ? 0x10000000 : UINT32_MAX);
const auto plan=Display13FBMemory::sharedReservation(layout,0x10000000,adlnFixedSysMemEnabled);
- const bool ready=Display13FBTopology::adlnWiring(self->adlnOutputWiring) && plan.valid &&
+ const bool ready=Display13FBTopology::xeLPDWiring(self->adlnOutputWiring) && plan.valid &&
display13FBGTT.initialized && !display13FBGTT.faulted && !display13FBPower.memoryFaulted &&
arg1 && arg1==__atomic_load_n(&adlnSharedOwner,__ATOMIC_ACQUIRE) && arg2 && arg3;
if(!ready)return kIOReturnNotReady;
@@ -5956,7 +6377,7 @@ bool XE::processDisplay13FramebufferTopology(KernelPatcher &patcher,size_t index
bool XE::wrapAlderLakeNGetOSInformation(IOService *controller) {
auto s=callbackXE;
if(!s->display13FBTopologyHooksReady || !controller || controller->getMetaClass()->getClassSize()<0x1C20 ||
- !Display13FBTopology::supportedWiring(s->adlnOutputWiring) || !checkKernelArgument("-disablegfxfirmware"))return false;
+ !Display13FBTopology::supportedWiring(s->adlnOutputWiring,s->xeProfile.display==XELP::Display::XeLPD) || !checkKernelArgument("-disablegfxfirmware"))return false;
if(!FunctionCast(wrapAlderLakeNGetOSInformation,s->orgAlderLakeNGetOSInformation)(controller))return false;
auto platform=Display13FBMemory::field(controller,0xD40);
if(!Display13FBTopology::apply(s->adlnPlatform,sizeof(s->adlnPlatform),platform,s->adlnOutputWiring))return false;
@@ -5987,6 +6408,13 @@ uint32_t XE::wrapAlderLakeNPortState(OSObject *port) {
if(!s->display13FBTopologyHooksReady || !s->adlnTopologyController || !port || port->getMetaClass()->getClassSize()<0x580)return 0;
auto config=Display13FBMemory::field(port,0x548);
if(Display13FBTopology::selected(s->adlnPlatform,config,s->adlnOutputWiring)) {
+ // Keep controller/memory/IRQ initialization reachable while isolating
+ // HPD, EDID and mode programming during framebuffer bring-up.
+ if(display13Offline) {
+ *reinterpret_cast(reinterpret_cast(port)+0x554)=0;
+ IORegistryEntry::getRegistryRoot()->setProperty("WEXDisplay13OfflineProbe",true);
+ return 0;
+ }
// CPU binding only. TC bits are added when mode/AUX/PLL/GOP become
// qualified together; a board description alone is insufficient.
if(!s->xelpBindProbe(s->adlnTopologyController)) {
@@ -6012,6 +6440,367 @@ uint32_t XE::wrapAlderLakeNPortState(OSObject *port) {
return 0;
}
+unsigned XE::display13TCTarget(OSObject *port,bool cleanup) {
+ auto &c=display13FBPower;auto self=callbackXE;
+ if(!self->display13FBTCHooksReady || !c.tcSupported || !c.controller || !c.bridge.begun ||
+ c.tcBankFaulted || c.bridge.state.faulted || (!cleanup && c.clockFaulted) || !port || port->getMetaClass()->getClassSize()<0x6b0)return UINT32_MAX;
+ auto config=Display13FBMemory::field(port,0x548);
+ if(!config || !self->adlnOutputWiring.matches(config) || config[0]<3 || config[0]>6 ||
+ !Display13FBTopology::bound(self->adlnPlatform,config,config[0]) ||
+ Display13FBMemory::field(port,0x578)!=c.access ||
+ Display13FBMemory::field(port,0x120)!=config[0])return UINT32_MAX;
+ return config[0];
+}
+
+uint32_t XE::wrapDisplay13TCMode(OSObject *port,uint32_t wanted) {
+ auto self=callbackXE;auto &c=display13FBPower;
+ auto config=port && port->getMetaClass()->getClassSize()>=0x580 ? Display13FBMemory::field(port,0x548) : nullptr;
+ if(config && config[0]<3)return FunctionCast(wrapDisplay13TCMode,self->orgDisplay13TCMode)(port,wanted);
+ if(!c.lock)return kIOReturnNotReady;
+ IOLockLock(c.lock);const auto ddi=display13TCTarget(port,wanted==0);
+ if(ddi>=7 || (wanted!=0 && wanted!=4) || c.tcBusy[ddi-3] ||
+ (c.tcObject[ddi-3] && c.tcObject[ddi-3]!=port)) {IOLockUnlock(c.lock);return kIOReturnNotReady;}
+ const auto tc=ddi-3;auto &mode=c.tcMode[tc];
+ if(!c.tcObject[tc]){c.tcObject[tc]=port;port->retain();}
+ struct IO : Display13FBTCIO {
+ using Preparation=XELP::LinkRequirements::Preparation;
+ uint32_t (*base)(OSObject *,uint32_t);Preparation preparation;
+ IO(OSObject *p,unsigned t,uint32_t (*f)(OSObject *,uint32_t),Preparation selected):Display13FBTCIO(p,t),base(f),preparation(selected) {}
+ bool admissionReady(){return base && admitted(tc) &&
+ (preparation==Preparation::ColdTC13 || preparation==Preparation::FirmwareTC13);}
+ uint32_t nativeMode(){return Display13FBMemory::field(object,0x550);}
+ uint32_t nativeSetMode(uint32_t value) {
+ // mode0 invokes the native reset callback. Retain this exact port
+ // transaction, but drop the owner lock across possible callbacks.
+ auto &c=display13FBPower;++c.tcBusy[tc];IOLockUnlock(c.lock);
+ auto result=base(object,value);IOLockLock(c.lock);--c.tcBusy[tc];return result;
+ }
+ Display13Power::Result acquireDomains(Display13Power::Lease &lease,uint32_t domains) {
+ Display13FBPowerIO io{map};return Display13Power::acquire(io,display13FBPower.bridge.state,lease,domains,true);
+ }
+ Display13Power::Result releaseDomains(Display13Power::Lease &lease) {
+ Display13FBPowerIO io{map};return Display13Power::release(io,display13FBPower.bridge.state,lease);
+ }
+ bool powerHeld(uintptr_t controller,uintptr_t port,unsigned d) {
+ return controller==reinterpret_cast(display13FBPower.controller) &&
+ port==reinterpret_cast(object) && d==tc+3 && Display13FBTCIO::accessHeld();
+ }
+ bool inheritedVerified(uintptr_t controller,uintptr_t port,unsigned d) {
+ return preparation==Preparation::FirmwareTC13 && controller==reinterpret_cast(display13FBPower.controller) &&
+ port==reinterpret_cast(object) && d==tc+3;
+ }
+ bool quiescent(uintptr_t controller,uintptr_t port,unsigned d) {
+ auto &c=display13FBPower;
+ if(controller!=reinterpret_cast(c.controller) || port!=reinterpret_cast(object) ||
+ d!=tc+3 || c.tcBusy[tc] || c.bridge.nativeDDIRefs(d) || c.bridge.nativeAUXRefs(d) ||
+ Display13FBMemory::field(object,0x570) || !Display13FBTCIO::quiescent(d))return false;
+ Display13TC::Port p;Display13TC::Port::resolve(d,p);
+ const auto pll=read(p.pll),aux=read(0x64310+0x100*tc);
+ return pll!=UINT32_MAX && aux!=UINT32_MAX && !(pll&0xc0000000) && !(aux&0x80000000);
+ }
+ };
+ // Adopt only the validated, protected firmware boot route. Merely finding
+ // a set ownership bit on another connector is not a handoff certificate.
+ Display13FBBootIO bootIO{c.map};
+ const bool dpBoot=ddi==c.bridge.bootPort && (self->adlnOutputWiring.dp&(1U<adlnOutputWiring,bootIO.read(0x60400),true)==ddi &&
+ (bootIO.read(0x70008)&0xc0000000)==0xc0000000 && boot.unchanged(bootIO);
+ if(inherited && dpBoot) {
+ const auto source=AlderLakeN::readDisplay(bootIO,true);
+ inherited=source.activePipes==1 && AlderLakeN::dpSourceUnchanged(bootIO,source,0,ddi,0,&boot);
+ }
+ const auto preparation=framebufferLinkRequirements(port,4).preparation(inherited);
+ IO io{port,tc,reinterpret_cast(self->display13TCBaseMode),preparation};
+ const auto owner=reinterpret_cast(c.controller),object=reinterpret_cast(port);
+ const auto result=wanted?mode.connect(io,owner,object,ddi):mode.disconnect(io,owner,object,ddi);
+ const bool done=result.status==Display13TC::Status::Complete;
+ if(!wanted && done){c.tcSignalReady[tc]=false;c.tcDPLink[tc]={};OSSafeReleaseNULL(c.tcObject[tc]);}
+ uint32_t record[]={1,ddi,wanted,uint32_t(result.status),uint32_t(result.phy.status),uint32_t(result.power.status),uint32_t(mode.phase),mode.disconnectPending};
+ IOLockUnlock(c.lock);auto root=IORegistryEntry::getRegistryRoot();
+ root->setProperty("WEXDisplay13TCMode",record,sizeof(record));
+ // Keep the initiating power-domain result; subsequent native getPG errors
+ // can only observe the shared fault and cannot reconstruct this boundary.
+ if(result.status==Display13TC::Status::NoPower) {
+ uint32_t power[]={1,ddi,wanted,uint32_t(result.power.status),result.power.domain,
+ result.power.writes,result.power.rollbackFailed,result.power.forcedOn,result.power.isolated};
+ if(!root->getProperty("WEXDisplay13TCFirstPowerError"))root->setProperty("WEXDisplay13TCFirstPowerError",power,sizeof(power));
+ }
+ if(!done && !root->getProperty("WEXDisplay13TCModeFailure")) {
+ root->setProperty("WEXDisplay13TCModeFailure",record,sizeof(record));
+ uint32_t detail[] {1,ddi,result.phy.before,result.phy.after,result.phy.writes,result.phy.elapsedUs};
+ root->setProperty("WEXDisplay13TCModeFailureState",detail,sizeof(detail));
+ }
+ return done ? kIOReturnSuccess : kIOReturnNotReady;
+}
+
+void XE::wrapDisplay13TCEnableClocks(void *controller,void *raw,void *other,uint32_t *params) {
+ auto self=callbackXE;auto port=static_cast(raw);auto &c=display13FBPower;
+ auto config=port && port->getMetaClass()->getClassSize()>=0x580 ? Display13FBMemory::field(port,0x548):nullptr;
+ if(config && config[0]<3) {
+ bool keep=false;
+ if(!comboClockTarget(controller,raw,other) || !comboBeginClock(controller,raw,true,keep,other)) {
+ adlnFault(c.clockFaulted,__LINE__);return;
+ }
+ FunctionCast(wrapDisplay13TCEnableClocks,self->orgDisplay13TCEnableClocks)(controller,raw,other,params);
+ comboEndClock();return;
+ }
+ if(!c.lock)return;IOLockLock(c.lock);const auto ddi=display13TCTarget(port);
+ bool ok=false;uint32_t rate=0;Display13Power::Result power;
+ auto stage=Display13TC::Clock::LinkStage::Preflight;
+ if(controller==c.controller && (!other || other==raw) && ddi<7 && !c.tcBusy[ddi-3]) {
+ const auto tc=ddi-3;Display13FBTCIO io{port,tc};Display13TC::Port p;Display13TC::Port::resolve(ddi,p);
+ if(self->adlnOutputWiring.dp&(1U<(port,0x128);
+ const auto lanes=Display13FBMemory::field(port,0x124);
+ const auto plan=Display13TC::DPClock::calculate(linkRate,c.clock.refKHz);rate=plan.rate;
+ ok=c.tcSignalReady[tc] && c.tcDPLink[tc].matches(io,p,plan,lanes);
+ if(ok)stage=Display13TC::Clock::LinkStage::Complete;
+ else {
+ ok=Display13TC::DPSignal::startLink(io,p,plan,lanes,&stage);
+ if(ok)c.tcDPLink[tc]={plan.rate,plan.reference,lanes};
+ }
+ } else {
+ rate=Display13TC::TMDSClock::nativeRate(params,c.clock.refIndex);
+ const auto plan=Display13TC::TMDSClock::calculate(rate,c.clock.refKHz);
+ ok=Display13TC::Clock::startLink(io,p,plan,self->firmwarePorts.port[ddi].hdmiPreset,&stage);
+ }
+ power=io.linkResult;
+ c.tcSignalReady[tc]=ok;
+ }
+ if(!ok)adlnFault(c.clockFaulted,__LINE__);
+ uint32_t record[]={2,ddi,rate,ok,uint32_t(stage),uint32_t(power.status),power.domain,power.writes,power.rollbackFailed};IOLockUnlock(c.lock);
+ IORegistryEntry::getRegistryRoot()->setProperty("WEXDisplay13TCClockEnable",record,sizeof(record));
+}
+void XE::wrapDisplay13TCDisableClocks(void *controller,void *raw,void *other) {
+ auto self=callbackXE;auto port=static_cast(raw);auto &c=display13FBPower;
+ auto config=port && port->getMetaClass()->getClassSize()>=0x580 ? Display13FBMemory::field(port,0x548):nullptr;
+ if(config && config[0]<3) {
+ bool keep=false;
+ if(!comboClockTarget(controller,raw,other) || !comboBeginClock(controller,raw,false,keep)) {
+ adlnFault(c.clockFaulted,__LINE__);return;
+ }
+ FunctionCast(wrapDisplay13TCDisableClocks,self->orgDisplay13TCDisableClocks)(controller,raw,keep?raw:nullptr);
+ comboEndClock();return;
+ }
+ // Native AGDC End ignores the port-disable return. Independently require
+ // transcoder stop before dropping DDI/PLL, including retry and abort paths.
+ if(config && config[0]<7 && (self->adlnOutputWiring.dp&(1U<display13ClockOrderReady &&
+ !display13StopTranscoder(raw)) {
+ IORegistryEntry::getRegistryRoot()->setProperty("WEXDisplay13ClockStopRejected",true);return;
+ }
+ if(!c.lock)return;IOLockLock(c.lock);const auto ddi=display13TCTarget(port,true);bool ok=false;
+ if(controller==c.controller && (!other || other==raw) && ddi<7 && !c.tcBusy[ddi-3]) {
+ Display13FBTCIO io{port,ddi-3};Display13TC::Port p;Display13TC::Port::resolve(ddi,p);
+ ok=Display13TC::Clock::stopLink(io,p);
+ if(ok){c.tcSignalReady[ddi-3]=false;c.tcDPLink[ddi-3]={};}
+ }
+ if(!ok)adlnFault(c.clockFaulted,__LINE__);
+ uint32_t record[]={1,ddi,ok};IOLockUnlock(c.lock);
+ IORegistryEntry::getRegistryRoot()->setProperty("WEXDisplay13TCClockDisable",record,sizeof(record));
+}
+uint32_t XE::wrapDisplay13TCSignal(void *raw,uint32_t swing,uint32_t emphasis,void *drive) {
+ auto self=callbackXE;auto port=static_cast(raw);auto &c=display13FBPower;
+ auto config=port && port->getMetaClass()->getClassSize()>=0x580 ? Display13FBMemory::field(port,0x548):nullptr;
+ if(config && config[0]<3)return FunctionCast(wrapDisplay13TCSignal,self->orgDisplay13SignalLevels)(raw,swing,emphasis,drive);
+ if(!c.lock)return kIOReturnNotReady;IOLockLock(c.lock);const auto ddi=display13TCTarget(port);
+ // TMDS signal parameters were programmed while the path was stopped,
+ // before native enablePipe. Do not run TGL DP's late voltage sequence.
+ bool ok=false;if(ddi<7){
+ Display13FBTCIO io{port,ddi-3};ok=io.owned(ddi) && io.linkHeld() && c.tcSignalReady[ddi-3];
+ if(ok && (self->adlnOutputWiring.dp&(1U<(port,0x128),swing,emphasis);
+ }
+ }
+ if(!ok)adlnFault(c.clockFaulted,__LINE__);IOLockUnlock(c.lock);
+ return ok ? 0 : kIOReturnNotReady;
+}
+
+uint32_t XE::wrapDisplay13DPLightUp(void *controller,void *fb,void *path,const void *timing) {
+ auto object=static_cast(path);
+ if(!object || object->getMetaClass()->getClassSize()<0x3730)return kIOReturnNotReady;
+ auto port=Display13FBMemory::field(path,0x3648);
+ if(port && port->getMetaClass()->getClassSize()>=0x580 && Display13FBMemory::field(port,0x120)>=3)
+ return wrapDisplay13TCLightUp(controller,fb,path,timing);
+ return FunctionCast(wrapDisplay13DPLightUp,callbackXE->orgDisplay13DPLightUp)(controller,fb,path,timing);
+}
+uint32_t XE::wrapDisplay13DPLaneMask(void *port) {
+ auto s=callbackXE;auto object=static_cast(port);
+ auto config=object && object->getMetaClass()->getClassSize()>=0x580?Display13FBMemory::field(port,0x548):nullptr;
+ if(!config || config[0]>=7 || !(s->adlnOutputWiring.dp&(1U<orgDisplay13DPLaneMask)(port);
+ auto &c=display13FBPower;if(!c.lock)return 0;IOLockLock(c.lock);
+ const auto ddi=display13TCTarget(object);bool ok=false;
+ if(ddi>=3 && ddi<=6){Display13FBTCIO io{object,ddi-3};ok=io.owned(ddi);}
+ IOLockUnlock(c.lock);return ok?15U:0;
+}
+void XE::wrapDisplay13DPPhyGating(void *port,bool gate) {
+ auto s=callbackXE;auto object=static_cast(port);
+ auto config=object && object->getMetaClass()->getClassSize()>=0x580?Display13FBMemory::field(port,0x548):nullptr;
+ if(!config || config[0]>=7 || !(s->adlnOutputWiring.dp&(1U<orgDisplay13DPPhyGating)(port,gate);return;
+ }
+ auto &c=display13FBPower;if(!c.lock)return;IOLockLock(c.lock);
+ const auto ddi=display13TCTarget(object);bool ok=false;
+ if(ddi>=3 && ddi<=6) {
+ Display13FBTCIO io{object,ddi-3};Display13TC::Port p;Display13TC::Port::resolve(ddi,p);
+ ok=io.owned(ddi) && io.linkHeld();
+ // Same fixed-DP contract as TGL: Linux leaves DP_MODE bits5:1
+ // intact during training. Do not run the donor's clear operation.
+ if(ok && gate)for(unsigned group=0;group<2;++group) {
+ uint32_t value=0x3e;ok=Display13TC::PHY::access(io,p,0x1000*group+0xa0,0x3e,value,true) && ok;
+ }
+ }
+ if(!ok)adlnFault(c.clockFaulted,__LINE__);IOLockUnlock(c.lock);
+}
+void XE::wrapDisplay13DPPortWrite(void *port,uint32_t reg,uint32_t value) {
+ auto s=callbackXE;
+ if(reinterpret_cast(__builtin_return_address(0))==s->display13DPImage+0x5fba0) {
+ auto object=static_cast(port);auto &c=display13FBPower;
+ if(c.lock && object && object->getMetaClass()->getClassSize()>=0x580) {
+ IOLockLock(c.lock);const auto ddi=display13TCTarget(object);
+ if(ddi>=3 && ddi<=6 && (s->adlnOutputWiring.dp&(1U<(port,0x128);
+ if(io.owned(ddi) && io.linkHeld() && (rate==6 || rate==10 || rate==20 || rate==30))
+ value=(value&~0xff00U)|(((rate*27+9)/10)<<8);
+ else {adlnFault(c.clockFaulted,__LINE__);IOLockUnlock(c.lock);return;}
+ }
+ IOLockUnlock(c.lock);
+ }
+ }
+ FunctionCast(wrapDisplay13DPPortWrite,s->orgDisplay13DPPortWrite)(port,reg,value);
+}
+
+uint32_t XE::wrapDisplay13TCLightUp(void *controller,void *rawFB,void *rawPath,const void *timing) {
+ auto self=callbackXE;auto &c=display13FBPower;
+ auto fb=static_cast(rawFB);auto path=static_cast(rawPath);
+ if(!c.lock || !path || path->getMetaClass()->getClassSize()<0x3730)return kIOReturnNotReady;
+ auto port=Display13FBMemory::field(path,0x3648);
+ if(port && port->getMetaClass()->getClassSize()>=0x580 && Display13FBMemory::field(port,0x120)<3)
+ return FunctionCast(wrapDisplay13TCLightUp,self->orgDisplay13TCLightUp)(controller,rawFB,rawPath,timing);
+ struct IO {
+ XE *self;void *controller;IOService *fb;OSObject *path,*object;const void *timing;
+ unsigned ddi {UINT32_MAX},pipeIndex {UINT32_MAX};
+ bool dp {};
+ bool ready(bool clock) {
+ auto &c=display13FBPower;IOLockLock(c.lock);
+ const auto current=display13TCTarget(object);
+ const auto binding=adlnInspectPathBinding(fb,path,self->adlnPlatform,self->adlnOutputWiring,false);
+ auto scaler=Display13FBMemory::field(path,0x32C0);
+ Display13FBPowerIO power{c.map};
+ bool ok=controller==c.controller && timing && current<7 && current==ddi &&
+ binding.status==TGLFBBinding::Status::Complete && binding.ddi==ddi && binding.pipe==pipeIndex &&
+ (!dp || !Display13FBMemory::field(object,0x117)) &&
+ scaler && scaler->getMetaClass()->getClassSize()>=0x58 &&
+ Display13FBMemory::field(scaler,0x10)==fb && Display13FBMemory::field(scaler,0x28)==c.access &&
+ !c.memoryFaulted && !c.watermarkFaulted && !c.clockStopped && !c.tcBusy[ddi-3] &&
+ (clock ? c.bridge.outputHeld(power,ddi,pipeIndex) : c.bridge.pipeHeld(power,pipeIndex));
+ if(ok){Display13FBTCIO tc{object,ddi-3};ok=tc.owned(ddi) && (!clock || c.tcSignalReady[ddi-3]);}
+ IOLockUnlock(c.lock);return ok;
+ }
+ void clocks() {
+ auto &c=display13FBPower;IOLockLock(c.lock);if(!dp)c.tcSignalReady[ddi-3]=false;IOLockUnlock(c.lock);
+ reinterpret_cast(self->display13TCFullClocks)(controller,fb,path);
+ }
+ bool transClock(uint32_t reg,uint32_t value) {
+ auto &c=display13FBPower;IOLockLock(c.lock);Display13FBTCIO tc{object,ddi-3};
+ const bool held=tc.owned(ddi) && c.tcSignalReady[ddi-3];
+ bool ok=false;if(held){tc.write(reg,value);ok=tc.read(reg)==value;}
+ if(!ok)adlnFault(c.clockFaulted,__LINE__);IOLockUnlock(c.lock);return ok;
+ }
+ void plane(){reinterpret_cast(self->display13TCPlane)(Display13FBMemory::field(path,0x32C8),false);}
+ void scaler(){reinterpret_cast(self->display13TCScaler)(Display13FBMemory::field(path,0x32C0),path);}
+ void pipe(){reinterpret_cast(self->display13TCPipe)(controller,fb,path,timing);}
+ void signal(){wrapDisplay13TCSignal(object,0,0,nullptr);}
+ uint32_t port(){return wrapDisplay13EnablePort(object);}
+ uint32_t train(){
+ const auto r=wrapDisplay13PortTraining(object,nullptr);
+ *(reinterpret_cast(fb)+0x4b84)=r!=0;return r;
+ }
+ void packets(){
+ if(dp)reinterpret_cast