diff --git a/tools/asahi/avd-av1-tail-tests.py b/tools/asahi/avd-av1-tail-tests.py index 8a36298013af61..a2655560b5ea2c 100644 --- a/tools/asahi/avd-av1-tail-tests.py +++ b/tools/asahi/avd-av1-tail-tests.py @@ -15,7 +15,7 @@ def function(source, name): - match = re.search(r'^(?:static )?(?:int|void)\s+' + name + r'\(', source, re.M) + match = re.search(r'^(?:static )?(?:int|void|u64)\s+' + name + r'\(', source, re.M) if not match: raise ValueError(name) start = match.start() @@ -46,6 +46,9 @@ def structure(source, name): typedef uint8_t u8; typedef uint16_t u16; typedef uint32_t u32; +typedef uint64_t u64; +typedef int8_t s8; +#define U64_MAX UINT64_MAX 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)) @@ -73,9 +76,10 @@ def structure(source, name): static struct v4l2_ctrl_av1_film_grain grain; static struct v4l2_ctrl controls[4]; static unsigned int lookup_calls, preamble_calls; +static uint64_t last_timestamp; static void *vb2_plane_vaddr(struct vb2_buffer *b,unsigned int p){assert(p==0);return b->memory;} 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 struct avd_decoded_buffer *avd_get_ref_buf(struct avd_ctx *ctx,struct vb2_v4l2_buffer *dst,uint64_t timestamp){(void)ctx;last_timestamp=timestamp;lookup_calls++;return timestamp==1000?reference:vb2_to_avd_decoded_buf(&dst->vb2_buf);} static void avd_run_preamble(struct avd_ctx *ctx,struct avd_run *run){(void)ctx;(void)run;preamble_calls++;} static struct v4l2_ctrl *v4l2_ctrl_find(int *h,unsigned int id){ (void)h; @@ -110,7 +114,7 @@ def structure(source, name): 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);} avd_av1_set_prob(&ctx,&run); - assert(lookup_calls==(mode?0:1));assert(memcmp(probabilities,&expected,sizeof(expected))==0); + assert(lookup_calls==(mode?0:1));if(!mode)assert(last_timestamp==1000);assert(memcmp(probabilities,&expected,sizeof(expected))==0); assert(memcmp(dst.base.vb.vb2_buf.memory+dst_offset,&expected,sizeof(expected))==0); for(size_t i=0;i=compressed);assert(cdf+sizeof(struct avd_av1_cdfs)<=co);assert(co+color<=length); assert(cdf==aligned_cdf(length,color));assert(co==cdf+9216); } +#ifdef HAVE_AVD_AV1_REF_TS +/* Not periodic: a read at a wrong offset, even a multiple of 256, sees other bytes. */ +static unsigned char fill(size_t i){uint32_t x=(uint32_t)i*2654435761u;x^=x>>15;x*=2246822519u;x^=x>>13;return (unsigned char)(x>>11);} +static void invalid_primary_index(signed char idx){ + const size_t len=7000064,color=138240,offset=aligned_cdf(len,color); + struct avd_decoded_buffer dst={0};struct avd_av1_ctx av1={0};struct avd_ctx ctx={.priv=&av1};struct avd_av1_run run={0}; + unsigned char probabilities[sizeof(struct avd_av1_cdfs)],expected[sizeof(struct avd_av1_cdfs)]; + dst.base.vb.vb2_buf.planes[0].length=len;dst.base.vb.vb2_buf.memory=malloc(len);assert(dst.base.vb.vb2_buf.memory);dst.av1.color_size=color; + for(size_t i=0;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;} @@ -137,6 +165,9 @@ def structure(source, name): for(unsigned int mode=1;mode<=4;mode++){inheritance(residue,mode,NULL,true);cases++;} reservation(1920,1080,6425727,residue);cases++;reservation(1281,721,3000017,residue);cases++; } +#ifdef HAVE_AVD_AV1_REF_TS + invalid_primary_index(-1);invalid_primary_index(-128);invalid_primary_index(8);invalid_primary_index(127);cases+=4; +#endif printf("%u production controls PASS\n",cases);return 0; } ''' @@ -148,14 +179,15 @@ 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', 'fallback-offset']) 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']} + helpers = [n for n in ['avd_av1_ref_ts'] if re.search(r'^static u64 ' + n + r'\(', src, re.M)] + functions = {n: function(src, n) for n in helpers + ['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') @@ -163,11 +195,19 @@ def main(): 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' + elif args.mutation == 'fallback-offset': + read = 'vb2_plane_vaddr(&ref->base.vb.vb2_buf, 0) +' + if 'avd_av1_ref_ts' not in helpers or functions['avd_av1_set_prob'].count(read) != 1: + print('mutation fallback-offset: no avd_av1_ref_ts() or not one reference read; no mutant verdict') + raise SystemExit(0) + functions['avd_av1_set_prob'] = functions['avd_av1_set_prob'].replace(read, read + ' (ref == dst ? 1 : 0) +') 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() generated = PREFIX.replace('@ALIGNMENT@', alignment).replace('@CDF_HEADER@', header).replace('@CDF_DEFAULTS@', defaults).replace('@RUN_STRUCT@', structure(src, 'avd_av1_run')).replace('@CTX_STRUCT@', structure(src, 'avd_av1_ctx')) generated += macros + '\n' + '\n'.join(functions.values()) + TESTS + if helpers: + generated = '#define HAVE_AVD_AV1_REF_TS 1\n' + generated uapi = args.source / 'include/uapi/linux/v4l2-controls.h' (args.out / 'controls.h').write_bytes(uapi.read_bytes()) cfile = args.out / 'controls.c' @@ -178,6 +218,9 @@ def main(): build = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, env=environment) (args.out / 'build.log').write_text(build.stdout) if build.returncode: + if args.mutation: + print(f'mutation {args.mutation}: controls did not build (see build.log); no mutant verdict') + raise SystemExit(0) raise SystemExit(build.returncode) run_command = [str(exe)] if args.case != 'all': diff --git a/tools/asahi/g15-display-order-tests.py b/tools/asahi/g15-display-order-tests.py index 50e0aa48093961..a6666efdf2776b 100644 --- a/tools/asahi/g15-display-order-tests.py +++ b/tools/asahi/g15-display-order-tests.py @@ -227,7 +227,7 @@ def c_source(root): } RS_PREFIX = r''' -#![allow(dead_code, unused_variables, unused_imports, non_snake_case)] +#![allow(dead_code, unused_variables, unused_imports, unused_macros, non_snake_case)] use std::cell::RefCell; use std::marker::PhantomData; #[derive(Copy, Clone, PartialEq, Eq, Debug)] @@ -236,13 +236,21 @@ def c_source(root): type Result = std::result::Result; const ENODEV: Error = Error(-19); const EINVAL: Error = Error(-22); +const EIO: Error = Error(-5); const EPROBE_DEFER: Error = Error(-517); macro_rules! dev_err { ($($args:tt)*) => {{}}; } macro_rules! dev_info { ($($args:tt)*) => {{}}; } -struct State { board: bool, machine: bool, pmp: i32, display: i32 } +macro_rules! dev_warn { ($($args:tt)*) => {{ warned(); }}; } +struct State { board: bool, machine: bool, pmp: i32, display: i32, sleep_hold: bool, asc_fail: bool } thread_local! { static CALLS: RefCell> = const { RefCell::new(Vec::new()) }; - static STATE: RefCell = const { RefCell::new(State { board: true, machine: true, pmp: 0, display: 0 }) }; + static STATE: RefCell = const { RefCell::new(State { board: true, machine: true, pmp: 0, display: 0, sleep_hold: true, asc_fail: false }) }; + static HOLD: RefCell> = const { RefCell::new(None) }; + static WARNINGS: RefCell = const { RefCell::new(0) }; +} +fn warned() { WARNINGS.with(|w| *w.borrow_mut() += 1); } +mod params { + pub fn sleep_hold() -> bool { super::STATE.with(|s| s.borrow().sleep_hold) } } fn call(name: &'static str) { CALLS.with(|c| c.borrow_mut().push(name)); } mod device { @@ -276,7 +284,9 @@ def c_source(root): pub struct Regs; impl Regs { pub fn new(_: &super::platform::Device) -> super::Result { super::call("regs"); Ok(Self) } - pub fn asc_control(&self) -> super::Result { Ok(0) } + pub fn asc_control(&self) -> super::Result { + if super::STATE.with(|s| s.borrow().asc_fail) { Err(super::EIO) } else { Ok(0) } + } } } mod glue { @@ -286,7 +296,7 @@ def c_source(root): pub unsafe fn asahi_g15_pmp_prepare(_: *mut core::ffi::c_void) -> core::ffi::c_int { call("pmp"); STATE.with(|s| s.borrow().pmp) } pub unsafe fn asahi_g15_display_prepare(_: *mut core::ffi::c_void) -> core::ffi::c_int { call("display"); STATE.with(|s| s.borrow().display) } pub fn dma_coherent(_: &super::device::Device) -> bool { call("coherent"); false } - pub fn hold_power(_: &super::device::Device) { call("hold"); } +@HOLD_POWER@ } struct Admitted { regs: regs::Regs, adt: adt::GpuAdt, board_images: u32 } ''' @@ -297,12 +307,15 @@ def c_source(root): const TAIL: [&str; 6] = ["adt", "identity", "coherent", "board_images", "regs", "hold"]; fn run(cfg: &'static Config, machine: bool, pmp: i32, display: i32) -> (Result<()>, Vec<&'static str>) { CALLS.with(|c| c.borrow_mut().clear()); - STATE.with(|s| *s.borrow_mut() = State { board: true, machine, pmp, display }); + STATE.with(|s| *s.borrow_mut() = State { board: true, machine, pmp, display, sleep_hold: true, asc_fail: false }); + HOLD.with(|h| *h.borrow_mut() = None); + WARNINGS.with(|w| *w.borrow_mut() = 0); let pdev = platform::Device(device::Device, Core(PhantomData)); let ret = admit(&pdev, &AsahiDevice, cfg).map(|_| ()); (ret, CALLS.with(|c| c.borrow().clone())) } fn with_tail(head: &[&'static str]) -> Vec<&'static str> { head.iter().chain(TAIL.iter()).copied().collect() } +@SLEEP_HOLD_RUN@ fn main() { assert_eq!(run(&T6031, true, 0, -517), (Err(EPROBE_DEFER), vec!["machine", "display"]), "T6031 defers before firmware"); assert_eq!(run(&T6031, true, 0, -19), (Err(ENODEV), vec!["machine", "display"]), "T6031 refusal stops admission"); @@ -310,9 +323,42 @@ def c_source(root): assert_eq!(run(&T6031, false, 0, 0), (Err(ENODEV), vec!["machine"]), "the machine check comes first"); assert_eq!(run(&T6030, true, 0, -517), (Ok(()), with_tail(&["board", "pmp"])), "T6030 path unchanged"); assert_eq!(run(&T6030, true, -517, 0), (Err(EPROBE_DEFER), vec!["board", "pmp"]), "T6030 still defers on its PMP link"); +@SLEEP_HOLD_CHECKS@ } ''' +HOLD_PLAIN = ' pub fn hold_power(_: &super::device::Device) { call("hold"); }' +HOLD_SLEEP = (' pub fn hold_power(_: &super::device::Device, sleep_hold: bool) {\n' + ' call("hold"); super::HOLD.with(|h| *h.borrow_mut() = Some(sleep_hold));\n }') +SLEEP_HOLD_RUN = r''' +fn run_hold(cfg: &'static Config, sleep_hold: bool, asc_fail: bool) -> (Result<()>, Vec<&'static str>, Option, u32) { + CALLS.with(|c| c.borrow_mut().clear()); + STATE.with(|s| *s.borrow_mut() = State { board: true, machine: true, pmp: 0, display: 0, sleep_hold, asc_fail }); + HOLD.with(|h| *h.borrow_mut() = None); + WARNINGS.with(|w| *w.borrow_mut() = 0); + let pdev = platform::Device(device::Device, Core(PhantomData)); + let ret = admit(&pdev, &AsahiDevice, cfg).map(|_| ()); + (ret, CALLS.with(|c| c.borrow().clone()), HOLD.with(|h| *h.borrow()), WARNINGS.with(|w| *w.borrow())) +} +fn before_hold(head: &[&'static str]) -> Vec<&'static str> { head.iter().chain(TAIL[..TAIL.len() - 1].iter()).copied().collect() } +''' +SLEEP_HOLD_CHECKS = r''' + // The system-sleep hold: T6031 only, and only while g15_sleep_hold is on; off is warned about. + assert_eq!(run_hold(&T6031, true, false), (Ok(()), with_tail(&["machine", "display"]), Some(true), 0), "T6031 holds gfx through sleep"); + assert_eq!(run_hold(&T6031, false, false), (Ok(()), with_tail(&["machine", "display"]), Some(false), 1), "g15_sleep_hold=0: no sleep hold, one warning"); + assert_eq!(run_hold(&T6030, true, false), (Ok(()), with_tail(&["board", "pmp"]), Some(false), 0), "T6030 never holds through sleep"); + // The ASC read comes before the hold: a failure there leaves no hold behind. + assert_eq!(run_hold(&T6031, true, true), (Err(EIO), before_hold(&["machine", "display"]), None, 0), "T6031: ASC failure takes no hold"); + assert_eq!(run_hold(&T6030, true, true), (Err(EIO), before_hold(&["board", "pmp"]), None, 0), "T6030: ASC failure takes no hold"); +''' +SLEEP_HOLD_MUTANTS = { + "sleep-hold-any-platform": ("let sleep_hold = t6031 && params::sleep_hold();", "let sleep_hold = params::sleep_hold();"), + "sleep-hold-ignores-parameter": ("let sleep_hold = t6031 && params::sleep_hold();", "let sleep_hold = t6031;"), + "no-warning-when-off": ("} else if t6031 {", "} else if false {"), + "asc-read-after-hold": (re.compile(r"(\n\s*)let asc = regs\.asc_control\(\)\?;(.*?glue::hold_power\(dev, sleep_hold\);)", re.S), + r"\2\1let asc = regs.asc_control()?;"), +} + RS_MUTANTS = { "display-refusal-ignored": ("dev_err!(dev, \"native display not admitted: {:?}\\n\", err);\n }\n return Err(err);", "dev_err!(dev, \"native display not admitted: {:?}\\n\", err);\n }"), @@ -346,10 +392,18 @@ def c_failures(exe): return [s for s in C_SCENARIOS if subprocess.run([str(exe), s], capture_output=True).returncode] -def compile_rs(work, rustc, source, name): +def rs_model(admit): + """The prelude and main for this admit(): with the system-sleep hold argument (g15_sleep_hold) or without.""" + sleep = "glue::hold_power(dev, sleep_hold);" in admit + prefix = RS_PREFIX.replace("@HOLD_POWER@", HOLD_SLEEP if sleep else HOLD_PLAIN) + main = RS_MAIN.replace("@SLEEP_HOLD_RUN@", SLEEP_HOLD_RUN if sleep else "").replace("@SLEEP_HOLD_CHECKS@", SLEEP_HOLD_CHECKS if sleep else "") + return sleep, prefix, main + + +def compile_rs(work, rustc, source, name, model): src = work / f"{name}.rs" exe = work / name - src.write_text(RS_PREFIX + source + RS_MAIN) + src.write_text(model[1] + source + model[2]) result = subprocess.run([rustc, "--edition=2021", "-o", str(exe), str(src)], capture_output=True, text=True) if result.returncode: raise SystemExit(f"{name}: does not build with {rustc}:\n{result.stderr}") @@ -387,6 +441,8 @@ def main(): checks = static_checks(args.root) source = c_source(args.root) admit = item((args.root / BOOT).read_text(), "pub(crate) fn admit(") + model = rs_model(admit) + rs_mutants = dict(RS_MUTANTS, **(SLEEP_HOLD_MUTANTS if model[0] else {})) with tempfile.TemporaryDirectory(prefix="g15-display-order-", dir=os.environ.get("TMPDIR")) as tmp: work = Path(tmp) failed = c_failures(compile_c(work, source, "control")) @@ -396,16 +452,17 @@ def main(): assert failed, f"C mutant {name} survived" print(f"mutant {name}: caught by {', '.join(failed)}") for rustc in rustcs: - exe = compile_rs(work, rustc, admit, "admit") + exe = compile_rs(work, rustc, admit, "admit", model) run = subprocess.run([str(exe)], capture_output=True, text=True) assert run.returncode == 0, f"admit() control failed with {rustc}:\n{run.stderr}" - for name, change in RS_MUTANTS.items(): - exe = compile_rs(work, rustc, mutate(admit, change), f"admit-{name}") + for name, change in rs_mutants.items(): + exe = compile_rs(work, rustc, mutate(admit, change), f"admit-{name}", model) run = subprocess.run([str(exe)], capture_output=True, text=True) assert run.returncode, f"admit() mutant {name} survived with {rustc}" print(f"mutant {name} ({rustc}): caught") print(f"PASS: {checks} static checks, {len(C_SCENARIOS)} C scenarios, " - f"{len(C_MUTANTS)} C mutants, admit() on {len(rustcs)} rustc, {len(RS_MUTANTS)} admit() mutants") + f"{len(C_MUTANTS)} C mutants, admit() on {len(rustcs)} rustc, {len(rs_mutants)} admit() mutants" + + (", system-sleep hold checks" if model[0] else "")) if __name__ == "__main__": diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/lifecycle.py b/tools/testing/selftests/drivers/gpu/apple-dcp/lifecycle.py index d8270fb0a89c4a..96fd0b5dcea014 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/lifecycle.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/lifecycle.py @@ -459,6 +459,25 @@ def function(file, name): static void __attribute__((unused)) dcp_relink_link_up(int *s, u64 generation, unsigned long now) { (void)s; (void)generation; (void)now; assert(!"legacy relink timestamp in an H17P scenario"); } """ + # Let the real helper classify an occupied display on newer trees, where + # -EBUSY means a transient mux/PHY failure rather than a taken pipeline. + if re.search(r'cd321x_display_taken\(int ret\)', pm): + assert shim.count('mode_error == -EBUSY') == 1 + assert tests.count('mode_error = -EBUSY;') == 2 + shim = shim.replace('mode_error == -EBUSY', 'cd321x_display_taken(mode_error)') + tests = tests.replace('mode_error = -EBUSY;', 'mode_error = -ENOSPC;') + busy = ' CHECK(sink.connected && !notices && !port.display_route_active);\n' + assert tests.count(busy) == 1 + tests = tests.replace(busy, busy + r''' + CHECK(roles == 1 && port.pm.phase == CD321X_PM_RUNNING); + /* A transient busy mux must retry before switching the USB role. */ + init(); port.display_route_active = false; role = USB_ROLE_NONE; + port.update_status.status = TPS_STATUS_PLUG_PRESENT; + port.update_status.data_status = TPS_DATA_STATUS_DP_CONNECTION | CD321X_DATA_STATUS_HPD_LEVEL; + mode_error = -EBUSY; cd321x_update_work(&port.update_work.work); + CHECK(!roles && port.pm.phase == CD321X_PM_REVALIDATE); + CHECK(sink.connected && !notices && !port.display_route_active); +''') # How long a held mux has been retried is measured on the driver's clock. if 'jiffies_to_msecs(jiffies)' in funcs: shim += ('\nstatic unsigned long jiffies;\n' diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/pm.py b/tools/testing/selftests/drivers/gpu/apple-dcp/pm.py index 63b1ddb2398cd0..6c30856841943d 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/pm.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/pm.py @@ -547,7 +547,7 @@ def function(text, name): int main(void) { (void)cd321x_pm_notifications; struct cd321x c; - struct cd321x_pm_state budget; + struct cd321x_pm_state budget={0}; cd321x_pm_init(&budget);assert(cd321x_pm_ready(&budget)); assert(cd321x_display_taken(-ENOSPC,true)&&!cd321x_display_taken(-EBUSY,true)&&!cd321x_display_taken(0,true)&&cd321x_display_taken(-EBUSY,false)&&!cd321x_display_taken(-ENOSPC,false)&&!cd321x_display_taken(0,false)); for(unsigned int i=0;i