fn compute_payload_cap(bulk: bool) -> Option<PayloadCap> {
    if let Some(limit) = env_u32_opt("LESAVKA_UVC_MAXPAYLOAD_LIMIT") {
        return Some(PayloadCap {
            limit,
            pct: 100,
            source: "env",
            periodic_dw: None,
            non_periodic_dw: None,
        });
    }

    let mut pct = env_u32_opt("LESAVKA_UVC_LIMIT_PCT")?;
    if pct == 0 {
        pct = 1;
    } else if pct > 100 {
        pct = 100;
    }
    let base: u32 = if bulk { 512 } else { 1024 };

    Some(PayloadCap {
        limit: base.saturating_mul(pct) / 100,
        pct,
        source: "stub",
        periodic_dw: None,
        non_periodic_dw: None,
    })
}

#[cfg(coverage)]
fn read_fifo_min(path: &str) -> Option<u32> {
    let raw = std::fs::read_to_string(path).ok()?;
    raw.split(|c: char| c == ',' || c.is_whitespace())
        .filter_map(|v| v.trim().parse::<u32>().ok())
        .filter(|v| *v > 0)
        .min()
}

#[cfg(coverage)]
const IOC_NRBITS: u8 = 8;
#[cfg(coverage)]
const IOC_TYPEBITS: u8 = 8;
#[cfg(coverage)]
const IOC_SIZEBITS: u8 = 14;
#[cfg(coverage)]
const IOC_NRSHIFT: u8 = 0;
#[cfg(coverage)]
const IOC_TYPESHIFT: u8 = IOC_NRSHIFT + IOC_NRBITS;
#[cfg(coverage)]
const IOC_SIZESHIFT: u8 = IOC_TYPESHIFT + IOC_TYPEBITS;
#[cfg(coverage)]
const IOC_DIRSHIFT: u8 = IOC_SIZESHIFT + IOC_SIZEBITS;
#[cfg(coverage)]
const IOC_READ: u8 = 2;
#[cfg(coverage)]
const IOC_WRITE: u8 = 1;

#[cfg(coverage)]
fn ioctl_read<T>(type_: u8, nr: u8) -> libc::c_ulong {
    ioc(IOC_READ, type_, nr, std::mem::size_of::<T>() as u16)
}

#[cfg(coverage)]
fn ioctl_write<T>(type_: u8, nr: u8) -> libc::c_ulong {
    ioc(IOC_WRITE, type_, nr, std::mem::size_of::<T>() as u16)
}

#[cfg(coverage)]
fn ioc(dir: u8, type_: u8, nr: u8, size: u16) -> libc::c_ulong {
    let dir = (dir as u32) << IOC_DIRSHIFT;
    let ty = (type_ as u32) << IOC_TYPESHIFT;
    let nr = (nr as u32) << IOC_NRSHIFT;
    let size = (size as u32) << IOC_SIZESHIFT;
    (dir | ty | nr | size) as libc::c_ulong
}
