Files
UniXNU/scripts/check-smmu4k-streams.py
2026-10-19 18:49:31 +08:00

90 lines
6.2 KiB
Python

#!/usr/bin/env python3
"""Native SMMUv2 exact stream binding; original-source oracle and software MMIO."""
import hashlib,json,re,struct,subprocess
from pathlib import Path
ROOT=Path(__file__).resolve().parents[1];kit=ROOT/'kits/ArmSmmu4K';out=ROOT/'build/arm-smmu-4k-streams'
out.mkdir(exist_ok=True);(out/'report.json').unlink(missing_ok=True)
pins=json.loads((ROOT/'scripts/data/adreno-inputs.json').read_text());cache=ROOT/'.cache/adreno-source'
def sha(p):return hashlib.sha256(p.read_bytes()).hexdigest()
names=['drivers/iommu/arm/arm-smmu/arm-smmu.h','drivers/iommu/arm/arm-smmu/arm-smmu.c']
for name in names:
if sha(cache/name)!=pins['linux']['files'][name]['sha256']:raise SystemExit('Pinned source differs '+name)
h=(cache/names[0]).read_text();c=(cache/names[1]).read_text();defs={}
for match in re.finditer(r'^#define\s+(\w+)(\([^\n]*?\))?\s+((?:[^\n]*\\\n)*[^\n]*)',h,re.M):defs[match[1]]=match[0]
selected={}
def include(name):
if name in selected:return
selected[name]=defs[name]
for dep in set(re.findall(r'ARM_SMMU_[A-Za-z0-9_]+',defs[name]))-{name}:include(dep)
registers=['ARM_SMMU_GR0_sCR0','ARM_SMMU_GR0_ID0','ARM_SMMU_GR0_sGFSR','ARM_SMMU_GR0_SMR(2)','ARM_SMMU_GR0_S2CR(2)',
'ARM_SMMU_SMR_VALID','ARM_SMMU_S2CR_EXIDVALID','ARM_SMMU_sCR0_USFCFG']
for key in registers+['ARM_SMMU_SMR_ID','ARM_SMMU_SMR_MASK','ARM_SMMU_S2CR_TYPE','ARM_SMMU_S2CR_PRIVCFG','ARM_SMMU_S2CR_CBNDX','ARM_SMMU_FEAT_EXIDS']:include(key.split('(')[0])
def block(source,signature):
start=source.index(signature);at=source.index('{',start)+1;depth=1
while depth:
if source[at]=='{':depth+=1
elif source[at]=='}':depth-=1
at+=1
return source[start:at]
prelude='''#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
typedef uint8_t u8;typedef uint16_t u16;typedef uint32_t u32;
#define BIT(n) (UINT32_C(1)<<(n))
#define GENMASK(h,l) ((UINT32_MAX>>(31-(h))) & (UINT32_MAX<<(l)))
#define FIELD_PREP(mask,value) (((u32)(value)<<__builtin_ctz(mask))&(mask))
'''+ '\n'.join(selected.values())+'\n'
for signature in ['enum arm_smmu_s2cr_type {','enum arm_smmu_s2cr_privcfg {','struct arm_smmu_smr {','struct arm_smmu_s2cr {']:
prelude+=block(h,signature)+';\n'
prelude+='''struct arm_smmu_device;
struct arm_smmu_impl {void (*write_s2cr)(struct arm_smmu_device *,int);};
struct arm_smmu_device {struct arm_smmu_smr *smrs;struct arm_smmu_s2cr *s2crs;unsigned features;const struct arm_smmu_impl *impl;};
static u32 sink_smr,sink_s2cr;
static void arm_smmu_gr0_write(struct arm_smmu_device *s,unsigned reg,u32 value) {
(void)s;if(reg==ARM_SMMU_GR0_SMR(0))sink_smr=value;else if(reg==ARM_SMMU_GR0_S2CR(0))sink_s2cr=value;
}
'''
oracle=out/'original-streams.c';oracle.write_text(prelude+block(c,'static void arm_smmu_write_smr(')+'\n'+block(c,'static void arm_smmu_write_s2cr(')+'''
void source_stream(unsigned exid,unsigned sid,unsigned mask,unsigned valid,unsigned bank,u32 *smr,u32 *s2cr) {
struct arm_smmu_smr m={.mask=mask,.id=sid,.valid=valid};
struct arm_smmu_s2cr r={.type=S2CR_TYPE_TRANS,.privcfg=S2CR_PRIVCFG_DEFAULT,.cbndx=bank};
struct arm_smmu_device s={&m,&r,exid?ARM_SMMU_FEAT_EXIDS:0,NULL};
arm_smmu_write_s2cr(&s,0);arm_smmu_write_smr(&s,0);*smr=sink_smr;*s2cr=sink_s2cr;
}
uint32_t source_stream_register(unsigned i){const uint32_t r[]={'''+','.join(registers)+'''};return r[i];}
''')
sources=[kit/(n+'.c') for n in ['ArmSmmu4K','ArmSmmu4KContext','ArmSmmu4KProgram','ArmSmmu4KStreams']]
test=out/'test-streams';subprocess.run(['xcrun','clang','-std=c11','-O2','-Wall','-Wextra','-Werror','-fsanitize=address,undefined',
'-DSOURCE_ORACLE',*sources,kit/'test_streams.c',oracle,'-o',test],check=True,timeout=60)
r=subprocess.run([test],check=True,capture_output=True,text=True,timeout=30);print(r.stdout,end='');(out/'host.log').write_text(r.stdout)
flags=['-target','arm64-apple-ios17.0','-O2','-Wall','-Wextra','-Werror','-Wno-incompatible-sysroot','-ffreestanding','-mgeneral-regs-only']
objdump=Path(subprocess.check_output(['brew','--prefix','llvm'],text=True).strip())/'bin/llvm-objdump'
objects=[];provided=set();imports=set()
for source in sources:
obj=out/(source.stem+'.o');subprocess.run(['xcrun','clang',*flags,'-std=c11','-c',source,'-o',obj],check=True,timeout=30)
raw=obj.read_bytes()
if struct.unpack_from('<4I',raw)!=(0xfeedfacf,0x100000c,0,1):raise SystemExit('Ordinary ARM64 required')
at=32;ios17=False
for _ in range(struct.unpack_from('<I',raw,16)[0]):
cmd,size=struct.unpack_from('<II',raw,at)
if cmd==0x32:ios17=struct.unpack_from('<II',raw,at+8)==(2,0x110000)
at+=size
if not ios17:raise SystemExit('iOS17 ABI required')
imports.update(subprocess.check_output(['xcrun','nm','-u',obj],text=True).split())
provided.update(re.findall(r'^[0-9a-f]+ [A-Z] (\S+)$',subprocess.check_output(['xcrun','nm','-gU',obj],text=True),re.M))
asm=subprocess.check_output([objdump,'-d',obj],text=True)
operands='\n'.join(re.findall(r'^[ \t]*[0-9a-f]+:[ \t]+[0-9a-f]{8}[ \t]+\w+[ \t]+([^\n]+)',asm,re.M))
if re.search(r'\b(?:[qds][0-9]+|v[0-9]+(?:\.[0-9a-z]+)?)\b',operands):raise SystemExit('SIMD found')
objects.append(obj)
imports-=provided
if imports-{'___stack_chk_fail','___stack_chk_guard','_memset'}:raise SystemExit('Unexpected imports '+repr(imports))
analysis=subprocess.run(['xcrun','clang','--analyze','-std=c11',*sources,'-Xanalyzer','-analyzer-output=text'],capture_output=True,text=True,timeout=60)
if analysis.returncode or analysis.stderr:raise SystemExit('Analyzer diagnostics: '+analysis.stderr)
archive=out/'libArmSmmu4KStreams.a';archive.unlink(missing_ok=True);subprocess.run(['xcrun','ar','rcs',archive,*objects],check=True,timeout=30)
inputs=sources+[kit/(s.stem+'.h') for s in sources]+[kit/'test_streams.c',Path(__file__)]
report=dict(passed=True,linux_revision=pins['linux']['commit'],pinned_sources={n:sha(cache/n) for n in names},
sources={str(p.relative_to(ROOT)):sha(p) for p in inputs},oracle_sha256=sha(oracle),objects={p.name:sha(p) for p in objects},
archive_sha256=sha(archive),native_imports=sorted(imports),host_asan_ubsan=True,ordinary_arm64_ios17=True,actual_instructions_without_simd=True,
static_analysis_clean=True,software_peer_only=True,active_hardware_mapping_qualified=False,hardware_ready=False)
(out/'report.json').write_text(json.dumps(report,indent=2)+'\n');print('PASS: original stream writer/register oracle and native archive; no hardware qualification')