Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 48 additions & 5 deletions tools/asahi/avd-av1-tail-tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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<dst_offset;i++)assert(dst.base.vb.vb2_buf.memory[i]==0xa5);
for(size_t i=dst_offset+sizeof(expected);i<dst_len;i++)assert(dst.base.vb.vb2_buf.memory[i]==0xa5);
Expand All @@ -125,6 +129,30 @@ def structure(source, name):
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);
}
#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;i<len;i++)dst.base.vb.vb2_buf.memory[i]=fill(i);
for(size_t i=0;i<sizeof(expected);i++)expected[i]=fill(offset+i);
memset(probabilities,0,sizeof(probabilities));
memset(&frame,0,sizeof(frame));frame.frame_width_minus_1=1919;frame.frame_height_minus_1=1079;frame.quantization.base_q_idx=47;
frame.frame_type=V4L2_AV1_INTER_FRAME;frame.primary_ref_frame=0;frame.ref_frame_idx[0]=idx;frame.reference_frame_ts[0]=1000;
for(int i=0;i<8;i++)frame.order_hints[i]=0;
controls[1].p_cur.p=&frame;run.frame=&frame;run.base.bufs.dst=&dst.base.vb;av1.bufs.probs.cpu=probabilities;reference=NULL;lookup_calls=0;last_timestamp=0;
avd_av1_set_prob(&ctx,&run);
assert(lookup_calls==1);assert(last_timestamp==UINT64_MAX);
/* Inherited from the destination's own CDF area as it was before the call... */
assert(memcmp(probabilities,expected,sizeof(expected))==0);
/* ...and written back unchanged: no destination byte differs from the fill. */
for(size_t i=0;i<len;i++)assert(dst.base.vb.vb2_buf.memory[i]==fill(i));
free(dst.base.vb.vb2_buf.memory);
}
#endif
int main(int argc,char **argv){
assert(sizeof(struct avd_av1_cdfs)==9166);
if(argc>1&&(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;}
Expand All @@ -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;
}
'''
Expand All @@ -148,26 +179,35 @@ 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')
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'
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 <linux/types.h>', '')
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'
Expand All @@ -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':
Expand Down
81 changes: 69 additions & 12 deletions tools/asahi/g15-display-order-tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand All @@ -236,13 +236,21 @@ def c_source(root):
type Result<T = ()> = std::result::Result<T, Error>;
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<Vec<&'static str>> = const { RefCell::new(Vec::new()) };
static STATE: RefCell<State> = const { RefCell::new(State { board: true, machine: true, pmp: 0, display: 0 }) };
static STATE: RefCell<State> = const { RefCell::new(State { board: true, machine: true, pmp: 0, display: 0, sleep_hold: true, asc_fail: false }) };
static HOLD: RefCell<Option<bool>> = const { RefCell::new(None) };
static WARNINGS: RefCell<u32> = 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 {
Expand Down Expand Up @@ -276,7 +284,9 @@ def c_source(root):
pub struct Regs;
impl Regs {
pub fn new<C>(_: &super::platform::Device<C>) -> super::Result<Self> { super::call("regs"); Ok(Self) }
pub fn asc_control(&self) -> super::Result<u32> { Ok(0) }
pub fn asc_control(&self) -> super::Result<u32> {
if super::STATE.with(|s| s.borrow().asc_fail) { Err(super::EIO) } else { Ok(0) }
}
}
}
mod glue {
Expand All @@ -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 }
'''
Expand All @@ -297,22 +307,58 @@ 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");
assert_eq!(run(&T6031, true, 0, 0), (Ok(()), with_tail(&["machine", "display"])), "T6031 admitted after its display");
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<bool>, 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 }"),
Expand Down Expand Up @@ -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}")
Expand Down Expand Up @@ -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"))
Expand All @@ -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__":
Expand Down
Loading