Files
UniXNU/scripts/add-gpu-plugin-data.py
rpone ab11a8cb68 feat(scripts): iPadOS 27 kernel, driver, GPU addon and seed build steps
Source-driver and source-storage builds, the arm64e PAC rewrite for the Rust bridge, the IOAccelerator base ABI of 24A446, the bridge with mimalloc and link-time optimisation, the plugin with the prime list, addon options for a prime list, a pipeline pack and cache export, wallpaper overlay and seed changes, and payload, kernel and userdata steps for the new profile.
2026-10-28 09:20:55 +08:00

110 lines
5.8 KiB
Python

#!/usr/bin/env python3
"""Stage the owner-built Adreno Metal plugin into a copy of a packed Data seed.
add-gpu-plugin-data.py [--publish] BASE.dbr OUTPUT.dbr
With --publish the seed also carries /private/var/ngpu/addons/NativeAdrenoMetal.publish: the plugin then returns the native Metal device to the original
discovery in backboardd (NativeAdrenoMetalPlugin.mm), so the render server composites through it. Without the file the plugin only runs its tests.
Adds /private/var/ngpu/addons/NativeAdrenoMetal.bundle, /private/var/ngpu/bin/adrenoprobed and /private/var/ngpu/daemons/*.plist (from build/adreno-metal-plugin, see build-adreno-metal-plugin.py). The
kernel's boot-time addon mount (sheng-gpu-addon=1) presents that directory as a read-only union view over
/System/Library/Extensions, which is where the original Metal discovery looks for MetalPluginName. BASE is not modified and the
volume UUID is unchanged. Nothing is written to Git or to the OS inputs."""
import json
import os
import plistlib
import shutil
import subprocess
import sys
import tempfile
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / 'drivers/DarwinBoot/Test'))
import packed_ramdisk
BUNDLE = ROOT / 'build/adreno-metal-plugin/NativeAdrenoMetal.bundle'
def main():
args = [a for a in sys.argv[1:] if a != '--publish']
publish = len(args) != len(sys.argv) - 1
if len(args) != 2:
sys.exit(__doc__)
base, output = Path(args[0]).resolve(), Path(args[1]).resolve()
if output.exists():
sys.exit('Refuse to overwrite ' + str(output))
if not (BUNDLE / 'NativeAdrenoMetal').is_file():
sys.exit('Build the plugin first: python3 scripts/build-adreno-metal-plugin.py')
blob = base.read_bytes()
logical, extents = packed_ramdisk.regions(blob)
with tempfile.TemporaryDirectory(prefix='data-seed-', dir=str(ROOT / 'build')) as td:
raw = Path(td) / 'seed.raw'
with raw.open('wb') as f:
f.truncate(logical)
for offset, data in extents:
f.seek(offset)
f.write(data)
mount = Path(td) / 'mount'
mount.mkdir()
attached = plistlib.loads(subprocess.check_output(
['hdiutil', 'attach', '-imagekey', 'diskimage-class=CRawDiskImage', '-nobrowse', '-owners', 'off',
'-mountpoint', str(mount), '-plist', str(raw)]))
device = attached['system-entities'][0]['dev-entry']
try:
if not (mount / 'mobile').is_dir():
sys.exit('The Data seed has no /mobile; wrong base image?')
target = mount / 'ngpu/addons/NativeAdrenoMetal.bundle'
if target.exists():
shutil.rmtree(target)
target.parent.mkdir(parents=True)
for directory in (mount / 'ngpu', mount / 'ngpu/addons'):
os.chmod(directory, 0o755)
shutil.copytree(BUNDLE, target, symlinks=False)
marker = mount / 'ngpu/addons/NativeAdrenoMetal.publish'
if publish:
marker.write_text('1\n')
os.chmod(marker, 0o644)
daemon_dir, bin_dir = mount / 'ngpu/daemons', mount / 'ngpu/bin'
for directory in (daemon_dir, bin_dir):
if directory.exists():
shutil.rmtree(directory)
directory.mkdir(parents=True)
os.chmod(directory, 0o755)
shutil.copyfile(BUNDLE.parent / 'adrenoprobed', bin_dir / 'adrenoprobed')
os.chmod(bin_dir / 'adrenoprobed', 0o755)
for plist in sorted(BUNDLE.parent.glob('*.plist')):
if plist.name.endswith('-entitlements.plist'):
continue # build input, not a launchd job
shutil.copyfile(plist, daemon_dir / plist.name)
os.chmod(daemon_dir / plist.name, 0o644)
if publish:
# The render server (backboardd) picks a 10-bit "deep" framebuffer unless told not to; the preference is read when the process
# starts, before any plugin code can run, so the seed carries it for the process's user (mobile) and for root.
# Both the process's own domain and the global one (the lookup falls back to it), in case the process name is not the domain.
for home, name in [(h, n) for h in ('mobile', 'root') for n in ('backboardd.plist', '.GlobalPreferences.plist')]:
directory = mount / home / 'Library/Preferences'
directory.mkdir(parents=True, exist_ok=True)
file = directory / name
values = plistlib.loads(file.read_bytes()) if file.exists() else {}
values.update({'CAEnableDeepFramebuffer': False, 'CAEnableFramebufferCompression': False,
'CAEnableIOSurfaceCompression': False})
file.write_bytes(plistlib.dumps(values, fmt=plistlib.FMT_BINARY))
os.chmod(file, 0o644)
for path in [target, *target.rglob('*')]:
os.chmod(path, 0o755 if path.is_dir() or path.name in ('NativeAdrenoMetal', 'libvulkan_freedreno.dylib') else 0o644)
print('staged', target.relative_to(mount), sorted(p.name for p in target.iterdir()))
finally:
subprocess.run(['hdiutil', 'detach', device], check=True)
if raw.read_bytes()[32:36] != b'NXSB':
sys.exit('The edited image is no APFS container any more')
metadata = packed_ramdisk.pack(raw, output)
report = dict(base_sha256=packed_ramdisk.inspect(base)[0]['sha256'], output=metadata, publish=publish,
plugin=json.loads((ROOT / 'build/adreno-metal-plugin/trust.json').read_text()))
output.with_suffix('.json').write_text(json.dumps(report, indent=2) + '\n')
print(json.dumps(report, indent=2))
if __name__ == '__main__':
main()