diff --git a/.github/workflows/host-tests.yml b/.github/workflows/host-tests.yml new file mode 100644 index 0000000000000..f16fb33953f7c --- /dev/null +++ b/.github/workflows/host-tests.yml @@ -0,0 +1,92 @@ +name: Host tests + +# Every host-side test of the Aurora drivers: tools/asahi (with their mutation +# and old-code controls) and the apple-dcp, apple-afkv2, asahi-g15 and asahi-m4 +# selftests. They extract production code from this tree and build it on the +# host, so a driver change can break them while the kernel build stays green. + +on: + push: + branches: [integration/aurora, aurora-wip] + pull_request: + branches: [integration/aurora, aurora-wip] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: host-tests-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +env: + RUST_VERSION: '1.96.0' + CLANG_VERSION: '18' + GCC_VERSION: '13' + +jobs: + host-tests: + runs-on: ubuntu-24.04 + timeout-minutes: 20 + steps: + # Negative controls read older revisions with `git show`, so keep the + # whole history. Trees and blobs of old commits load on demand. + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + filter: tree:0 + + - uses: actions/setup-python@v5 + with: + python-version: '3.14' + + - name: Cache the Rust toolchain + uses: actions/cache@v4 + with: + path: ~/.rustup/toolchains/${{ env.RUST_VERSION }}-x86_64-unknown-linux-gnu + key: rustup-${{ runner.os }}-${{ env.RUST_VERSION }} + + - name: Pin the host toolchains + run: | + rustup toolchain install "$RUST_VERSION" --profile minimal --no-self-update + echo "RUSTUP_TOOLCHAIN=$RUST_VERSION" >> "$GITHUB_ENV" + # Tests that would pick their own rustup toolchain use this one. + echo "HOSTRUSTC=rustc" >> "$GITHUB_ENV" + need= + for p in clang-$CLANG_VERSION gcc-$GCC_VERSION flex bison bc; do + command -v "$p" >/dev/null || need="$need $p" + done + command -v "ld.lld-$CLANG_VERSION" >/dev/null || need="$need lld-$CLANG_VERSION" + dpkg -s "libclang-rt-$CLANG_VERSION-dev" >/dev/null 2>&1 || need="$need libclang-rt-$CLANG_VERSION-dev" + if [ -n "$need" ]; then + sudo apt-get update -q + sudo apt-get install -y -q $need + fi + # The tests call clang, ld.lld, cc and gcc by name. + bin="$RUNNER_TEMP/toolchain" + mkdir -p "$bin" + ln -sf "$(command -v clang-$CLANG_VERSION)" "$bin/clang" + ln -sf "$(command -v ld.lld-$CLANG_VERSION)" "$bin/ld.lld" + ln -sf "$(command -v gcc-$GCC_VERSION)" "$bin/gcc" + ln -sf "$(command -v gcc-$GCC_VERSION)" "$bin/cc" + echo "$bin" >> "$GITHUB_PATH" + + - name: Show the toolchains + run: | + python3 --version + rustc --version + clang --version | head -1 + ld.lld --version + cc --version | head -1 + git --version + + - name: Run the host tests + run: python3 -I tools/asahi/run-host-tests.py --jobs "$(nproc)" --work "$RUNNER_TEMP/host-tests" + + - name: Keep the logs of a failed run + if: failure() + uses: actions/upload-artifact@v4 + with: + name: host-test-logs + path: ${{ runner.temp }}/host-tests/**/log + retention-days: 14 diff --git a/tools/asahi/avd-av1-tail-tests.py b/tools/asahi/avd-av1-tail-tests.py index 8a36298013af6..d602801b7c0a7 100644 --- a/tools/asahi/avd-av1-tail-tests.py +++ b/tools/asahi/avd-av1-tail-tests.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # SPDX-License-Identifier: GPL-2.0-only OR MIT -"""AV1 auxiliary alignment, probability inheritance and reservation controls.""" +"""AV1 auxiliary alignment, probability and segmentation map inheritance, and reservation controls.""" import argparse import hashlib import json @@ -44,20 +44,22 @@ def structure(source, name): #include #include "controls.h" typedef uint8_t u8; +typedef int8_t s8; typedef uint16_t u16; typedef uint32_t u32; typedef uint64_t dma_addr_t; #define ALIGN(x,a) (((x)+(a)-1)&~((__typeof__(x))(a)-1)) #define ALIGN_DOWN(x,a) ((x)&~((__typeof__(x))(a)-1)) #define WARN_ON(x) (x) +#define DIV_ROUND_UP(n,d) (((n)+(d)-1)/(d)) @ALIGNMENT@ @CDF_HEADER@ @CDF_DEFAULTS@ -struct vb2_buffer {struct {unsigned int length;} planes[1]; unsigned char *memory;}; +struct vb2_buffer {struct {unsigned int length;} planes[1]; unsigned char *memory; dma_addr_t dma;}; struct vb2_v4l2_buffer {struct vb2_buffer vb2_buf;}; struct avd_decoded_buffer { struct {struct vb2_v4l2_buffer vb;} base; - struct {unsigned int color_size;} av1; + struct {unsigned int color_size; unsigned short width,height; size_t seg_size;} av1; }; struct avd_buf {void *cpu; dma_addr_t addr;}; struct avd_run {struct {struct vb2_v4l2_buffer *dst;} bufs; dma_addr_t y_out;}; @@ -65,7 +67,7 @@ def structure(source, name): @CTX_STRUCT@ struct v4l2_ctrl {struct {void *p;} p_cur;}; struct v4l2_pix_format_mplane {unsigned int width,height; struct {unsigned int sizeimage;} plane_fmt[1];}; -struct avd_ctx {void *priv; int ctrl_hdl;}; +struct avd_ctx {void *priv; int ctrl_hdl; struct {struct {struct {unsigned int width,height;} pix_mp;} fmt;} decoded_fmt;}; static struct avd_decoded_buffer *reference; static struct v4l2_ctrl_av1_frame frame; static struct v4l2_ctrl_av1_sequence sequence; @@ -74,6 +76,7 @@ def structure(source, name): static struct v4l2_ctrl controls[4]; static unsigned int lookup_calls, preamble_calls; static void *vb2_plane_vaddr(struct vb2_buffer *b,unsigned int p){assert(p==0);return b->memory;} +static dma_addr_t vb2_dma_contig_plane_dma_addr(struct vb2_buffer *b,unsigned int p){assert(p==0);return b->dma;} static struct avd_decoded_buffer *vb2_to_avd_decoded_buf(struct vb2_buffer *b){return (void *)b;} static struct avd_decoded_buffer *avd_get_ref_buf(struct avd_ctx *ctx,struct vb2_v4l2_buffer *dst,uint64_t timestamp){(void)ctx;(void)dst;assert(timestamp==1000);lookup_calls++;return reference;} static void avd_run_preamble(struct avd_ctx *ctx,struct avd_run *run){(void)ctx;(void)run;preamble_calls++;} @@ -89,11 +92,14 @@ def structure(source, name): TESTS = r''' static size_t aligned_cdf(size_t length,size_t color){return (length&~255UL)-((color+255)&~255UL)-9216;} +/* 3 bits per 4x4 block over 128x128 superblocks, 256-byte aligned. */ +static size_t seg_bytes(size_t w,size_t h){return ALIGN(ALIGN(w,128)*ALIGN(h,128)/128*3,256);} +static void set_frame_ctx(struct avd_ctx *ctx){ctx->decoded_fmt.fmt.pix_mp.width=1920;ctx->decoded_fmt.fmt.pix_mp.height=1080;controls[0].p_cur.p=&sequence;controls[1].p_cur.p=&frame;controls[2].p_cur.p=&tile;controls[3].p_cur.p=&grain;} static void read_exact(const char *path,void *data,size_t n){FILE *f=fopen(path,"rb");assert(f);assert(fread(data,1,n,f)==n);assert(fgetc(f)==EOF);assert(fclose(f)==0);} static void inheritance(unsigned int residue,unsigned int mode,const char *neighbors,bool check_addresses){ const size_t ref_len=6964992+residue,dst_len=7000064+(residue?((residue+79)%256):0); const size_t color=138240,ref_offset=aligned_cdf(ref_len,color),dst_offset=aligned_cdf(dst_len,color); - struct avd_decoded_buffer src={0},dst={0};struct avd_av1_ctx av1={0};struct avd_ctx ctx={.priv=&av1};struct avd_av1_run run={0}; + struct avd_decoded_buffer src={0},dst={0};struct avd_av1_ctx av1={0};struct avd_ctx ctx={.priv=&av1};struct avd_av1_run run={0};set_frame_ctx(&ctx); struct avd_av1_cdfs expected={0};unsigned char probabilities[sizeof(expected)]; src.base.vb.vb2_buf.planes[0].length=ref_len;src.base.vb.vb2_buf.memory=malloc(ref_len);assert(src.base.vb.vb2_buf.memory); dst.base.vb.vb2_buf.planes[0].length=dst_len;dst.base.vb.vb2_buf.memory=malloc(dst_len);assert(dst.base.vb.vb2_buf.memory); @@ -108,7 +114,7 @@ def structure(source, name): controls[0].p_cur.p=&sequence;controls[1].p_cur.p=&frame;controls[2].p_cur.p=&tile;controls[3].p_cur.p=&grain; run.base.bufs.dst=&dst.base.vb;run.base.y_out=0x120000000UL;av1.bufs.probs.cpu=probabilities;lookup_calls=0;preamble_calls=0; assert(avd_av1_run_preamble(&ctx,&run)==0);assert(preamble_calls==1); - if(check_addresses){assert(run.addresses.probs_out==run.base.y_out+dst_offset);assert(run.addresses.color==run.base.y_out+dst_offset+9216);} + if(check_addresses){assert(run.addresses.probs_out==run.base.y_out+dst_offset);assert(run.addresses.color==run.base.y_out+dst_offset+9216);assert(run.addresses.seg==run.base.y_out+dst_offset-seg_bytes(1920,1080));} avd_av1_set_prob(&ctx,&run); assert(lookup_calls==(mode?0:1));assert(memcmp(probabilities,&expected,sizeof(expected))==0); assert(memcmp(dst.base.vb.vb2_buf.memory+dst_offset,&expected,sizeof(expected))==0); @@ -120,18 +126,68 @@ def structure(source, name): struct v4l2_pix_format_mplane pix={.width=width,.height=height};struct avd_ctx ctx={0};pix.plane_fmt[0].sizeimage=compressed; avd_av1_adjust_decoded_fmt(&ctx,&pix); size_t min=pix.plane_fmt[0].sizeimage,color=avd_color_size(width,height),length=min+residue; - assert(min%256==0);assert(min==ALIGN(compressed,256)+9216+ALIGN(color,256)); - size_t cdf=AVD_AV1_CDFS_OFFSET(length,color),co=AVD_AV1_COLOR_OFFSET(length,color); + size_t seg=seg_bytes(width,height); + assert(min%256==0);assert(min==ALIGN(compressed,256)+9216+ALIGN(color,256)+seg); + size_t cdf=AVD_AV1_CDFS_OFFSET(length,color),co=AVD_AV1_COLOR_OFFSET(length,color),sg=AVD_AV1_SEG_OFFSET(length,color,seg); + assert(sg%256==0);assert(sg>=ALIGN(compressed,256));assert(sg+seg==cdf); assert(cdf%256==0&&co%256==0);assert(cdf>=compressed);assert(cdf+sizeof(struct avd_av1_cdfs)<=co);assert(co+color<=length); assert(cdf==aligned_cdf(length,color));assert(co==cdf+9216); } +/* The preamble refuses frames larger than the format and out-of-range reference indices. */ +static int preamble(unsigned int w,unsigned int h,unsigned char primary,signed char idx){ + struct avd_decoded_buffer dst={0};struct avd_av1_ctx av1={0};struct avd_ctx ctx={.priv=&av1};struct avd_av1_run run={0};set_frame_ctx(&ctx); + dst.base.vb.vb2_buf.planes[0].length=7000064;memset(&frame,0,sizeof(frame)); + frame.frame_width_minus_1=w-1;frame.frame_height_minus_1=h-1;frame.primary_ref_frame=primary;if(primary<7)frame.ref_frame_idx[primary]=idx; + run.base.bufs.dst=&dst.base.vb;run.base.y_out=0x120000000UL;return avd_av1_run_preamble(&ctx,&run); +} +static void guards(void){ + assert(preamble(1920,1080,0,0)==0);assert(preamble(1280,720,0,0)==0); + assert(preamble(1921,1080,0,0)==-EINVAL);assert(preamble(1920,1081,0,0)==-EINVAL); + assert(preamble(1920,1080,7,0)==0);assert(preamble(1920,1080,8,0)==-EINVAL);assert(preamble(1920,1080,255,0)==-EINVAL); + assert(preamble(1920,1080,6,7)==0);assert(preamble(1920,1080,6,8)==-EINVAL);assert(preamble(1920,1080,0,-1)==-EINVAL);assert(preamble(1920,1080,3,-128)==-EINVAL); +} +/* Segmentation map kept with each frame: expect 0 zeroes it, 1 copies the reference's, 2 leaves the AVD's map. */ +static void segmap(unsigned char flags,unsigned char primary,unsigned int ref_w,unsigned int ref_h,bool missing,int expect){ + const size_t len=7000064+79,ref_len=6964992+64+(ref_w*ref_h>1920*1080?65536:0),color=138240,seg=seg_bytes(1920,1080),ref_seg=seg_bytes(ref_w,ref_h); + const size_t off=aligned_cdf(len,color)-seg,roff=aligned_cdf(ref_len,color)-ref_seg;const dma_addr_t none=0x140000000UL; + struct avd_decoded_buffer src={0},dst={0};struct avd_av1_ctx av1={0};struct avd_ctx ctx={.priv=&av1};struct avd_av1_run run={0};set_frame_ctx(&ctx); + dst.base.vb.vb2_buf.planes[0].length=len;dst.base.vb.vb2_buf.memory=malloc(len);dst.base.vb.vb2_buf.dma=0x120000000UL;assert(dst.base.vb.vb2_buf.memory); + src.base.vb.vb2_buf.planes[0].length=ref_len;src.base.vb.vb2_buf.memory=malloc(ref_len);src.base.vb.vb2_buf.dma=0x130000000UL;assert(src.base.vb.vb2_buf.memory); + dst.av1.color_size=color;dst.av1.seg_size=seg;src.av1.color_size=color;src.av1.seg_size=ref_seg;src.av1.width=ref_w-1;src.av1.height=ref_h-1; + memset(dst.base.vb.vb2_buf.memory,0xa5,len);memset(src.base.vb.vb2_buf.memory,0x3c,ref_len); + for(size_t i=0;i=roff&&i1&&(strcmp(argv[1],"snapshot")==0||strcmp(argv[1],"snapshot-copy")==0)){assert(argc==3);inheritance(64,0,argv[2],strcmp(argv[1],"snapshot")==0);puts("snapshot inheritance PASS");return 0;} if(argc>1&&strcmp(argv[1],"copy")==0){inheritance(64,0,NULL,false);puts("probability inheritance PASS");return 0;} if(argc>1&&strcmp(argv[1],"seed")==0){inheritance(64,1,NULL,false);puts("default destination seed PASS");return 0;} if(argc>1&&strcmp(argv[1],"aligned")==0){for(unsigned int mode=0;mode<=4;mode++)inheritance(0,mode,NULL,true);reservation(1920,1080,6425600,0);puts("aligned compatibility PASS");return 0;} unsigned int cases=0; + guards();cases++;cases+=segmaps(); for(unsigned int residue=0;residue<256;residue++){ inheritance(residue,0,NULL,true);cases++; for(unsigned int mode=1;mode<=4;mode++){inheritance(residue,mode,NULL,true);cases++;} @@ -148,21 +204,36 @@ def main(): parser.add_argument('--out', type=Path, required=True) parser.add_argument('--neighbors', type=Path) parser.add_argument('--case', choices=['all', 'snapshot', 'snapshot-copy', 'copy', 'seed', 'aligned'], default='all') - parser.add_argument('--mutation', choices=['round-up', 'cpu-unaligned']) + parser.add_argument('--mutation', choices=['round-up', 'cpu-unaligned', 'seg-cpu-unaligned', 'seg-any-size', 'ref-index-upper', 'frame-width-unchecked']) args = parser.parse_args() args.out.mkdir(parents=True, exist_ok=True) paths = {name: args.source / DRIVER / name for name in ['avd-av1.c', 'avd-av1-entropymode.h', 'avd-av1-entropymode.c', 'avd.h']} text = {name: p.read_text() for name, p in paths.items()} src = text['avd-av1.c'] macros = src[src.index('#define AVD_CDFS_SIZE'):src.index('struct avd_av1_run {')] - functions = {n: function(src, n) for n in ['avd_av1_set_prob', 'avd_color_size', 'avd_av1_run_preamble', 'avd_av1_adjust_decoded_fmt']} - if args.mutation == 'round-up': - if 'ALIGN_DOWN((dst), AVD_ALIGN)' not in macros: - parser.error('round-up mutation requires aligned tail macros') - macros = macros.replace('ALIGN_DOWN((dst), AVD_ALIGN)', 'ALIGN((dst), AVD_ALIGN)') - elif args.mutation == 'cpu-unaligned': - functions['avd_av1_set_prob'] = functions['avd_av1_set_prob'].replace('AVD_AV1_CDFS_OFFSET(', 'UNALIGNED_CDFS_OFFSET(') - macros += '\n#define UNALIGNED_CDFS_OFFSET(dst,cl) ((dst)-ALIGN(cl,AVD_ALIGN)-ALIGN(AVD_CDFS_SIZE,AVD_ALIGN))\n' + functions = {n: function(src, n) for n in ['avd_av1_set_prob', 'avd_av1_set_seg', 'avd_av1_seg_size', 'avd_color_size', 'avd_av1_run_preamble', 'avd_av1_adjust_decoded_fmt']} + unaligned = ('\n#define UNALIGNED_CDFS_OFFSET(dst,cl) ((dst)-ALIGN(cl,AVD_ALIGN)-ALIGN(AVD_CDFS_SIZE,AVD_ALIGN))\n' + '#define UNALIGNED_SEG_OFFSET(dst,cl,sl) (UNALIGNED_CDFS_OFFSET(dst,cl)-(sl))\n') + # Each mutation names the extracted text it rewrites; a stale one is refused, never run as a no-op. + mutations = { + 'round-up': (None, 'ALIGN_DOWN((dst), AVD_ALIGN)', 'ALIGN((dst), AVD_ALIGN)'), + 'cpu-unaligned': ('avd_av1_set_prob', 'AVD_AV1_CDFS_OFFSET(', 'UNALIGNED_CDFS_OFFSET('), + 'seg-cpu-unaligned': ('avd_av1_set_seg', 'AVD_AV1_SEG_OFFSET(dst->', 'UNALIGNED_SEG_OFFSET(dst->'), + 'seg-any-size': ('avd_av1_set_seg', 'DIV_ROUND_UP(ref->av1.width + 1, 8)', 'DIV_ROUND_UP(frame->frame_width_minus_1 + 1, 8)'), + 'ref-index-upper': ('avd_av1_run_preamble', 'idx < 0 || idx >= V4L2_AV1_TOTAL_REFS_PER_FRAME', 'idx < 0'), + 'frame-width-unchecked': ('avd_av1_run_preamble', 'run->frame->frame_width_minus_1 + 1 > ctx->decoded_fmt.fmt.pix_mp.width ||', '0 ||'), + } + if args.mutation: + target, old, new = mutations[args.mutation] + body = macros if target is None else functions[target] + if old not in body: + parser.error(args.mutation + ' mutation no longer matches the driver source') + body = body.replace(old, new) + if target is None: + macros = body + else: + functions[target] = body + macros += unaligned header = text['avd-av1-entropymode.h'].replace('#include ', '') defaults = re.sub(r'^#include .*$', '', text['avd-av1-entropymode.c'], flags=re.M) alignment = re.search(r'^#define AVD_ALIGN\s+\d+', text['avd.h'], re.M).group() diff --git a/tools/asahi/bcm4377-id-tests.py b/tools/asahi/bcm4377-id-tests.py index 6d5bdb4b68427..167139cfffbc4 100755 --- a/tools/asahi/bcm4377-id-tests.py +++ b/tools/asahi/bcm4377-id-tests.py @@ -97,15 +97,28 @@ def main(): } pieces = {name: take(current, pattern) for name, pattern in patterns.items()} old_pieces = {name: take(old, pattern) for name, pattern in patterns.items()} + # The old gate is compiled against the current tables. Later commits added + # hardware fields and variant settings, so the struct and table may differ, + # but the old gate must still see the same chip IDs, PCI table and probe code. for name in patterns: - if name != 'gate': + if name not in ('gate', 'hw_struct', 'variants'): assert pieces[name] == old_pieces[name], name + for src in (pieces, old_pieces): + assert re.search(r'^\tunsigned int id;$', src['hw_struct'], re.M), src['hw_struct'] + variant_ids = r'^\t\[(BCM\d+)\] = \{\n\t\t\.id = (0x[0-9a-f]+),' + current_ids = re.findall(variant_ids, pieces['variants'], re.M) + assert len(current_ids) == 4, current_ids + assert current_ids == re.findall(variant_ids, old_pieces['variants'], re.M), 'variant ids' defs = '\n'.join(re.findall(r'^#define BCM\d+_DEVICE_ID [^\n]+', current, re.M)) assert defs == '\n'.join(re.findall(r'^#define BCM\d+_DEVICE_ID [^\n]+', old, re.M)) ids = (root / 'include/linux/pci_ids.h').read_text() pci_defs = '\n'.join(re.findall(r'^#define (?:PCI_VENDOR_ID_BROADCOM|PCI_CLASS_NETWORK_OTHER)\s+[^\n]+', ids, re.M)) any_id = take((root / 'include/linux/device-id/pci.h').read_text(), r'^#define PCI_ANY_ID[^\n]+') - source_prefix = PREFIX + defs + '\n' + pci_defs + '\n' + any_id + '\n' + '\n'.join(pieces[n] for n in ('enum', 'hw_struct', 'variants', 'pci_macro_table')) + # Register offsets the variant table names by macro, as defined in the driver. + table_macros = sorted(set(re.findall(r'\b[A-Z][A-Z0-9_]+\b', pieces['variants']))) + hw_defs = '\n'.join(m.group() for name in table_macros for m in [re.search(r'^#define ' + name + r'\b[^\n]*', current, re.M)] if m) + assert 'BAR2_CONTEXT_ADDR_HI' in hw_defs, hw_defs + source_prefix = PREFIX + defs + '\n' + hw_defs + '\n' + pci_defs + '\n' + any_id + '\n' + '\n'.join(pieces[n] for n in ('enum', 'hw_struct', 'variants', 'pci_macro_table')) suffix = SUFFIX.replace('@SELECTION@', pieces['selection']).replace('@REGISTRATION@', pieces['registration']) gates = {'positive': pieces['gate'], 'published-original': old_pieces['gate']} assert pieces['gate'].count('0x4378') == 1 @@ -124,7 +137,7 @@ def no_core(): (args.out / f'{name}.log').write_text(run.stdout + run.stderr) assert run.returncode == (0 if name == 'positive' else -6), (name, run.returncode) results[name] = {'exit': run.returncode, 'source_sha256': sha(source.read_text()), 'stdout': run.stdout.strip()} - receipt = {'source_sha256': sha(current), 'old_source_sha256': sha(old), 'old_ref': args.old_ref, 'actual_inputs': {name: sha(value) for name, value in pieces.items()}, 'device_ids_sha256': sha(defs), 'pci_defs_sha256': sha(pci_defs + any_id), 'results': results, 'scope': 'Actual variant/PCI tables, hardware struct, probe selection statement, reset registration block and admission function. PCI enumeration, OF board matching and unrelated probe operations are controlled; no full probe or hardware execution.'} + receipt = {'source_sha256': sha(current), 'old_source_sha256': sha(old), 'old_ref': args.old_ref, 'actual_inputs': {name: sha(value) for name, value in pieces.items()}, 'device_ids_sha256': sha(defs), 'table_macros_sha256': sha(hw_defs), 'variant_ids': current_ids, 'pci_defs_sha256': sha(pci_defs + any_id), 'results': results, 'scope': 'Actual variant/PCI tables, hardware struct, probe selection statement, reset registration block and admission function. PCI enumeration, OF board matching and unrelated probe operations are controlled; no full probe or hardware execution.'} (args.out / 'receipt.json').write_text(json.dumps(receipt, indent=2) + '\n') print(json.dumps(receipt, indent=2)) diff --git a/tools/asahi/bcm4377-resume-tests.py b/tools/asahi/bcm4377-resume-tests.py index 2eab82a96c1ca..4c7104ff1b458 100755 --- a/tools/asahi/bcm4377-resume-tests.py +++ b/tools/asahi/bcm4377-resume-tests.py @@ -35,6 +35,11 @@ def extract(s,n): hw_enum=re.search(r'^enum bcm4377_chip \{.*?^\};',s,re.M|re.S).group() hw_table=re.search(r'^static const struct bcm4377_hw bcm4377_hw_variants\[\] = \{.*?^\};',s,re.M|re.S).group() fixture=re.sub(r'^struct bcm4377_hw \{.*?\};', 'typedef uint32_t u32;\n'+hw_struct,fixture,count=1,flags=re.M) +# The variant table names register offsets by macro; take their definitions from the driver. +hw_macros='\n'+''.join(m.group()+'\n' for name in sorted(set(re.findall(r'\b[A-Z][A-Z0-9_]+\b',hw_table))) for m in [re.search(r'^#define '+name+r'\b.*$',s,re.M)] if m) +assert 'BAR2_CONTEXT_ADDR_HI' in hw_macros,hw_macros +# bcm4377_suspend() undoes a failed wake arming through bcm4377_resume(), declared ahead of it. +resume_decl=re.search(r'^static int bcm4377_resume\(struct device \*dev\);$',s,re.M).group()+'\n' fixture=fixture.replace('struct bcm4377_hw *hw;', 'const struct bcm4377_hw *hw;') table_callbacks=''' static int send_cal(struct bcm4377_data *); @@ -45,7 +50,7 @@ def extract(s,n): #define bcm4377_send_ptb send_ptb #define bcm4378_send_ptb send_ptb ''' -code=fixture+hw_enum+table_callbacks+hw_table+'\n'.join(bodies.values())+'\n'.join(core_bodies.values())+extract(core,'hci_reset_dev_sync')+(r/'tools/asahi/bcm4377-resume/controls.c').read_text() +code=fixture+hw_enum+hw_macros+table_callbacks+hw_table+resume_decl+'\n'.join(bodies.values())+'\n'.join(core_bodies.values())+extract(core,'hci_reset_dev_sync')+(r/'tools/asahi/bcm4377-resume/controls.c').read_text() (o/'production.c').write_text(code);env=dict(os.environ,TMPDIR=str(o/'tmp')) c=subprocess.run(['cc','-std=gnu11','-pthread','-Wall','-Wextra','-Wno-unused-function','-Wno-unused-parameter','-fsanitize=address,undefined','-fno-pie','-no-pie',str(o/'production.c'),'-o',str(o/'controls')],env=env,capture_output=True,text=True) (o/'compile.log').write_text(c.stdout+c.stderr);assert not c.returncode,c.stderr diff --git a/tools/asahi/compute-preempt-tests.py b/tools/asahi/compute-preempt-tests.py index fdab1e9e7406b..ed246a3e69060 100755 --- a/tools/asahi/compute-preempt-tests.py +++ b/tools/asahi/compute-preempt-tests.py @@ -29,7 +29,9 @@ def read(path, ref=None): return (root / path).read_text() def policy(text): start = text.index(' let mut prio = *raw::PRIORITY.get(priority as usize).ok_or(EINVAL)?;') - end = text.index(' let inner = WorkQueueInner::ver {', start) + # The policy ends with the compute priority override; queue allocation follows. + tail = ' prio.5 = 1;\n }\n' + end = text.index(tail, start) + len(tail) return text[start:end] queue = read('drivers/gpu/drm/asahi/workqueue.rs', args.ref) macro = read('rust/macros/versions.rs', args.ref) diff --git a/tools/asahi/dma-heap-fd-tests.py b/tools/asahi/dma-heap-fd-tests.py index f40d0709d58a8..4bf6770547efe 100755 --- a/tools/asahi/dma-heap-fd-tests.py +++ b/tools/asahi/dma-heap-fd-tests.py @@ -45,6 +45,7 @@ def function(source, name): #define PAGE_ALIGN(x) (((x)+PAGE_SIZE-1)&~(uint64_t)(PAGE_SIZE-1)) #define ARRAY_SIZE(x) (sizeof(x)/sizeof((x)[0])) #define max(a,b) ((a)>(b)?(a):(b)) +#define max3(a,b,c) max(max(a,b),c) #define IOC_IN (_IOC_WRITE << _IOC_DIRSHIFT) #define IOC_OUT (_IOC_READ << _IOC_DIRSHIFT) #define array_index_nospec(x,n) (x) diff --git a/tools/asahi/g15-input-error-tests.py b/tools/asahi/g15-input-error-tests.py index 47971fccfa710..809411963fd17 100644 --- a/tools/asahi/g15-input-error-tests.py +++ b/tools/asahi/g15-input-error-tests.py @@ -32,13 +32,14 @@ def extract(text, marker): impl Error { fn from_errno(n:i32)->Self {assert!(n<0);Self(n)} fn to_errno(self)->i32{self.0} } type Result = std::result::Result; const EIO:Error=Error(-5);const ENODEV:Error=Error(-19);const ETIMEDOUT:Error=Error(-110); -const ECANCELED:Error=Error(-125);const EINVAL:Error=Error(-22);const ENOMEM:Error=Error(-12); +const ECANCELED:Error=Error(-125);const EINVAL:Error=Error(-22);const ENOMEM:Error=Error(-12);const ENOENT:Error=Error(-2); macro_rules! pr_warn { ($($arg:tt)*)=>{{ let _=format_args!($($arg)*); }} } +macro_rules! mod_pr_debug { ($($arg:tt)*)=>{{ let _=format_args!($($arg)*); }} } struct Mutex(StdMutex);impl Mutex{fn lock(&self)->MutexGuard<'_,T>{self.0.lock().unwrap()}} struct Mirror{word:*mut i32} -struct VmStatus{error:AtomicI32,reported_error:AtomicI32,mirror:Mutex>} +struct VmStatus{error:AtomicI32,reported_error:AtomicI32,client_closed:AtomicBool,warned:AtomicBool,mirror:Mutex>} impl VmStatus { - fn new(word:*mut i32)->Self{Self{error:AtomicI32::new(0),reported_error:AtomicI32::new(0),mirror:Mutex(StdMutex::new(Some(Mirror{word})))}} + fn new(word:*mut i32)->Self{Self{error:AtomicI32::new(0),reported_error:AtomicI32::new(0),client_closed:AtomicBool::new(false),warned:AtomicBool::new(false),mirror:Mutex(StdMutex::new(Some(Mirror{word})))}} __STATUS__ } mod g17 {use super::*;__POLICY__} @@ -137,7 +138,8 @@ def main(): bodies = "\n".join(methods) code = PRELUDE.replace("__JOB__", bodies).replace("__STATUS__", "\n".join( extract(status, marker) for marker in [" pub(crate) fn get(", " pub(crate) fn record(", - " pub(crate) fn reported_error(", " pub(crate) fn report_failure("])).replace( + " pub(crate) fn reported_error(", " pub(crate) fn report_failure(", + " fn unobserved_teardown("])).replace( "__POLICY__", extract(policy, "pub(crate) fn inherited_error(")) variants = {"control": code} expected = {} diff --git a/tools/asahi/mt7932-association-events-tests.py b/tools/asahi/mt7932-association-events-tests.py index 3f352376a1c82..436ab4d00b75f 100644 --- a/tools/asahi/mt7932-association-events-tests.py +++ b/tools/asahi/mt7932-association-events-tests.py @@ -52,6 +52,8 @@ def function(source, name): #define NL80211_CHAN_WIDTH_40 1 #define NL80211_CHAN_WIDTH_80 2 #define WLAN_REASON_UNSPECIFIED 1 +#define WLAN_EID_HT_CAPABILITY 45 +struct element {u8 id,datalen,data[];}; #define WLAN_EID_EXTENSION 255 #define WLAN_EID_EXT_HE_CAPABILITY 35 #define dev_info(...) ((void)0) @@ -74,7 +76,6 @@ def function(source, name): static void complete_all(struct completion*c){c->done=UINT_MAX;} struct ieee80211_channel {int center_freq;}; struct cfg80211_chan_def {struct ieee80211_channel *chan;int width,center_freq1;}; -struct element {u8 data[32];}; struct test_band {struct {bool ht_supported;}ht_cap;}; struct test_wiphy {struct test_band *bands[NUM_NL80211_BANDS];}; static struct test_wiphy wiphy; @@ -108,6 +109,13 @@ def function(source, name): static bool cfg80211_chandef_usable(void*w,const struct cfg80211_chan_def*d,int flags){return usable;} static bool mt7932_assoc_phy_valid(const void*b,const void*p,size_t n){return true;} static const struct element *cfg80211_find_ext_elem(int id,const void*p,size_t n){return NULL;} +/* The HT lookup feeds a debug log; it walks only the exported request IEs. */ +static unsigned ht_lookups;static const void *ht_found; +static const struct element *cfg80211_find_elem(int id,const void*p,size_t n){ + const u8*b=p;size_t o=0;assert(id==WLAN_EID_HT_CAPABILITY&&p&&n<=512);ht_lookups++;ht_found=NULL; + while(n-o>=2&&b[o+1]<=n-o-2){if(b[o]==id)return ht_found=b+o;o+=2+b[o+1];} + return NULL; +} static void netif_carrier_off(void*n){} static void netif_stop_queue(void*n){} static void schedule_work(void*w){scheduled++;} @@ -209,6 +217,12 @@ def function(source, name): reset(&m,SUCCESS);u8 rejected[52]={};put_unaligned_le16(1,rejected); receive(&m,0xee,0,rejected,sizeof(rejected),0x40); assert(m.connect_error==-ECONNREFUSED&&m.assoc_start.done&&m.assoc_done.done&&!m.peer_valid);cases++; + // The generated request IEs are exported once; the HT log reads the copy. + reset(&m,SUCCESS);u8 ies[32]={};put_unaligned_le32(28,ies);ies[4]=WLAN_EID_HT_CAPABILITY;ies[5]=26;for(unsigned i=0;i<26;i++)ies[6+i]=i+1;ht_lookups=0; + receive(&m,0xee,0,ies,sizeof(ies),0x5e); + assert(!m.connect_error&&m.assoc_request_seen&&m.assoc_request_ie_len==28&&!memcmp(m.assoc_request_ies,ies+4,28)&&ht_lookups==1&&ht_found==m.assoc_request_ies);cases++; + reset(&m,SUCCESS);ies[5]=25;ht_lookups=0;receive(&m,0xee,0,ies,sizeof(ies),0x5e); + assert(m.connect_error==-EPROTO&&!m.assoc_request_seen&&!ht_lookups&&m.assoc_start.done&&m.assoc_done.done);cases++; printf("PASS %u association event/wait/calibration ownership controls\n",cases); } ''' diff --git a/tools/asahi/neo-stability-tests.py b/tools/asahi/neo-stability-tests.py index f65cab986c626..ff02d627e5711 100644 --- a/tools/asahi/neo-stability-tests.py +++ b/tools/asahi/neo-stability-tests.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # SPDX-License-Identifier: GPL-2.0-only OR MIT -"""Host controls for retained domains, wake IRQs, USB roles and country policy.""" +"""Host controls for retained domains, wake IRQs, USB roles and band-qualified country policy.""" import argparse import hashlib import json @@ -34,7 +34,16 @@ def function(source, name): #include #include #define SHA256_DIGEST_SIZE 32 -typedef uint8_t u8; typedef uint16_t u16; typedef uint32_t u32; typedef int32_t s32; +typedef uint8_t u8; typedef uint16_t u16; typedef uint32_t u32; typedef uint64_t u64; typedef int8_t s8; typedef int32_t s32; +enum nl80211_band {NL80211_BAND_2GHZ,NL80211_BAND_5GHZ,NL80211_BAND_60GHZ,NL80211_BAND_6GHZ,NL80211_BAND_S1GHZ,NL80211_BAND_LC,NUM_NL80211_BANDS}; +#define min(a,b) ((a)<(b)?(a):(b)) +#define min_t(t,a,b) ((t)(a)<(t)(b)?(t)(a):(t)(b)) +#define ARRAY_SIZE(a) (sizeof(a)/sizeof((a)[0])) +#define BIT_ULL(n) (1ULL<<(n)) +#define GENMASK_ULL(h,l) (((~0ULL)>>(63-(h)))&(~0ULL<<(l))) +#define ERR_PTR(e) ((void*)(intptr_t)(e)) +#define IS_ERR(p) ((uintptr_t)(p)>=(uintptr_t)-4095) +#define MHZ_TO_KHZ(f) ((f)*1000) #define READ_ONCE(x) (x) #define WRITE_ONCE(x,v) ((x)=(v)) #define lockdep_assert_held(x) ((void)0) @@ -61,12 +70,24 @@ def function(source, name): static int devm_add_action_or_reset(struct device*d,void(*f)(void*),void*p){if(action_error){f(p);return action_error;}cleanup=f;cleanup_data=p;return 0;} static int devm_device_init_wakeup(struct device*d){wakeup_calls++;if(!wakeup_error)d->wakeup=true;return wakeup_error;} static int devm_pm_set_wake_irq(struct device*d,int i){irq_calls++;if(!irq_error)d->wake_irq=i;return irq_error;} +#define IRQF_NO_SUSPEND 0x4000 +#define IRQF_NO_AUTOEN 0x80000 +static unsigned long rx_flags;static int rx_irq_requested; +static int dockchannel_rx_irq(int i,void*p){return 0;} +static int dockchannel_rx_irq_thread(int i,void*p){return 0;} +static int devm_request_threaded_irq(struct device*d,int irq,int(*h)(int,void*),int(*t)(int,void*),unsigned long f,const char*n,void*p){ + assert(h==dockchannel_rx_irq&&t==dockchannel_rx_irq_thread&&!strcmp(n,"apple-dockchannel-rx"));rx_irq_requested=irq;rx_flags=f;return 0;} enum dwc3_apple_state {DWC3_APPLE_PROBE_PENDING,DWC3_APPLE_NO_CABLE,DWC3_APPLE_HOST,DWC3_APPLE_DEVICE,DWC3_APPLE_SUSPENDED}; -struct dwc3 {int num_usb3_ports,num_usb2_ports;int usb3_generic_phy[2],usb2_generic_phy[2],usb3_phy,usb2_phy;}; -struct dwc3_apple {struct device*dev;struct dwc3 dwc;int lock,reset;enum dwc3_apple_state state;bool fixed_hub,core_active,core_exit_pending,reset_pending;unsigned usb3_power_off_pending,usb2_power_off_pending;}; -static int core_error,host_error,gadget_error,reset_error; -static int dwc3_apple_core_start(struct dwc3_apple*a,enum dwc3_apple_state s){if(core_error)return core_error;a->core_active=true;a->reset_pending=true;return 0;} -static void dwc3_apple_set_role(struct dwc3_apple*a,enum dwc3_apple_state s){} +#define DWC3_GCTL_PRTCAP_HOST 1 +struct dwc3 {int num_usb3_ports,num_usb2_ports;int usb3_generic_phy[2],usb2_generic_phy[2],usb3_phy,usb2_phy;int current_dr_role;}; +struct dwc3_apple {struct device*dev;struct dwc3 dwc;int lock,reset;enum dwc3_apple_state state;bool fixed_hub,host_only,rpm_held,core_active,core_exit_pending,reset_pending;unsigned usb3_power_off_pending,usb2_power_off_pending;}; +static int core_error,host_error,gadget_error,reset_error,role_error,start_role; +static unsigned rpm_puts; +/* A started core holds the glue runtime PM reference until core_stop drops it. */ +static int dwc3_apple_core_start(struct dwc3_apple*a,enum dwc3_apple_state s){start_role=a->dwc.current_dr_role;if(core_error)return core_error;a->core_active=true;a->reset_pending=true;a->rpm_held=true;return 0;} +static int dwc3_apple_set_role(struct dwc3_apple*a,enum dwc3_apple_state s){assert(a->core_active);return role_error;} +static void pm_runtime_mark_last_busy(struct device*d){} +static void pm_runtime_put_autosuspend(struct device*d){rpm_puts++;} static int dwc3_host_init(struct dwc3*d){return host_error;} static int dwc3_gadget_init(struct dwc3*d){return gadget_error;} static void dwc3_host_exit(struct dwc3*d){role_exits++;} @@ -93,16 +114,24 @@ def function(source, name): #define WLAN_REASON_DEAUTH_LEAVING 3 #define __assign_bit(i,b,v) ((b)[(i)/64]=((b)[(i)/64]&~(1ULL<<((i)%64)))|((uint64_t)!!(v)<<((i)%64))) #define for_each_set_bit(i,b,n) for((i)=0;(i)<(n);(i)++) if((b)[(i)/64]&(1ULL<<((i)%64))) -#define bitmap_zero(b,n) memset((b),0,8) -struct ieee80211_channel {unsigned flags,hw_value;int max_power;}; +#define bitmap_zero(b,n) memset((b),0,((n)+63)/64*8) +#define IEEE80211_CHAN_NO_6GHZ_VLP_CLIENT 128 +#define NL80211_RRF_NO_IR (1<<7) +struct ieee80211_channel {unsigned flags,hw_value;int max_power;enum nl80211_band band;int center_freq;s8 psd;}; +static u64 get_unaligned_le64(const u8*p){return get_unaligned_le32(p)|(u64)get_unaligned_le32(p+4)<<32;} +static const void *memchr_inv(const void*p,int c,size_t n){const u8*b=p;for(size_t i=0;i=5955000&&khz<=7115000&&(khz/1000-5955)%20==0);return rule6_absent?ERR_PTR(-ERANGE):&rule6;} static unsigned rtnl_depth,wiphy_depth,schedules; static void rtnl_lock(void){assert(!rtnl_depth++);} static void rtnl_unlock(void){assert(rtnl_depth--==1);} static void wiphy_lock(struct wiphy*w){assert(rtnl_depth&& !wiphy_depth++);} static void wiphy_unlock(struct wiphy*w){assert(wiphy_depth--==1);} -struct mt7932 {struct ieee80211_channel channels[MT7932_CHANNELS_2G],channels5[MT7932_CHANNELS_5G];struct wiphy*wiphy;struct net_device*netdev;struct {struct device dev;}*pdev;struct mt7932_reg_snapshot reg_desired,reg_deferred;unsigned reg_generation,response_lock,startup_work;bool reg_pending,stopping,interface_registered,connecting,connected,disconnecting,reg_deferral,data_ready;int data_lock;int connect_error,assoc_start,assoc_done,discovery_done;uint64_t policy_disabled[1];}; +struct mt7932 {struct ieee80211_channel channels[MT7932_CHANNELS_2G],channels5[MT7932_CHANNELS_5G],channels6[MT7932_CHANNELS_6G];struct wiphy*wiphy;struct net_device*netdev;struct {struct device dev;}*pdev;struct mt7932_reg_snapshot reg_desired,reg_deferred;unsigned reg_generation,response_lock,startup_work;bool reg_pending,stopping,interface_registered,connecting,connected,disconnecting,reg_deferral,data_ready;int data_lock;int connect_error,assoc_start,assoc_done,discovery_done;uint64_t policy_disabled[(MT7932_CHANNELS+63)/64];}; #define spin_lock(l) ((void)(l)) #define spin_unlock(l) ((void)(l)) static void mt_data_kick_locked(struct mt7932*m){assert(0 && "unexpected TX boundary");} @@ -117,9 +146,11 @@ def function(source, name): int main(int argc,char**argv){ assert(argc==2);struct device d={};struct dockchannel dc={.dev=&d,.rx_irq=42}; if(!strcmp(argv[1],"wake")){ - assert(!dockchannel_init_wakeup(&dc)&&d.wakeup&&d.wake_irq==42); - wakeup_error=-ENOMEM;assert(dockchannel_init_wakeup(&dc)==-ENOMEM&&irq_calls==1); - wakeup_error=0;irq_error=-EINVAL;assert(dockchannel_init_wakeup(&dc)==-EINVAL); + /* The FIFO's device is a wakeup source; the receive IRQ is not a wake IRQ. */ + assert(!dockchannel_init_wakeup(&dc)&&d.wakeup&&!d.wake_irq&&!irq_calls&&wakeup_calls==1); + wakeup_error=-ENOMEM;d.wakeup=false;assert(dockchannel_init_wakeup(&dc)==-ENOMEM&&!d.wakeup&&!irq_calls); + /* It keeps receiving through system sleep, so the client filters wake data. */ + assert(!dockchannel_request_rx(&d,&dc)&&rx_irq_requested==42&&rx_flags==(IRQF_NO_AUTOEN|IRQF_NO_SUSPEND)); }else if(!strcmp(argv[1],"sep")){ domain_count=1;assert(!sep_pm_keep_domains(&d)&&!attach_calls); domain_count=2;attach_error=-EAGAIN;assert(sep_pm_keep_domains(&d)==-EAGAIN&&!pd0.syscore); @@ -127,39 +158,78 @@ def function(source, name): cleanup(cleanup_data);assert(!pd0.syscore&&!pd1.syscore); action_error=-ENOMEM;assert(sep_pm_keep_domains(&d)==-ENOMEM&&!pd0.syscore&&!pd1.syscore); }else if(!strcmp(argv[1],"usb")){ - for(unsigned fixed=0;fixed<2;fixed++)for(unsigned role=DWC3_APPLE_HOST;role<=DWC3_APPLE_DEVICE;role++){ - struct dwc3_apple a={.dev=&d,.fixed_hub=fixed,.state=DWC3_APPLE_PROBE_PENDING}; - assert(!dwc3_apple_init(&a,role)&&d.syscore&&a.state==role); - assert(!dwc3_apple_exit(&a)&&!d.syscore&&a.state==DWC3_APPLE_NO_CABLE); - unsigned n=core_exits;assert(!dwc3_apple_exit(&a)&&core_exits==n); - core_error=-EIO;assert(dwc3_apple_init(&a,role)==-EIO&&!d.syscore);core_error=0; - host_error=gadget_error=-ENOMEM;n=core_exits;assert(dwc3_apple_init(&a,role)==-ENOMEM&&!d.syscore&&core_exits==n+1);host_error=gadget_error=0; - assert(!dwc3_apple_init(&a,role)&&d.syscore);reset_error=-EIO;assert(dwc3_apple_exit(&a)==-EIO&&d.syscore&&a.reset_pending); - reset_error=0;n=core_exits;unsigned exits=role_exits; - assert(!dwc3_apple_exit(&a)&&!d.syscore&&!a.reset_pending); + for(unsigned fixed=0;fixed<2;fixed++)for(unsigned host_only=0;host_only<2;host_only++)for(unsigned role=DWC3_APPLE_HOST;role<=DWC3_APPLE_DEVICE;role++){ + struct dwc3_apple a={.dev=&d,.fixed_hub=fixed,.host_only=host_only,.state=DWC3_APPLE_PROBE_PENDING}; + unsigned puts=rpm_puts; + assert(!dwc3_apple_init(&a,role)&&d.syscore&&a.state==role&&a.rpm_held); + /* Only a host-only host start marks the role before the core comes up. */ + assert(start_role==(host_only&&role==DWC3_APPLE_HOST?DWC3_GCTL_PRTCAP_HOST:0)); + assert(!dwc3_apple_exit(&a)&&!d.syscore&&a.state==DWC3_APPLE_NO_CABLE&&!a.rpm_held&&rpm_puts==puts+1); + unsigned n=core_exits;assert(!dwc3_apple_exit(&a)&&core_exits==n&&rpm_puts==puts+1); + core_error=-EIO;assert(dwc3_apple_init(&a,role)==-EIO&&!d.syscore&&!a.rpm_held);core_error=0; + host_error=gadget_error=-ENOMEM;n=core_exits;assert(dwc3_apple_init(&a,role)==-ENOMEM&&!d.syscore&&core_exits==n+1&&!a.rpm_held&&rpm_puts==puts+2);host_error=gadget_error=0; + /* A refused PHY role stops the core again before any host or gadget start. */ + role_error=-EIO;n=core_exits;unsigned exits=role_exits; + assert(dwc3_apple_init(&a,role)==-EIO&&!d.syscore&&!a.core_active&&!a.rpm_held&&core_exits==n+1&&role_exits==exits&&a.state==DWC3_APPLE_NO_CABLE&&rpm_puts==puts+3);role_error=0; + assert(!dwc3_apple_init(&a,role)&&d.syscore);reset_error=-EIO;assert(dwc3_apple_exit(&a)==-EIO&&d.syscore&&a.reset_pending&&a.rpm_held&&rpm_puts==puts+3); + reset_error=0;n=core_exits;exits=role_exits; + assert(!dwc3_apple_exit(&a)&&!d.syscore&&!a.reset_pending&&!a.rpm_held&&rpm_puts==puts+4); assert(core_exits==n&&role_exits==exits); } }else if(!strcmp(argv[1],"regulatory")){ + enum {N25=MT7932_CHANNELS_2G+MT7932_CHANNELS_5G,SIX13=N25+3,VLP=N25+5}; struct mt7932 m={.reg_generation=7};struct wiphy w={.priv=&m};m.wiphy=&w; struct mt7932_policy p={};u8 tables[9][1020]={}; - for(unsigned i=0;i<9;i++){p.table[i+1]=tables[i];tables[i][4]=0;} - for(unsigned i=0;iband=ihw_value=imax_power=20; memset(tables[t]+44+j*122,0xc4,122);tables[t][44+j*122]=c->hw_value; if(i!=10)tables[t][45+j*122]=1; } - assert(!mt7932_policy_permits(&p,11)&&mt7932_policy_permits(&p,10)&&mt7932_policy_permits(&p,165)&&!mt7932_policy_permits(&p,14)); - struct regulatory_request request={.alpha2={'U','S'}};mt_regulatory_notify(&w,&request);m.reg_pending=false; - unsigned generation=m.reg_generation;mt_policy_disable(&m,&p,generation-1);assert(!m.channels[10].flags&&!m.policy_disabled[0]); - mt_policy_disable(&m,&p,generation);assert(m.channels[10].flags==1&&m.policy_disabled[0]==(1ULL<<10)); - struct mt7932_reg_snapshot reg=m.reg_desired;mt7932_policy_filter(®,&p);assert(reg.length==12+(MT7932_CHANNELS-1)*8&®.domain[8]==12&®.domain[9]==MT7932_CHANNELS_5G); - for(unsigned i=0;iband=NL80211_BAND_6GHZ;c->hw_value=mt7932_channel_6g(k);c->center_freq=5950+5*c->hw_value; + c->max_power=14;c->psd=-1;c->flags=IEEE80211_CHAN_NO_IR|IEEE80211_CHAN_PSD|(N25+k==VLP?IEEE80211_CHAN_NO_6GHZ_VLP_CLIENT:0); + } + assert(!mt7932_policy_permits(&p,NL80211_BAND_2GHZ,11)&&mt7932_policy_permits(&p,NL80211_BAND_2GHZ,10)&&mt7932_policy_permits(&p,NL80211_BAND_5GHZ,165)&&!mt7932_policy_permits(&p,NL80211_BAND_2GHZ,14)); + /* Channel numbers mean nothing without their band. */ + assert(mt7932_policy_permits(&p,NL80211_BAND_2GHZ,13)&&!mt7932_policy_permits(&p,NL80211_BAND_6GHZ,13)&&mt7932_policy_permits(&p,NL80211_BAND_6GHZ,1)); + assert(!mt7932_policy_permits(&p,NL80211_BAND_5GHZ,1)&&!mt7932_policy_permits(&p,NL80211_BAND_2GHZ,36)&&!mt7932_policy_permits(&p,NL80211_BAND_6GHZ,3)&&!mt7932_policy_permits(&p,NUM_NL80211_BANDS,1)); + struct regulatory_request request={.alpha2={'U','S'}};rule_lookups=0;mt_regulatory_notify(&w,&request);m.reg_pending=false; + assert(rule_lookups==MT7932_CHANNELS_6G); + /* The VLP-client channel stays out of the firmware domain; the others lose NO_IR there. */ + assert(m.reg_desired.length==12+(MT7932_CHANNELS-1)*8&&m.reg_desired.domain[8]==13&&m.reg_desired.domain[9]==MT7932_CHANNELS_5G&&m.reg_desired.domain[10]==MT7932_CHANNELS_6G-1); + for(unsigned i=0;ihw_value&&!f); + else{unsigned k=i-N25+(i>=VLP);assert(get_unaligned_le16(e)==mt7932_channel_6g(k)&&f==IEEE80211_CHAN_PSD);} + } + for(unsigned k=0;k=N25-1?c!=13:true);} + /* A rule that keeps NO_IR, or no rule at all, leaves the channel passive. */ + m.channels6[0].flags|=IEEE80211_CHAN_NO_IR;m.channels6[1].flags|=IEEE80211_CHAN_NO_IR; + rule6.flags=NL80211_RRF_NO_IR;mt_policy_disable(&m,&p,generation);assert(m.channels6[0].flags&IEEE80211_CHAN_NO_IR); + rule6.flags=0;rule6_absent=true;mt_policy_disable(&m,&p,generation);assert(m.channels6[1].flags&IEEE80211_CHAN_NO_IR); + rule6_absent=false;mt_policy_disable(&m,&p,generation);assert(!(m.channels6[0].flags&IEEE80211_CHAN_NO_IR)&&!(m.channels6[1].flags&IEEE80211_CHAN_NO_IR)); + m.channels[10].flags=0;m.channels6[3].flags=IEEE80211_CHAN_NO_IR|IEEE80211_CHAN_PSD;mt_regulatory_notify(&w,&request); + assert(m.reg_generation==generation&&m.channels[10].flags==1&&(m.channels6[3].flags&IEEE80211_CHAN_DISABLED)); + m.channels[10].flags=0;m.channels6[3].flags=IEEE80211_CHAN_NO_IR|IEEE80211_CHAN_PSD;request.alpha2[0]='C';request.alpha2[1]='A';mt_regulatory_notify(&w,&request); + assert(m.reg_generation==generation+1&&!m.policy_disabled[0]&&!m.policy_disabled[1]); m.reg_pending=false;m.connected=true;unsigned applied=m.reg_generation; put_unaligned_le32(IEEE80211_CHAN_NO_IR,m.reg_desired.domain+16); mt_regulatory_notify(&w,&request); @@ -178,7 +248,7 @@ def main(): parser.add_argument('--output', type=Path, required=True) parser.add_argument('--old-usb', action='store_true') parser.add_argument('--old-regulatory', action='store_true') - parser.add_argument('--mutation', choices=['sep-syscore', 'wake-irq', 'stale-policy']) + parser.add_argument('--mutation', choices=['sep-syscore', 'wake-irq', 'rx-suspend', 'stale-policy', 'usb-rpm', 'vlp-client', 'six-band']) args = parser.parse_args() args.output.mkdir(parents=True, exist_ok=True) files = { @@ -199,16 +269,33 @@ def main(): ROOT/'drivers/net/wireless/mediatek/mt7932/regulatory.h'] header = '\n'.join(re.sub(r'^#include .*\n', '', path.read_text(), flags=re.M) for path in headers) + # The receive IRQ request statement from dockchannel_init(), run against a recording stub. + dock = (ROOT/'drivers/soc/apple/dockchannel.c').read_text() + request = re.search(r'\tret = devm_request_threaded_irq\(dev, dockchannel->rx_irq,.*?\);\n', dock, re.S) + if not request: + raise ValueError('dockchannel RX IRQ request') + bodies['dockchannel_request_rx'] = ('static int dockchannel_request_rx(struct device *dev, struct dockchannel *dockchannel)\n' + '{\n\tint ret;\n\n' + request[0] + '\treturn ret;\n}\n') at = PREFIX.index('struct mt7932 {') code = PREFIX[:at] + header + PREFIX[at:] + '\n'.join(bodies.values()) + MAIN if not args.old_usb: code = code.replace(',DWC3_APPLE_SUSPENDED', '') - if args.mutation == 'sep-syscore': - code = code.replace('dev_pm_syscore_device(list->pd_devs[i], true);', 'dev_pm_syscore_device(list->pd_devs[i], false);') - if args.mutation == 'wake-irq': - code = code.replace('return devm_pm_set_wake_irq(dockchannel->dev, dockchannel->rx_irq);', 'return 0;') - if args.mutation == 'stale-policy': - code = code.replace('generation != READ_ONCE(m->reg_generation)', 'false') + mutations = { + 'sep-syscore': ('dev_pm_syscore_device(list->pd_devs[i], true);', 'dev_pm_syscore_device(list->pd_devs[i], false);'), + 'wake-irq': ('return devm_device_init_wakeup(dockchannel->dev);', + 'return devm_device_init_wakeup(dockchannel->dev) ?: devm_pm_set_wake_irq(dockchannel->dev, dockchannel->rx_irq);'), + 'rx-suspend': ('IRQF_NO_AUTOEN | IRQF_NO_SUSPEND', 'IRQF_NO_AUTOEN'), + 'stale-policy': ('generation != READ_ONCE(m->reg_generation)', 'false'), + 'usb-rpm': ('pm_runtime_put_autosuspend(appledwc->dev);', '(void)0;'), + 'vlp-client': ('if (flags & IEEE80211_CHAN_NO_6GHZ_VLP_CLIENT)', 'if (0)'), + 'six-band': ('(policy->six_channels & BIT_ULL((channel - 1) / 4))', 'true'), + } + if args.mutation: + old, new = mutations[args.mutation] + # A mutation that no longer matches the driver would test nothing. + if code.count(old) != 1: + parser.error(args.mutation + ' mutation no longer matches the driver source') + code = code.replace(old, new) source = args.output/'controls.c'; source.write_text(code) binary = args.output/'controls' subprocess.run(['cc', '-std=gnu11', '-g', '-Wall', '-Werror', '-Wno-unused-function', diff --git a/tools/asahi/run-host-tests.py b/tools/asahi/run-host-tests.py new file mode 100755 index 0000000000000..bcc42fe042c0f --- /dev/null +++ b/tools/asahi/run-host-tests.py @@ -0,0 +1,250 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only +"""Run every Aurora host test: tools/asahi and the GPU/display selftests. + +Each tools/asahi test runs under `python3 -I` with its own output directory, +once as a control and once per mutation or old-code negative control listed +below. A mutation run is only accepted as a kill when the test reports one +(an assertion, an abort or a panic of the built control), so a mutant that +merely stops compiling is a failure, not a pass. + +Every tools/asahi/*-tests.{py,rs,sh} file must appear here. A new test that +is not registered fails the run, so it cannot be silently skipped. + +Negative controls read older revisions with `git show`, so the checkout needs +the commits (a full or treeless clone, not a depth-1 one). + +Usage: tools/asahi/run-host-tests.py [--jobs N] [--work DIR] [--only SUBSTR] +""" +import argparse +import concurrent.futures +import os +import re +import shlex +import subprocess +import sys +import tempfile +import time +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[2] +ASAHI = ROOT / 'tools/asahi' +GPU = ROOT / 'tools/testing/selftests/drivers/gpu' + +PASS = 'pass' +KILL = 'kill' + +# A killed mutant shows the failing control: a C assertion or abort (-6), or +# a Rust panic (exit 101). A Python traceback means the harness itself broke. +KILL_SIGN = re.compile(r'Assertion|": -6\b|"exit": 101\b|panicked at') +BROKEN_SIGN = re.compile(r'Traceback \(most recent call last\)') + + +def out(flag='--out'): + return [flag, '{out}'] + + +def mutations(flag, names, base, expect=KILL): + return [(base + [flag, n], expect) for n in names] + + +# test file -> [(arguments, expectation)]. '{out}' is a fresh directory. +ASAHI_TESTS = { + 'apple-dart-fw-tests.py': [(out(), PASS)], + 'apple-dart-range-tests.py': [([], PASS)], + 'atc-usb3-tests.py': [(out(), PASS), (out() + ['--mutations'], PASS)], + 'avd-av1-tail-tests.py': [(out(), PASS)] + + [(out() + ['--case', c], PASS) for c in ('copy', 'seed', 'aligned')] + + mutations('--mutation', ('round-up', 'cpu-unaligned', 'seg-cpu-unaligned', + 'seg-any-size', 'ref-index-upper', 'frame-width-unchecked'), out()), + 'avd-codec-tail-tests.py': [(out('--output'), PASS), (out('--output') + ['--mutations'], PASS)], + # The old revision is the one before the J493 hardware-table ID match. + 'bcm4377-id-tests.py': [(out() + ['--old-ref', '87ddae1397f6f70817cb57df76477a86bdf96424'], PASS)], + # These mutations are caught inside the test, which then exits 0. + 'bcm4377-resume-tests.py': [(out(), PASS)] + + mutations('--mutation', ('epoch', 'close', 'producer-fence', 'active-fence', 'board'), out(), PASS), + 'brcmf-assoc-tests.py': [(out('--output'), PASS)] + + mutations('--mutation', ('short-ie', 'header-length', 'skb-capacity', 'cmplt-header'), out('--output')), + 'brcmf-bss-tests.py': [(out(), PASS)], + 'brcmf-pcie-dma-tests.py': [(out('--output'), PASS)] + + mutations('--mutation', ('narrow-stream', 'ignore-aperture', 'revision-wide'), out('--output')), + 'brcmf-roam-tests.py': [(out(), PASS)], + 'brcmfmac-scan-timeout-tests.py': [(out('--output'), PASS)], + 'clpc-gpu-demand-tests.py': [([], PASS)], + # The old policy always disabled compute preemption. + 'compute-preempt-tests.py': [(out(), PASS), + (out() + ['--old-ref', '6dbfbcb21eb09d07ae59037a1ceca8cc3500841f'], PASS)], + 'dcp14-backlight-tests.py': [([], PASS)], + 'dcp14-panel-tests.py': [([], PASS)], + 'devcoredump-init-tests.py': [([], PASS)], + 'display26-tests.py': [([], PASS)], + # The old revision publishes the fd before copy_to_user() succeeds. + 'dma-heap-fd-tests.py': [(out() + ['--old-ref', 'cb732d027aa18e1fcf9d2797f47d20b179ebc59c'], PASS)], + 'g15-input-error-tests.py': [(out('--output'), PASS), (out('--output') + ['--mutations'], PASS), + (out('--output') + ['--ref', 'ede99b3c1990b4a4a8c045fdf627a2bd0f14446a'], PASS)], + 'g15-maintained-handoff-tests.py': [(out('--output'), PASS), (out('--output') + ['--mutations'], PASS)], + 'g15-pb-growth-tests.py': [(out('--output'), PASS), (out('--output') + ['--mutations'], PASS)], + 'g15-probe-init-tests.py': [(out('--output'), PASS), (out('--output') + ['--mutations'], PASS)], + 'g15-queue-credit-tests.py': [(out('--output'), PASS), (out('--output') + ['--mutations'], PASS)], + 'g15-retention-tests.py': [(out('--output'), PASS), (out('--output') + ['--mutations'], PASS)], + 'g15-stats-drain-tests.py': [(out('--output'), PASS), (out('--output') + ['--mutations'], PASS)], + 'g15-zero-words-tests.py': [([], PASS), (['--mutations'], PASS)], + 'g17-admission-deadline-tests.py': [([], PASS)], + 'g17-admission-order-tests.py': [([], PASS)], + 'g17-capacity-fault-tests.py': [([], PASS)], + 'g17-compute-constructor-tests.py': [([], PASS)], + 'g17-compute-deadline-tests.py': [([], PASS)], + 'g17-compute-growth-deadline-tests.py': [([], PASS)], + 'g17-firmware-selection-tests.py': [([], PASS)], + 'g17-growth-gate-tests.py': [([], PASS)], + 'g17-growth-tests.py': [([], PASS)], + 'g17-owner-release-order-tests.py': [([], PASS)], + 'g17-pool-release-tests.py': [([], PASS)], + 'g17-resource-tests.py': [([], PASS)], + 'g17-shared-pool-tests.py': [([], PASS)], + 'g17-uninstalled-cleanup-tests.py': [([], PASS)], + 'g17-unpublished-tests.py': [([], PASS)], + 'mt7932-association-events-tests.py': [(out(), PASS)], + 'mt7932-connect-result-tests.py': [(out('--output'), PASS)], + 'mt7932-owe-retry-tests.py': [(out(), PASS)], + 'mt7932-policy-6ghz-tests.py': [([], PASS)], + 'neo-auxiliary-tests.py': [(out('--output'), PASS), (out('--output') + ['--mutation'], KILL), + (out('--output') + ['--old'], KILL)], + 'neo-stability-tests.py': [(out('--output'), PASS)] + + mutations('--mutation', ('sep-syscore', 'wake-irq', 'rx-suspend', 'stale-policy', + 'usb-rpm', 'vlp-client', 'six-band'), out('--output')) + + [(out('--output') + ['--old-usb'], KILL), (out('--output') + ['--old-regulatory'], KILL)], + 'pmp-j613-hwmon-tests.py': [([], PASS)], + 'qspi-nvram-tests.py': [(out('--output'), PASS)], + 'rtkit-oslog-tests.py': [(out(), PASS)] + + mutations('--mutate', ('admission', 'channel', 'counter', 'reset', 'buffer'), out()), + 't6030-clock-tests.py': [(out(), PASS)], + 't6030-display-admission-tests.py': [(out('--output'), PASS), (out('--output') + ['--mutations'], PASS)], + 't6030-display-linkage-tests.py': [(out('--output') + ['--conf', '{conf}'], PASS)], + 't6030-pmp-graph-tests.py': [(out('--output'), PASS), (out('--output') + ['--mutations'], PASS)], + 't6030-pmp-supplier-tests.py': [(out('--output'), PASS), (out('--output') + ['--mutations'], PASS)], + 't6030-wiring-tests.py': [(out(), PASS), (out() + ['--old-classifier', 'tb'], PASS), + (out() + ['--old-classifier', 'dcp'], PASS)], + 't6040-calibration-tests.py': [([], PASS)], + 'tb-dp-repair-tests.py': [(out('--output'), PASS), (out('--output') + ['--mutations'], PASS)], + 'tbnet-refill-tests.py': [(out(), PASS)] + + mutations('--mutant', ('refill-count', 'no-retry', 'no-cancel', 'worker-allocation'), out()), + 'tiled-split-tests.py': [(out(), PASS), (out() + ['--mutations'], PASS)], + 'tunnel-route-tests.py': [(out(), PASS)], +} + +# Rust unit-test files built with `rustc --test`, and shell drivers. +ASAHI_RUST = ['m3-batching-tests.rs', 'm3-runtime-tests.rs', 'pmp-bootargs-tests.rs', 'pmp-power-tests.rs'] +ASAHI_SHELL = ['g17-ray-query-timeout-tests.sh'] + +# Python that runs on the host but imports its neighbours, so it keeps the +# script directory on sys.path (-E -s instead of -I). +SUITES = [ + ('apple-dcp', GPU / 'apple-dcp', ['python3', '-E', '-s', '-B', 'run.py', '--output', '{out}']), + ('asahi-g15', GPU / 'asahi-g15', ['python3', '-E', '-s', '-B', 'run.py', '--output', '{out}']), + ('asahi-m4', GPU / 'asahi-m4', ['python3', '-E', '-s', '-B', 'run-host-tests.py']), + ('apple-afkv2-gcc', GPU / 'apple-afkv2', ['sh', 'run-afkv2-tests.sh']), + ('apple-afkv2-clang', GPU / 'apple-afkv2', ['sh', 'run-afkv2-tests.sh']), + ('t8140-external-bw', GPU, ['python3', '-I', '-B', 'test_t8140_external_bw.py']), +] + + +def jobs(work, conf): + found = {p.name for p in ASAHI.iterdir() + if re.search(r'-tests\.(py|rs|sh)$', p.name) and p.name != Path(__file__).name} + known = set(ASAHI_TESTS) | set(ASAHI_RUST) | set(ASAHI_SHELL) + missing = sorted(found - known) + gone = sorted(known - found) + if missing or gone: + for name in missing: + print(f'not registered in {Path(__file__).name}: tools/asahi/{name}', file=sys.stderr) + for name in gone: + print(f'registered but missing: tools/asahi/{name}', file=sys.stderr) + sys.exit(2) + result = [] + # The suites take longest, so start them first. + for name, cwd, cmd in SUITES: + d = work / name + # Only ASan catches an unbounded legacy reply copy, so keep it on. + env = {'AFKV2_SANITIZERS': '1', 'HOSTCC': name.rsplit('-', 1)[1]} if name.startswith('apple-afkv2') else {} + result.append((name, [a.replace('{out}', str(d / 'out')) for a in cmd], cwd, PASS, d, env)) + for name, runs in ASAHI_TESTS.items(): + for index, (args, expect) in enumerate(runs): + label = name.removesuffix('.py') + ('' if not args or args == out() or args == out('--output') + else ' ' + ' '.join(a for a in args if a not in ('{out}', '--out', '--output', '{conf}', '--conf'))) + d = work / f'{name}.{index}' + cmd = ['python3', '-I', '-B', str(ASAHI / name)] + [a.replace('{out}', str(d / 'out')).replace('{conf}', str(conf)) for a in args] + result.append((label, cmd, ASAHI, expect, d, {})) + for name in ASAHI_RUST: + d = work / name + binary = d / 'tests' + cmd = ['sh', '-c', f'{os.environ.get("RUSTC", "rustc")} --edition=2021 --test {shlex.quote(str(ASAHI / name))} ' + f'-o {shlex.quote(str(binary))} && {shlex.quote(str(binary))}'] + result.append((name.removesuffix('.rs'), cmd, ASAHI, PASS, d, {})) + for name in ASAHI_SHELL: + d = work / name + result.append((name.removesuffix('.sh'), ['sh', str(ASAHI / name), str(d / 'out')], ROOT, PASS, d, {})) + return result + + +def run(job, timeout): + label, cmd, cwd, expect, d, extra = job + (d / 'tmp').mkdir(parents=True, exist_ok=True) + env = dict(os.environ, TMPDIR=str(d / 'tmp'), **extra) + start = time.monotonic() + try: + p = subprocess.run(cmd, cwd=cwd, env=env, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, + text=True, errors='replace', timeout=timeout) + rc, text = p.returncode, p.stdout + except subprocess.TimeoutExpired as e: + rc, text = 'timeout', (e.stdout or b'').decode(errors='replace') if isinstance(e.stdout, bytes) else (e.stdout or '') + took = time.monotonic() - start + (d / 'log').write_text(f'$ {shlex.join(cmd)}\n{text}\nexit: {rc}\n') + if expect == PASS: + ok = rc == 0 + why = '' if ok else f'exit {rc}' + else: + ok = rc not in (0, 'timeout') and KILL_SIGN.search(text) and not BROKEN_SIGN.search(text) + why = '' if ok else (f'mutant survived (exit {rc})' if rc == 0 else f'exit {rc} without a control failure') + return label, bool(ok), why, took, d / 'log' + + +def main(): + ap = argparse.ArgumentParser(description=__doc__.split('\n', 1)[0]) + ap.add_argument('--jobs', type=int, default=os.cpu_count() or 2) + ap.add_argument('--work', type=Path, help='keep logs and build products here (default: a temporary directory)') + ap.add_argument('--conf', type=Path, help='a built scripts/kconfig/conf (default: build one)') + ap.add_argument('--only', help='run only jobs whose name contains this text') + ap.add_argument('--timeout', type=int, default=900) + args = ap.parse_args() + + work = args.work or Path(tempfile.mkdtemp(prefix='aurora-host-tests-')) + work = work.resolve() + work.mkdir(parents=True, exist_ok=True) + conf = args.conf + if conf is None: + build = work / 'kconfig' + subprocess.run(['make', '-s', '-C', str(ROOT), f'O={build}', 'ARCH=arm64', 'allnoconfig'], + check=True, stdout=subprocess.DEVNULL) + conf = build / 'scripts/kconfig/conf' + todo = jobs(work, conf.resolve()) + if args.only: + todo = [j for j in todo if args.only in j[0]] + print(f'{len(todo)} runs, {args.jobs} at a time, logs in {work}', flush=True) + failed = [] + start = time.monotonic() + with concurrent.futures.ThreadPoolExecutor(args.jobs) as pool: + for label, ok, why, took, log in pool.map(lambda j: run(j, args.timeout), todo): + print(f'{"ok" if ok else "FAIL"} {label} ({took:.1f}s){"" if ok else ": " + why}', flush=True) + if not ok: + failed.append((label, log)) + print(f'{len(todo) - len(failed)}/{len(todo)} passed in {time.monotonic() - start:.0f}s') + for label, log in failed: + text = log.read_text(errors='replace').splitlines() + print(f'\n=== {label}: last lines of {log}') + print('\n'.join(text[-40:])) + return 1 if failed else 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/tools/asahi/t6030-pmp-graph-tests.c b/tools/asahi/t6030-pmp-graph-tests.c index be106bf7e3d1f..9f018ee94d628 100644 --- a/tools/asahi/t6030-pmp-graph-tests.c +++ b/tools/asahi/t6030-pmp-graph-tests.c @@ -22,6 +22,7 @@ typedef uint8_t u8; #define PMP_REPORT_READY 1 #define GFP_KERNEL 0 #define GENPD_FLAG_ACTIVE_WAKEUP 1 +#define GENPD_FLAG_IRQ_SAFE 2 #define MAX_PHANDLE_ARGS 4 #define EPROBE_DEFER 517 #define SZ_4K 4096 @@ -44,7 +45,7 @@ static void host_log(const char *format, ...) #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) struct device_node { const char *name, *compatible; - bool available, always_on, no_ack, deferred, uuid, soc_record; + bool available, always_on, no_ack, deferred, irq_safe, uuid, soc_record; u32 reg; int refs; struct device_node *parent, *child, *sibling, *pmp; @@ -387,6 +388,8 @@ static bool of_property_read_bool(const struct device_node *n, const char *p) return n->no_ack; if (!strcmp(p, "apple,defer-startup-ack")) return n->deferred; + if (!strcmp(p, "apple,irq-safe")) + return n->irq_safe; if (!strcmp(p, "apple,soc-device-record")) return n->soc_record; return false; @@ -620,7 +623,7 @@ static int of_platform_populate(struct device_node *n, void *a, void *b, } struct overlay_entry { u32 reg; - bool always_on, no_ack, deferred; + bool always_on, no_ack, deferred, irq_safe; }; #include "overlay.h" static void cleanup(void) @@ -669,6 +672,7 @@ static struct apple_pmp_report *setup(void) .always_on = o->always_on, .no_ack = o->no_ack, .deferred = o->deferred, + .irq_safe = o->irq_safe, .parent = &report_node, .sibling = i < 3 ? &entries[i + 1] : NULL }; @@ -727,6 +731,14 @@ int main(void) assert(regs[0x1180 / 8] == r->start->seed); assert(((struct apple_pmp_report_entry *)entry_devs[3].dev.data) ->no_ack); + /* genpd flags follow the overlay: always-on wakes, irq-safe opts in. */ + for (unsigned int i = 0; i < 4; i++) { + const struct apple_pmp_report_entry *e = entry_devs[i].dev.data; + assert(!!(e->genpd.flags & GENPD_FLAG_ACTIVE_WAKEUP) == + overlay_entries[i].always_on); + assert(!!(e->genpd.flags & GENPD_FLAG_IRQ_SAFE) == + overlay_entries[i].irq_safe); + } assert(apple_pmp_report_wait_ready(&entries[0], 30000) == 0 && enables == 1 && pmp_node.available); assert(g15_vote() == 0 && commands == 1 && enables == 1); @@ -747,6 +759,17 @@ int main(void) assert(g15_vote() == -EIO && commands == 1); cases++; cleanup(); + /* Only an entry that carries apple,irq-safe becomes an IRQ-safe domain. */ + setup(); + entries[1].irq_safe = true; + assert(apple_pmp_report_probe(&report_dev) == 0); + for (unsigned int i = 0; i < 4; i++) { + const struct apple_pmp_report_entry *e = entry_devs[i].dev.data; + assert(!!(e->genpd.flags & GENPD_FLAG_IRQ_SAFE) == + (i == 1 || overlay_entries[i].irq_safe)); + } + cases++; + cleanup(); /* Missing display acknowledgement cannot release G15, even if bound+READY. */ setup(); assert(apple_pmp_report_probe(&report_dev) == 0); diff --git a/tools/asahi/t6030-pmp-graph-tests.py b/tools/asahi/t6030-pmp-graph-tests.py index af7c849401d3b..6ba26a09c0176 100644 --- a/tools/asahi/t6030-pmp-graph-tests.py +++ b/tools/asahi/t6030-pmp-graph-tests.py @@ -30,9 +30,10 @@ def main(): entries = [] for node in re.findall(r'report@[0-9a-f]+\s*\{(.*?)\n\s*\};', overlay, re.S): reg = int(re.search(r'reg = <(0x[0-9a-f]+|[0-9]+)>', node)[1], 0) - entries.append((reg, 'apple,always-on;' in node, 'apple,no-ack;' in node, 'apple,defer-startup-ack;' in node)) + entries.append((reg, 'apple,always-on;' in node, 'apple,no-ack;' in node, 'apple,defer-startup-ack;' in node, + 'apple,irq-safe;' in node)) assert len(entries) == 4 - entry_data = ',\n'.join('{%d, %d, %d, %d}' % e for e in entries) + entry_data = ',\n'.join('{%d, %d, %d, %d, %d}' % e for e in entries) names = ['static int apple_pmp_report_bit(', 'static int apple_pmp_start_check_layout(', 'static int apple_pmp_start_describe(', 'static void apple_pmp_start_finish(', 'static bool apple_pmp_start_stopping(', 'static void apple_pmp_start_work(', @@ -81,6 +82,8 @@ def main(): ('ignore-startup-ack', '(ack & st->startup_ack) != st->startup_ack', 'false'), ('ignore-entry-ack', 'status == PMP_REPORT_READY && (ack & BIT_ULL(id))', '((void)ack, status == PMP_REPORT_READY)'), ('storage-ack-layout', 'st->ack & ~st->record_seed,', 'st->seed & ~st->record_seed,'), + ('ignore-irq-safe', 'ent->genpd.flags |= GENPD_FLAG_IRQ_SAFE;', '(void)0;'), + ('irq-safe-without-property', 'of_property_read_bool(node, "apple,irq-safe")', 'true'), ('skip-supplier-wait', 'apple_pmp_report_wait_ready(entry, msecs_to_jiffies(timeout_ms))', '((void)timeout_ms, 0)'), ] for name, old, new in controls: diff --git a/tools/testing/selftests/drivers/gpu/apple-afkv2/extract-edid.py b/tools/testing/selftests/drivers/gpu/apple-afkv2/extract-edid.py index 0d554d1c82b5d..bcced2f684cfa 100644 --- a/tools/testing/selftests/drivers/gpu/apple-afkv2/extract-edid.py +++ b/tools/testing/selftests/drivers/gpu/apple-afkv2/extract-edid.py @@ -29,12 +29,17 @@ def function(name): #define kzalloc(n,f) calloc(1,n) #define cpu_to_le64(x) (x) #define le64_to_cpu(x) (x) +#define READ_ONCE(x) (x) +#define KUNIT_STATIC_STUB_REDIRECT(...) do {} while (0) +#define SZ_2K 2048U +#define EDID_COMPACT_BUF_SIZE (EDID_LEADING_DATA_SIZE + SZ_2K) #define EDID_EXT_BLOCK_COUNT_OFFSET 0x7e #define ERR_PTR(x) ((void*)(intptr_t)(x)) #define IS_ERR(p) ((uintptr_t)(p)>(uintptr_t)-4096) static void cleanup_free(void *p) { free(*(void**)p); } #define __free(x) __attribute__((cleanup(cleanup_free))) -struct apple_epic_service { int unused; }; +struct ep { bool compact; }; +struct apple_epic_service { struct ep *ep; }; struct drm_edid { size_t size; }; struct dpavserv_copy_edid_cmd { u64 max_size;u8 pad1[24];u64 used_size;u8 pad2[8]; }; struct dpavserv_copy_edid_resp { u64 max_size;u8 pad1[24];u64 used_size;u8 pad2[8];u8 data[]; }; @@ -42,26 +47,36 @@ def function(name): static bool truncated; static const struct drm_edid *drm_edid_alloc(const void *p,size_t n) { struct drm_edid *e=malloc(sizeof(*e));(void)p;e->size=n;return e; } -static int afk_service_call(struct apple_epic_service *s,u16 group,u32 command, +/* The request asks for the buffer the transport can carry: 2 KiB on compact EPIC. */ +static size_t request_size(const struct apple_epic_service *s, bool inline_call) +{ return !inline_call && s->ep->compact ? EDID_COMPACT_BUF_SIZE : EDID_BUF_SIZE; } +static int edid_call(struct apple_epic_service *s,bool inline_call,u16 group,u32 command, const void *data,size_t data_len,size_t data_pad,void *output,size_t output_len,size_t output_pad) { const struct dpavserv_copy_edid_cmd *cmd=data; - (void)s;(void)data_pad;(void)output_pad; - assert(group==1 && command==7 && data_len==48 && cmd->max_size==EDID_BUF_SIZE); + size_t want=request_size(s,inline_call); + assert(group==1 && command==7 && data_len==48 && cmd->max_size==want && data_pad==want); + assert(output_len==48+want && output_pad==0); assert(output_len>=sizeof(fixture));memcpy(output,fixture,sizeof(fixture));return 0; } static int afk_service_call_len(struct apple_epic_service *s,u16 group,u32 command, const void *data,size_t data_len,size_t data_pad,void *output,size_t output_len, size_t output_pad,size_t *received) -{ int ret=afk_service_call(s,group,command,data,data_len,data_pad,output,output_len,output_pad); +{ int ret=edid_call(s,true,group,command,data,data_len,data_pad,output,output_len,output_pad); *received=sizeof(fixture)-(truncated?1:0);return ret; } +/* Legacy EPIC: the reply length the firmware reported. */ +static int afk_service_call_with_reply_len(struct apple_epic_service *s,u16 group,u32 command, + const void *data,size_t data_len,size_t data_pad,void *output,size_t output_len, + size_t output_pad,size_t *reply_len) +{ int ret=edid_call(s,false,group,command,data,data_len,data_pad,output,output_len,output_pad); + *reply_len=sizeof(fixture)-(truncated?1:0);return ret; } static void fix_checksum(u8 *p) { unsigned int i;u8 sum=0;p[127]=0;for(i=0;i<127;i++)sum+=p[i];p[127]=-sum; } ''' suffix = r''' int main(void) { - struct apple_epic_service service={0};const struct drm_edid *e; + struct ep ep={0};struct apple_epic_service service={.ep=&ep};const struct drm_edid *e; u8 *base=fixture+56,*extension=base+128; afkv2_put64(fixture,EDID_BUF_SIZE);afkv2_put64(fixture+32,8+256); memset(base+1,255,6);base[126]=1;fix_checksum(base); @@ -72,8 +87,23 @@ def function(name): e=dcpavserv_read_edid_inline(&service);assert(!IS_ERR(e) && e->size==256);free((void*)e); truncated=true; e=dcpavserv_read_edid_inline(&service);assert(IS_ERR(e)); - e=dcpavserv_read_edid_legacy(&service);assert(!IS_ERR(e));free((void*)e); - puts("PASS: actual legacy EDID checksum tolerance preserved; inline checksum/actual-length validation enforced"); + /* The legacy reader now bounds the EDID by the reported reply length too. */ + e=dcpavserv_read_edid_legacy(&service);assert(IS_ERR(e)); + truncated=false; + /* Its max_size echo must match the request, and the EDID must be whole blocks. */ + afkv2_put64(fixture,EDID_BUF_SIZE-1); + e=dcpavserv_read_edid_legacy(&service);assert(IS_ERR(e)); + afkv2_put64(fixture,EDID_BUF_SIZE);afkv2_put64(fixture+32,8+200); + e=dcpavserv_read_edid_legacy(&service);assert(IS_ERR(e)); + afkv2_put64(fixture+32,8+128); + e=dcpavserv_read_edid_legacy(&service);assert(IS_ERR(e)); /* base block announces one extension */ + afkv2_put64(fixture+32,8+256); + /* Compact EPIC asks for 2 KiB and expects that size echoed. */ + ep.compact=true; + e=dcpavserv_read_edid_legacy(&service);assert(IS_ERR(e)); + afkv2_put64(fixture,EDID_COMPACT_BUF_SIZE); + e=dcpavserv_read_edid_legacy(&service);assert(!IS_ERR(e) && e->size==256);free((void*)e); + puts("PASS: actual legacy EDID checksum tolerance preserved with reply-length, echo and block bounds (legacy and compact); inline checksum/actual-length validation enforced"); } ''' pathlib.Path(sys.argv[2]).write_text(prefix + function("dcpavserv_read_edid_legacy") + function("dcpavserv_read_edid_inline") + suffix) diff --git a/tools/testing/selftests/drivers/gpu/apple-afkv2/extract-hpd.py b/tools/testing/selftests/drivers/gpu/apple-afkv2/extract-hpd.py index 6957d92f390e2..b21cba6c7665c 100644 --- a/tools/testing/selftests/drivers/gpu/apple-afkv2/extract-hpd.py +++ b/tools/testing/selftests/drivers/gpu/apple-afkv2/extract-hpd.py @@ -1,6 +1,9 @@ #!/usr/bin/env python3 # SPDX-License-Identifier: GPL-2.0-only OR MIT -"""Exercise the production DPTX HPD request with mocked service-call replies.""" +"""Exercise the production DPTX HPD request with mocked service-call replies. + +Covers the legacy, AFK-v2 and compact EPIC replies and the 14.7 AV client close. +""" import pathlib import re import sys @@ -44,12 +47,19 @@ def function(source, name): #define EPIC_SUBTYPE_STD_SERVICE 0xc0 #define dev_warn(...) do {} while (0) #define dev_dbg(...) do {} while (0) +#define READ_ONCE(x) (x) +static u32 get_unaligned_le32(const void *p) +{ + const u8 *b = p; + + return b[0] | b[1] << 8 | b[2] << 16 | (u32)b[3] << 24; +} struct epic_service_call { u8 pad0[2]; u16 group; u32 command, data_len, magic; u8 pad1[48]; }; struct dcp { void *dev; }; struct ops { const char *name; }; -struct ep { struct dcp *dcp; int endpoint; bool inline_v2; }; +struct ep { struct dcp *dcp; int endpoint; bool inline_v2; bool compact; }; struct apple_epic_service { struct ep *ep; struct ops *ops; u32 channel; }; int afk_service_call_timeout_len(struct apple_epic_service *service, u16 group, u32 command, const void *data, size_t data_len, size_t data_pad, void *output, @@ -67,7 +77,16 @@ def function(source, name): */ static bool wire_v2; static size_t declared, arrived; -static u32 echo_flip; +static u32 echo_flip, compact_status; +/* An HPD low closes the 14.7 external AV client first; the stub counts it. */ +static struct dcp *expected_dcp; +static unsigned int v14_closes, closes_at_send; +/* Kept even if a mutant drops the call, so the count check is what fails. */ +__attribute__((unused)) static void dcpavserv_v14_close(struct dcp *dcp) +{ + assert(dcp == expected_dcp); + v14_closes++; +} static int afk_send_command_timeout_len(struct apple_epic_service *s, u8 type, const void *payload, size_t payload_len, void *output, size_t output_len, u32 *retcode, unsigned int timeout_ms, size_t *received) @@ -76,15 +95,21 @@ def function(source, name): u8 *body = (u8 *)output + sizeof(*call); u32 echo; - (void)s; (void)payload; (void)timeout_ms; + (void)payload; (void)timeout_ms; assert(type == EPIC_SUBTYPE_STD_SERVICE && payload_len == output_len); assert(output_len == sizeof(*call) + 32 && call->data_len == 32); assert(call->group == 8 && call->command == 8); + closes_at_send = v14_closes; if (wire_v2 && !received && arrived < output_len - sizeof(*call)) return -EMSGSIZE; memcpy(&echo, body + 16, sizeof(echo)); echo ^= echo_flip; memcpy(body + 16, &echo, sizeof(echo)); + /* Compact EPIC answers with an IOReturn status in the first word. */ + if (s->ep->compact) { + for (int i = 0; i < 4; i++) + body[i] = compact_status >> (8 * i); + } call->data_len = declared; *retcode = 0; if (received) @@ -94,12 +119,24 @@ def function(source, name): ''' suffix = r''' static int failures; +static struct ep *the_ep; static void expect(bool ok, const char *what, bool hpd, int ret) { - printf("%s %s: %s hpd=%d declared=%zu arrived=%zu ret=%d\n", ok ? "ok" : "FAIL", - wire_v2 ? "afk-v2" : "legacy", what, hpd, declared, arrived, ret); + printf("%s %s%s: %s hpd=%d declared=%zu arrived=%zu ret=%d\n", ok ? "ok" : "FAIL", + wire_v2 ? "afk-v2" : "legacy", the_ep->compact ? " compact" : "", what, hpd, + declared, arrived, ret); failures += !ok; } +/* Every request closes the AV client exactly when it lowers HPD, and before it is sent. */ +static int set_hpd(struct apple_epic_service *service, int hpd) +{ + unsigned int before = v14_closes; + int ret = dptxport_set_hpd_timeout(service, hpd, 1000); + + expect(v14_closes == before + !hpd && closes_at_send == before + !hpd, + "AV client closed before an HPD low only", hpd, ret); + return ret; +} int main(void) { static const size_t lengths[] = { 0, 4, 8, 12, 16, 20, 32, 36, 65535 }; @@ -108,6 +145,9 @@ def function(source, name): struct ep ep = { .dcp = &dcp }; struct apple_epic_service service = { .ep = &ep, .ops = &ops, .channel = 1 }; + the_ep = &ep; + expected_dcp = &dcp; + for (int v2 = 0; v2 < 2; v2++) { wire_v2 = v2; for (size_t i = 0; i < sizeof(lengths) / sizeof(lengths[0]); i++) { @@ -115,7 +155,7 @@ def function(source, name): int ret; declared = lengths[i]; arrived = 32; echo_flip = 0; - ret = dptxport_set_hpd_timeout(&service, hpd, 1000); + ret = set_hpd(&service, hpd); if (declared < sizeof(struct dcpdptx_hotplug_cmd)) expect(ret < 0, "short reply refused", hpd, ret); else if (declared > 32) @@ -128,19 +168,38 @@ def function(source, name): int ret; declared = 20; arrived = 32; echo_flip = 1; - ret = dptxport_set_hpd_timeout(&service, hpd, 1000); + ret = set_hpd(&service, hpd); expect(ret == -EINVAL, "wrong echo refused", hpd, ret); if (!wire_v2) continue; declared = 20; arrived = 16; echo_flip = 0; - ret = dptxport_set_hpd_timeout(&service, hpd, 1000); + ret = set_hpd(&service, hpd); expect(ret < 0, "reply cut short in transit refused", hpd, ret); } + /* Compact EPIC: the status word decides, there is no echo to compare. */ + ep.compact = true; + for (int hpd = 0; hpd < 2; hpd++) { + int ret; + + declared = 20; arrived = 32; echo_flip = 0; compact_status = 0; + ret = set_hpd(&service, hpd); + expect(ret == 0, "zero status accepted", hpd, ret); + echo_flip = 1; + ret = set_hpd(&service, hpd); + expect(ret == 0, "echo word ignored", hpd, ret); + echo_flip = 0; compact_status = 0xe00002c2; + ret = set_hpd(&service, hpd); + expect(ret == -EIO, "firmware error status refused", hpd, ret); + compact_status = 0x00000100; + ret = set_hpd(&service, hpd); + expect(ret == -EIO, "any nonzero status byte refused", hpd, ret); + } + ep.compact = false; compact_status = 0; } printf("%d HPD reply checks failed\n", failures); if (failures) return 1; - puts("PASS: extracted production DPTX HPD request: short, oversized, wrong-echo and complete replies, legacy and AFK-v2"); + puts("PASS: extracted production DPTX HPD request: short, oversized, wrong-echo and complete replies, legacy, AFK-v2 and compact status, AV client close on HPD low"); return 0; } ''' diff --git a/tools/testing/selftests/drivers/gpu/apple-afkv2/extract-send.py b/tools/testing/selftests/drivers/gpu/apple-afkv2/extract-send.py index 70dbd5d3a69f4..eb55dd281456f 100644 --- a/tools/testing/selftests/drivers/gpu/apple-afkv2/extract-send.py +++ b/tools/testing/selftests/drivers/gpu/apple-afkv2/extract-send.py @@ -1,25 +1,27 @@ #!/usr/bin/env python3 # SPDX-License-Identifier: GPL-2.0-only OR MIT -"""Compile the real command sender against synthetic ring and DMA operations.""" +"""Compile the real command sender against synthetic ring and DMA operations. + +Covers AFK-v2, legacy reply-length and compact EPIC requests. +""" import pathlib import re import sys source = pathlib.Path(sys.argv[1]).read_text() -match = re.search(r"static int afk_send_command_timeout_len\([^;]*?\)\n\{", source) -if not match: - raise RuntimeError("production command sender not found") -start, end, depth = match.start(), match.end(), 1 -while depth: - depth += (source[end] == "{") - (source[end] == "}") - end += 1 -reply_match = re.search(r"static void afk_recv_handle_reply\([^;]*?\)\n\{", source) -if not reply_match: - raise RuntimeError("production reply completer not found") -reply_end, reply_depth = reply_match.end(), 1 -while reply_depth: - reply_depth += (source[reply_end] == "{") - (source[reply_end] == "}") - reply_end += 1 + + +def function(pattern, what): + match = re.search(r"^" + pattern + r"\([^;]*?\)\n\{", source, re.M) + if not match: + raise RuntimeError("production " + what + " not found") + end, depth = match.end(), 1 + while depth: + depth += (source[end] == "{") - (source[end] == "}") + end += 1 + return source[match.start():end] + "\n" + + prefix = r''' #undef NDEBUG #include @@ -27,7 +29,10 @@ #include #include #include "afkv2-codec.h" +#include "afk_reply.h" #define SZ_1M 1048576U +#define U32_MAX 0xffffffffU +#define MSEC_PER_SEC 1000U #define GFP_KERNEL 0 #define MAX_PENDING_CMDS 16 #define AFKV2_INTERFACES (1U << 16) @@ -55,10 +60,13 @@ struct cmd_info { u16 tag; u8 type; size_t reply_length; void *rxbuf,*txbuf; dma_addr_t rxbuf_dma,txbuf_dma; size_t rxlen,txlen; u32 retcode; + size_t reply_len; bool reply_len_valid; bool done,free_on_ack; int *completion; }; -struct dcp { void *dev; bool quiescing; }; -struct apple_dcp_afkep { struct dcp *dcp; bool wire_v2,inline_v2; int endpoint,lock;u8 *command_tags; }; +struct dcp { void *dev; bool quiescing,external; void *rtk; }; +struct afk_ringbuffer { bool ready; }; +struct apple_dcp_afkep { struct dcp *dcp; bool wire_v2,inline_v2,compact; int endpoint,lock;u8 *command_tags; + struct afk_ringbuffer txbfr,rxbfr; }; struct ops { const char *name; }; struct apple_epic_service { struct apple_dcp_afkep *ep; struct ops *ops; unsigned int channel; unsigned long cmd_map[1]; @@ -68,6 +76,25 @@ static unsigned int frees, sends; static int wire_fail, timeout, reply_status; static size_t owned_reply_size=32888; +/* Legacy replies can go through the real completer, as from the ring. */ +static bool real_reply; +static u32 legacy_rxlen; +static void afk_recv_handle_reply(struct apple_dcp_afkep *ep,u32 channel,u16 tag,void *payload,size_t payload_size); +/* Compact EPIC goes inline through the ring; this mock stands for that transport. */ +static bool compact_echo; +static u32 compact_reply,compact_request; +static size_t compact_copied; +static unsigned int compact_sends; +static int afk_send_compact_command(struct apple_epic_service *s,u8 type,const void *payload,size_t payload_len, + void *output,size_t output_len,u32 request_status,u32 *retcode,size_t *copied,unsigned long timeout) +{ + (void)timeout; + assert(s==active && type==0xc0 && payload_len==112 && ((const u8 *)payload)[0]==0x5a && output_len==32888); + compact_sends++;compact_request=request_status; + *retcode=compact_echo?request_status:compact_reply; + memset(output,0x33,compact_copied);*copied=compact_copied; + return 0; +} static struct apple_epic_service *afk_service_get(struct apple_epic_service *s) { if(!s->enabled) return NULL; s->users++; return s; } static void afk_service_put(struct apple_epic_service *s) { assert(s->users); s->users--; } @@ -100,6 +127,14 @@ } if(wire_fail)return -ENOSPC; if(timeout)return 0; + if(real_reply) { + struct epic_cmd reply={.retcode=reply_status,.rxlen=legacy_rxlen}; + assert(!ep->wire_v2); + memset(c->rxbuf,0x77,legacy_rxlenrxlen?legacy_rxlen:c->rxlen); + afk_recv_handle_reply(ep,channel,tag,&reply,sizeof(reply)); + assert(c->done && c->reply_len_valid && c->reply_len==legacy_rxlen && *c->completion==1); + return 0; + } c->reply_length=ep->inline_v2?4:owned_reply_size;c->retcode=reply_status; memset(c->rxbuf,0x77,c->reply_length);c->done=true;*c->completion=1; return 0; @@ -139,6 +174,39 @@ ep.wire_v2=false;owned_reply_size=4; assert(!afk_send_command_timeout_len(&s,0xc0,request,112,output,32888,&status,1000,&received)); assert(received==32888 && s.users==1); + /* Legacy EPIC reply lengths: bounded by the buffer, rest of the output zeroed. */ + real_reply=true;legacy_rxlen=4;size_t rlen=99;memset(output,0x19,32888); + assert(!afk_send_command_with_reply_len(&s,0xc0,request,112,output,32888,&status,&rlen)); + assert(rlen==4 && status==0 && output[0]==0x77 && output[3]==0x77 && !output[4] && !output[32887]); + assert(!s.cmd_map[0] && s.users==1); + legacy_rxlen=32889;rlen=99; + assert(afk_send_command_with_reply_len(&s,0xc0,request,112,output,32888,&status,&rlen)==-EMSGSIZE); + assert(rlen==0 && !s.cmd_map[0] && s.users==1); + assert(afk_send_command_with_reply_len(&s,0xc0,request,112,output,32888,&status,NULL)==-EINVAL); + unsigned int before_sends=sends; + assert(afk_send_command_with_reply_len(&s,0xc0,request,(size_t)U32_MAX+1,output,32888,&status,&rlen)==-EOVERFLOW); + /* AFK-v2 reports reply lengths its own way and refuses this interface. */ + ep.wire_v2=true; + assert(afk_send_command_with_reply_len(&s,0xc0,request,112,output,32888,&status,&rlen)==-EOPNOTSUPP); + assert(sends==before_sends && s.users==1 && !s.cmd_map[0]); + real_reply=false; + /* Compact EPIC: success is the reply echoing the request's own status. */ + ep.compact=true;unsigned int before_frees=frees;status=99;received=99; + compact_echo=true;compact_copied=20; + assert(!afk_send_command_timeout_len(&s,0xc0,request,112,output,32888,&status,1000,&received)); + assert(status==0 && received==20 && output[0]==0x33 && compact_sends==1); + u32 first=compact_request; + compact_echo=false;compact_reply=0xe00002c2; + assert(!afk_send_command_timeout_len(&s,0xc0,request,112,output,32888,&status,1000,&received)); + assert(status==0xe00002c2 && compact_request==((((first>>8)+1)&0xff)<<8)); + compact_reply=0; + assert(!afk_send_command_timeout_len(&s,0xc0,request,112,output,32888,&status,1000,&received)); + assert(compact_request && status==~0U); + compact_echo=true;compact_copied=8;rlen=99; + assert(!afk_send_command_with_reply_len(&s,0xc0,request,112,output,32888,&status,&rlen) && rlen==8 && !status); + assert(compact_sends==4 && sends==before_sends && frees==before_frees && s.users==1 && !s.cmd_map[0]); + ep.compact=false; + dcp.external=true;ep.txbfr.ready=ep.rxbfr.ready=true; ep.wire_v2=true;owned_reply_size=32888;timeout=1; assert(afk_send_command_timeout_len(&s,0xc0,request,112,output,32888,&status,1000,&received)==-ETIMEDOUT); assert(s.cmd_map[0] && s.cmds[0].free_on_ack && !s.cmds[0].completion); @@ -155,14 +223,13 @@ s.enabled=true;s.torndown=false;timeout=0; assert(!afk_send_command_timeout_len(&s,0xc0,request,112,output,32888,&status,1000,&received)); assert(!s.cmd_map[0] && s.users==1);free(output); - puts("PASS: extracted production sender, 128-byte command envelope / 32888-byte reply, short OOB, strict/legacy callers and actual late-ACK ownership"); + puts("PASS: extracted production sender, 128-byte command envelope / 32888-byte reply, short OOB, strict/legacy callers, legacy reply lengths, compact status and actual late-ACK ownership"); } ''' -tag_match = re.search(r"static u16 afk_command_tag\([^;]*?\)\n\{", source) -if not tag_match: - raise RuntimeError("production interface tag allocator not found") -tag_end, tag_depth = tag_match.end(), 1 -while tag_depth: - tag_depth += (source[tag_end] == "{") - (source[tag_end] == "}") - tag_end += 1 -pathlib.Path(sys.argv[2]).write_text(prefix + source[tag_match.start():tag_end] + source[start:end] + source[reply_match.start():reply_end] + suffix) +bodies = [function(r"static u32 afk_compact_next_status", "compact status allocator"), + function(r"static u16 afk_command_tag", "interface tag allocator"), + function(r"static int afk_send_command_common", "command sender"), + function(r"static int afk_send_command_timeout_len", "length-aware sender"), + function(r"int afk_send_command_with_reply_len", "legacy reply-length sender"), + function(r"static void afk_recv_handle_reply", "reply completer")] +pathlib.Path(sys.argv[2]).write_text(prefix + "".join(bodies) + suffix) diff --git a/tools/testing/selftests/drivers/gpu/apple-afkv2/host-include/linux/errno.h b/tools/testing/selftests/drivers/gpu/apple-afkv2/host-include/linux/errno.h index 23e11801f1e76..e7a1050305b68 100644 --- a/tools/testing/selftests/drivers/gpu/apple-afkv2/host-include/linux/errno.h +++ b/tools/testing/selftests/drivers/gpu/apple-afkv2/host-include/linux/errno.h @@ -8,3 +8,5 @@ #define ENODEV 19 #define ETIMEDOUT 110 #define ENOMEM 12 +#define EOVERFLOW 75 +#define EOPNOTSUPP 95 diff --git a/tools/testing/selftests/drivers/gpu/asahi-m4/abi.py b/tools/testing/selftests/drivers/gpu/asahi-m4/abi.py index 38c6d7806eabd..7b9bbce635e6e 100644 --- a/tools/testing/selftests/drivers/gpu/asahi-m4/abi.py +++ b/tools/testing/selftests/drivers/gpu/asahi-m4/abi.py @@ -104,7 +104,9 @@ def c_layout(header, bindings, output, tag): lines += [f'printf("{key}=%llu\\n", (unsigned long long)({expr}));' for key, expr in keys] source.write_text('\n'.join(lines + ['return 0;', '}']) + '\n') return run_measurements(shlex.split(os.environ.get('CC', 'cc')) + - ['-std=gnu11', '-Wall', '-Werror', '-D__user='], + ['-std=gnu11', '-Wall', '-Werror', '-D__user=', + # Fall back to the tree for UAPI headers the host lacks. + '-idirafter', str(header.parents[1])], source, output / tag)