A DisplayLink dock does not advance bcdDevice when its firmware changes, so
that cannot answer whether an update is due. The version lives in a 16-byte
vendor descriptor inside the configuration descriptor, alongside a platform
name that selects the package targeting the hardware; the shipped
*-release.spkg packages carry theirs in a tagged table under `VE`, at no
fixed offset -- 100 in one shipped image and 24574 in another.

Add both parsers, request the matching firmware by the vendor's own
filename so a distribution can install the package unmodified, and update
only when the packaged version is strictly newer. A current dock therefore
costs one descriptor read and no writes, and a missing firmware file is not
an error: a dock runs on the firmware it shipped with.

The transfer is USB DFU 1.1 on the dock's DFU interface, matching the
vendor updater recorded end to end: DFU_DETACH, then the package verbatim
in 4096-byte blocks with an ascending wValue, each followed by
DFU_GETSTATUS, then a zero-length DFU_DNLOAD to manifest. There is no bus
reset between the detach and the first block -- the dock takes the download
in its runtime interface -- and it re-enumerates once the image is
manifested.

The functional descriptor clears bitCanUpload, so the running image cannot
be read back and there is no host-side restore path; and it clears
bitManifestationTolerant. Hence the strictly-newer rule and the package
magic check before any byte is written.

Userspace can also push an image on demand through the firmware loader's
upload interface, for a dock whose packaged firmware a distribution does
not ship.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Mike Lothian <[email protected]>
---
 drivers/gpu/drm/vino/firmware.rs | 670 +++++++++++++++++++++++++++++++
 1 file changed, 670 insertions(+)
 create mode 100644 drivers/gpu/drm/vino/firmware.rs

diff --git a/drivers/gpu/drm/vino/firmware.rs b/drivers/gpu/drm/vino/firmware.rs
new file mode 100644
index 000000000000..13c9cff55ae4
--- /dev/null
+++ b/drivers/gpu/drm/vino/firmware.rs
@@ -0,0 +1,670 @@
+// SPDX-License-Identifier: GPL-2.0
+//! Dock firmware: identity, package parsing, and the USB DFU update.
+//!
+//! A DisplayLink dock carries its firmware version in a vendor descriptor 
rather than in
+//! `bcdDevice`, which does not change across an update. The shipped 
`*-release.spkg` packages carry
+//! theirs in a tagged table. Comparing the two says whether an update is due; 
the transfer itself
+//! is textbook USB DFU 1.1 on the dock's DFU interface.
+
+use core::sync::atomic::{AtomicBool, AtomicU64, Ordering};
+use kernel::device::Device;
+#[cfg(CONFIG_RUST_FW_LOADER_ABSTRACTIONS)]
+use kernel::firmware::Firmware;
+use kernel::prelude::*;
+use kernel::sync::{Arc, ArcBorrow};
+use kernel::time::Delta;
+use kernel::usb;
+
+/// The dock's DFU interface: `bInterfaceClass 0xfe`, `bInterfaceSubClass 1`, 
and the interface
+/// number every DFU class request is addressed to.
+pub(crate) const DFU_INTERFACE: u8 = 1;
+
+/// Vendor descriptor carrying the platform name and running firmware version.
+///
+/// `bcdDevice` does not change when a dock is updated, so it cannot answer 
"is an update due".
+/// This descriptor can: it is 16 bytes, `[len, 0x40, major, minor, patch, 
..., name(8)]`, and the
+/// name selects the package that targets this hardware.
+pub(crate) const DESCRIPTOR_IDENTITY: u8 = 0x40;
+const IDENTITY_LEN: usize = 16;
+const IDENTITY_NAME: usize = 8;
+
+/// A dock's platform name and the firmware version it is running.
+#[derive(Clone, Copy, PartialEq, Eq)]
+pub(crate) struct Identity {
+    pub(crate) version: Version,
+    name: [u8; IDENTITY_NAME],
+}
+
+/// A three-part firmware version, ordered major-minor-patch.
+#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+pub(crate) struct Version(pub(crate) u8, pub(crate) u8, pub(crate) u8);
+
+impl kernel::fmt::Display for Version {
+    fn fmt(&self, f: &mut kernel::fmt::Formatter<'_>) -> kernel::fmt::Result {
+        write!(f, "{}.{}.{}", self.0, self.1, self.2)
+    }
+}
+
+impl Identity {
+    /// Parse the identity descriptor out of a device's raw configuration 
descriptors.
+    pub(crate) fn parse(raw: &[u8]) -> Option<Self> {
+        let mut i = 0usize;
+        while i + 2 <= raw.len() {
+            let len = usize::from(raw[i]);
+            if len < 2 || i + len > raw.len() {
+                return None;
+            }
+            if raw[i + 1] == DESCRIPTOR_IDENTITY && len >= IDENTITY_LEN {
+                let d = &raw[i..i + IDENTITY_LEN];
+                let mut name = [0u8; IDENTITY_NAME];
+                name.copy_from_slice(&d[8..16]);
+                return Some(Self {
+                    version: Version(d[2], d[3], d[4]),
+                    name,
+                });
+            }
+            i += len;
+        }
+        None
+    }
+
+    /// The platform name, trimmed of its padding.
+    pub(crate) fn platform(&self) -> &[u8] {
+        let end = self
+            .name
+            .iter()
+            .position(|&c| c == 0 || c == b' ')
+            .unwrap_or(IDENTITY_NAME);
+        &self.name[..end]
+    }
+
+    /// Which dock family this is.
+    pub(crate) fn family(&self) -> Option<Family> {
+        Family::from_identity(self.platform())
+    }
+
+    /// The firmware file that targets this platform, under `/lib/firmware`.
+    pub(crate) fn firmware_name(&self) -> Option<&'static CStr> {
+        Some(self.family()?.firmware_name())
+    }
+}
+
+impl kernel::fmt::Display for Identity {
+    /// Names the hardware the way its documentation does, falling back to the 
raw identity tag
+    /// for a device this driver does not recognise.
+    fn fmt(&self, f: &mut kernel::fmt::Formatter<'_>) -> kernel::fmt::Result {
+        match self.family() {
+            Some(family) => write!(f, "{}", family.description()),
+            None => match core::str::from_utf8(self.platform()) {
+                Ok(name) => write!(f, "unrecognised device {name}"),
+                Err(_) => write!(f, "unrecognised device {:02x?}", 
self.platform()),
+            },
+        }
+    }
+}
+
+/// A dock family: the hardware a firmware package targets.
+///
+/// Only `NavaDock` has been read off real hardware; the spellings for the 
other three come from
+/// the vendor's firmware packages and are unverified.
+#[derive(Clone, Copy, PartialEq, Eq)]
+pub(crate) enum Family {
+    /// DL-3x00 dock, e.g. the HP 3005pr.
+    Ella,
+    /// DL-6xxx dock, e.g. the Dell D6000.
+    Ridge,
+    /// DL-7400 quad dock.
+    Navarro,
+    /// Firefly monitor.
+    Firefly,
+}
+
+impl Family {
+    /// From the device's identity descriptor name.
+    pub(crate) fn from_identity(name: &[u8]) -> Option<Self> {
+        match name {
+            b"NavaDock" => Some(Self::Navarro),
+            b"Ridge" | b"RidgeDoc" => Some(Self::Ridge),
+            b"Ella" | b"EllaDock" => Some(Self::Ella),
+            b"Firefly" | b"FflyMoni" => Some(Self::Firefly),
+            _ => None,
+        }
+    }
+
+    /// How this family is described in a log line, including what kind of 
device it is.
+    ///
+    /// The device's own identity string is an eight-character tag -- 
"NavaDock", "FflyMoni" --
+    /// which is not what the hardware is called anywhere else.
+    fn description(self) -> &'static str {
+        match self {
+            Self::Navarro => "Navarro dock",
+            Self::Ridge => "Ridge dock",
+            Self::Ella => "Ella dock",
+            Self::Firefly => "Firefly monitor",
+        }
+    }
+
+    /// The file that carries this family's firmware, under `/lib/firmware`.
+    ///
+    /// Named after the package DisplayLink ships for the platform, so a 
distribution can drop the
+    /// vendor's own file in unmodified.
+    pub(crate) fn firmware_name(self) -> &'static CStr {
+        match self {
+            Self::Navarro => c"vino/navarro-dock-release.spkg",
+            Self::Ridge => c"vino/ridge-dock-release.spkg",
+            Self::Ella => c"vino/ella-dock-release.spkg",
+            Self::Firefly => c"vino/firefly-monitor-release.spkg",
+        }
+    }
+
+    /// From a package's `RD` tag.
+    pub(crate) fn from_package(rd: &[u8]) -> Option<Self> {
+        Self::from_identity(rd)
+    }
+}
+
+/// The dock family a package targets, from its `RD` tag.
+///
+/// This is what stops a Ridge image being written to a Navarro dock: the 
package says who it is
+/// for, so an image pushed in by hand can be checked without trusting the 
filename.
+pub(crate) fn package_family(image: &[u8]) -> Option<Family> {
+    const TAG: [u8; 4] = [b'R', b'D', 8, 0];
+    let w = image
+        .windows(TAG.len() + 8)
+        .find(|w| w[..TAG.len()] == TAG)?;
+    let name = &w[4..12];
+    let end = name.iter().position(|&c| c == 0).unwrap_or(name.len());
+    Family::from_package(&name[..end])
+}
+
+/// Standard `GET_DESCRIPTOR` for the configuration descriptor, which the 
identity blob rides in.
+const GET_DESCRIPTOR: u8 = 6;
+const DEVICE_TO_HOST_STANDARD: u8 = 0x80;
+const DESCRIPTOR_CONFIG: u16 = 0x0200;
+/// Enough for any configuration this hardware reports; the dock's is well 
under 1 KiB.
+const CONFIG_DESCRIPTOR_MAX: usize = 1024;
+
+/// Read the dock's identity by walking its configuration descriptor.
+///
+/// The blob is a vendor descriptor inside the configuration, not a separately 
addressable one, so
+/// the whole configuration is fetched and walked. 
`usb_device::rawdescriptors` holds the same bytes
+/// already, but no binding exposes them and one standard control read costs 
nothing on probe.
+pub(crate) fn read_identity(io: &usb::Io<'_>) -> Result<Identity> {
+    let mut connector = [0u8; 9];
+    io.control_recv(
+        GET_DESCRIPTOR,
+        DEVICE_TO_HOST_STANDARD,
+        DESCRIPTOR_CONFIG,
+        0,
+        &mut connector,
+        XFER_TIMEOUT,
+        GFP_KERNEL,
+    )?;
+    let total = usize::from(u16::from_le_bytes([connector[2], connector[3]]));
+    if total < connector.len() || total > CONFIG_DESCRIPTOR_MAX {
+        return Err(EINVAL);
+    }
+    let mut all = KVec::with_capacity(total, GFP_KERNEL)?;
+    all.resize(total, 0u8, GFP_KERNEL)?;
+    io.control_recv(
+        GET_DESCRIPTOR,
+        DEVICE_TO_HOST_STANDARD,
+        DESCRIPTOR_CONFIG,
+        0,
+        &mut all,
+        XFER_TIMEOUT,
+        GFP_KERNEL,
+    )?;
+    Identity::parse(&all).ok_or(ENODEV)
+}
+
+/// The version a `.spkg` will install.
+///
+/// The package is a tagged table: a two-byte ASCII tag, a `u16` length, then 
the value. `VE` holds
+/// the three version bytes. Its offset differs per package -- 100 in one 
shipped image and 24574 in
+/// another -- so it is searched for rather than assumed.
+pub(crate) fn package_version(image: &[u8]) -> Option<Version> {
+    const TAG: [u8; 4] = [b'V', b'E', 3, 0];
+    image
+        .windows(TAG.len() + 3)
+        .find(|w| w[..TAG.len()] == TAG)
+        .map(|w| Version(w[4], w[5], w[6]))
+}
+
+/// `.spkg` container magic, checked before anything is written to a dock.
+const PACKAGE_MAGIC: &[u8; 4] = b"ELLA";
+
+/// Whether `image` is a firmware package at all.
+pub(crate) fn is_package(image: &[u8]) -> bool {
+    image.len() > 8 && &image[..4] == PACKAGE_MAGIC
+}
+
+// USB DFU 1.1 class requests, on the dock's DFU interface.
+const DFU_OUT: u8 = 0x21; // host-to-device, class, interface
+const DFU_IN: u8 = 0xa1; // device-to-host, class, interface
+const DFU_DETACH: u8 = 0;
+const DFU_DNLOAD: u8 = 1;
+const DFU_GETSTATUS: u8 = 3;
+
+/// Payload per `DFU_DNLOAD`.
+///
+/// The DFU functional descriptor advertises a 16384-byte `wTransferSize`, but 
the vendor's own
+/// updater sends 4096 and the dock is only known to accept that.
+const BLOCK: usize = 4096;
+
+/// `wValue` of `DFU_DETACH`, in milliseconds. The vendor sends 100.
+const DETACH_TIMEOUT_MS: u16 = 100;
+
+/// How long a single control transfer may take.
+const XFER_TIMEOUT: Delta = Delta::from_secs(5);
+
+/// `DFU_GETSTATUS` reply: `bStatus, bwPollTimeout[3], bState, iString`.
+const STATUS_LEN: usize = 6;
+const STATUS_OK: u8 = 0;
+const STATE_DNLOAD_IDLE: u8 = 5;
+const STATE_DNBUSY: u8 = 4;
+const STATE_MANIFEST_SYNC: u8 = 6;
+const STATE_MANIFEST: u8 = 7;
+
+/// Poll `DFU_GETSTATUS` until the dock leaves `dfuDNBUSY`, honouring its own 
poll timeout.
+fn wait_ready(io: &usb::Io<'_>, iface: u16) -> Result<u8> {
+    for _ in 0..1000 {
+        let mut st = [0u8; STATUS_LEN];
+        io.control_recv(
+            DFU_GETSTATUS,
+            DFU_IN,
+            0,
+            iface,
+            &mut st,
+            XFER_TIMEOUT,
+            GFP_KERNEL,
+        )?;
+        if st[0] != STATUS_OK {
+            pr_err!(
+                "vino: firmware update rejected: DFU status {} in state {}\n",
+                st[0],
+                st[4]
+            );
+            return Err(EIO);
+        }
+        // bwPollTimeout is a 24-bit little-endian millisecond count the 
device asks us to wait.
+        let poll = u32::from(st[1]) | u32::from(st[2]) << 8 | u32::from(st[3]) 
<< 16;
+        if st[4] != STATE_DNBUSY && st[4] != STATE_MANIFEST {
+            return Ok(st[4]);
+        }
+        kernel::time::delay::fsleep(Delta::from_millis(poll.clamp(1, 1000) as 
i64));
+    }
+    Err(ETIMEDOUT)
+}
+
+/// Write `image` to the dock over USB DFU.
+///
+/// This is not reversible from the host. The DFU functional descriptor clears
+/// `bitCanUpload`, so the running firmware cannot be read back and there is 
nothing to restore
+/// from; and it sets `bitManifestationTolerant = 0`, so the dock 
re-enumerates when the image is
+/// manifested. An interrupted write leaves the dock with a partial image.
+///
+/// The sequence is the vendor updater's, recorded end to end: `DFU_DETACH`, 
then the whole package
+/// verbatim in 4096-byte blocks with an ascending `wValue`, each followed by 
`DFU_GETSTATUS`, then
+/// a zero-length `DFU_DNLOAD` to manifest. There is no bus reset between the 
detach and the first
+/// block -- the dock accepts the download in its runtime interface.
+pub(crate) fn flash(io: &usb::Io<'_>, iface: u16, image: &[u8]) -> Result {
+    if !is_package(image) {
+        pr_err!("vino: refusing to flash: not a DisplayLink firmware 
package\n");
+        return Err(EINVAL);
+    }
+    let blocks = image.len().div_ceil(BLOCK);
+    if blocks > usize::from(u16::MAX) {
+        return Err(EFBIG);
+    }
+    pr_info!(
+        "vino: flashing {} bytes of dock firmware in {} block(s) -- do not 
disconnect\n",
+        image.len(),
+        blocks
+    );
+
+    io.control_send(
+        DFU_DETACH,
+        DFU_OUT,
+        DETACH_TIMEOUT_MS,
+        iface,
+        &[],
+        XFER_TIMEOUT,
+        GFP_KERNEL,
+    )?;
+
+    for (n, chunk) in image.chunks(BLOCK).enumerate() {
+        io.control_send(
+            DFU_DNLOAD,
+            DFU_OUT,
+            n as u16,
+            iface,
+            chunk,
+            XFER_TIMEOUT,
+            GFP_KERNEL,
+        )?;
+        let state = wait_ready(io, iface)?;
+        if state != STATE_DNLOAD_IDLE {
+            pr_err!("vino: firmware block {n} left the dock in DFU state 
{state}\n");
+            return Err(EIO);
+        }
+    }
+
+    // Zero-length download: the image is complete, manifest it. The dock 
re-enumerates from here,
+    // so a failure to read status back afterwards is expected rather than an 
error.
+    io.control_send(DFU_DNLOAD, DFU_OUT, 0, iface, &[], XFER_TIMEOUT, 
GFP_KERNEL)?;
+    match wait_ready(io, iface) {
+        Ok(state) if state == STATE_MANIFEST_SYNC || state == 
STATE_DNLOAD_IDLE => {}
+        Ok(state) => pr_info!("vino: dock manifested in DFU state {state}\n"),
+        Err(_) => pr_info!("vino: dock stopped answering after manifest, as it 
re-enumerates\n"),
+    }
+    pr_info!("vino: dock firmware written; it will re-enumerate to run it\n");
+    Ok(())
+}
+
+/// Decide whether `dev` needs an update, and apply one if it does.
+///
+/// An update is applied only when the packaged version is strictly newer than 
the one the dock
+/// reports, so the ordinary path on a current dock is one descriptor read and 
no writes at all. A
+/// missing firmware file is not an error: a dock runs perfectly well on the 
firmware it shipped
+/// with, and most systems will never carry the package.
+#[cfg(not(CONFIG_RUST_FW_LOADER_ABSTRACTIONS))]
+pub(crate) fn update_if_newer(
+    _io: &usb::Io<'_>,
+    dev: &Device,
+    identity: &Identity,
+    _iface: u16,
+    _force: bool,
+) -> Result {
+    vino_dev_debug!(
+        dev,
+        "dock firmware {}; updates need CONFIG_RUST_FW_LOADER_ABSTRACTIONS\n",
+        identity.version
+    );
+    Ok(())
+}
+
+/// Automatic updates attempted for one device: the device-name hash in the 
high 32 bits, the
+/// attempt count in the low 32.
+///
+/// A dock that re-enumerates still reporting the old version is otherwise 
rewritten on every
+/// probe, without end. One slot is enough, because the device that is looping 
is the one being
+/// probed; another device simply takes the slot over and starts its own count.
+#[cfg(CONFIG_RUST_FW_LOADER_ABSTRACTIONS)]
+static UPDATE_ATTEMPTS: AtomicU64 = AtomicU64::new(0);
+#[cfg(CONFIG_RUST_FW_LOADER_ABSTRACTIONS)]
+const ATTEMPT_KEY_MASK: u64 = 0xffff_ffff_0000_0000;
+
+/// How many automatic writes one device is given before the driver leaves it 
on what it runs.
+///
+/// A write that takes effect is visible on the very next probe, so a device 
that has been given
+/// this many and still reports the old version is not going to accept another 
one.
+#[cfg(CONFIG_RUST_FW_LOADER_ABSTRACTIONS)]
+const MAX_UPDATE_ATTEMPTS: u32 = 2;
+
+/// FNV-1a over the device name, which only has to tell one device's slot from 
another's. The name
+/// is the bus path, so it survives the re-enumeration a write causes.
+#[cfg(CONFIG_RUST_FW_LOADER_ABSTRACTIONS)]
+fn attempt_key(dev: &Device) -> u64 {
+    let mut h: u32 = 0x811c_9dc5;
+    for b in dev.name().to_bytes() {
+        h ^= u32::from(*b);
+        h = h.wrapping_mul(0x0100_0193);
+    }
+    u64::from(h) << 32
+}
+
+#[cfg(CONFIG_RUST_FW_LOADER_ABSTRACTIONS)]
+/// Whether `dev` has an automatic write left, without consuming one.
+fn update_attempts_left(dev: &Device) -> bool {
+    let key = attempt_key(dev);
+    let slot = UPDATE_ATTEMPTS.load(Ordering::Acquire);
+    slot & ATTEMPT_KEY_MASK != key || (slot as u32) < MAX_UPDATE_ATTEMPTS
+}
+
+#[cfg(CONFIG_RUST_FW_LOADER_ABSTRACTIONS)]
+/// Count another automatic write against `dev`, or report that it has had its 
share.
+fn claim_update_attempt(dev: &Device) -> bool {
+    let key = attempt_key(dev);
+    let slot = UPDATE_ATTEMPTS.load(Ordering::Acquire);
+    let count = if slot & ATTEMPT_KEY_MASK == key {
+        slot as u32
+    } else {
+        0
+    };
+    if count >= MAX_UPDATE_ATTEMPTS {
+        return false;
+    }
+    UPDATE_ATTEMPTS.store(key | u64::from(count + 1), Ordering::Release);
+    true
+}
+
+/// Forget the attempts recorded against `dev`, once it is seen running what 
the package offers.
+#[cfg(CONFIG_RUST_FW_LOADER_ABSTRACTIONS)]
+fn clear_update_attempts(dev: &Device) {
+    let key = attempt_key(dev);
+    if UPDATE_ATTEMPTS.load(Ordering::Acquire) & ATTEMPT_KEY_MASK == key {
+        UPDATE_ATTEMPTS.store(0, Ordering::Release);
+    }
+}
+
+#[cfg(CONFIG_RUST_FW_LOADER_ABSTRACTIONS)]
+pub(crate) fn update_if_newer(
+    io: &usb::Io<'_>,
+    dev: &Device,
+    identity: &Identity,
+    iface: u16,
+    force: bool,
+) -> Result {
+    let Some(name) = identity.firmware_name() else {
+        return Ok(());
+    };
+    let Ok(fw) = Firmware::request_nowarn(name, dev) else {
+        vino_dev_debug!(
+            dev,
+            "no {} available; leaving the dock on {}\n",
+            name,
+            identity.version
+        );
+        return Ok(());
+    };
+    let Some(packaged) = package_version(fw.data()) else {
+        dev_warn!(dev, "{} carries no version tag; ignoring it\n", name);
+        return Ok(());
+    };
+    if packaged <= identity.version && !force {
+        clear_update_attempts(dev);
+        vino_dev_debug!(dev, "firmware is current ({} offers {})\n", name, 
packaged);
+        return Ok(());
+    }
+    // A write that does not take leaves the dock reporting the old version, 
which is the same
+    // state that asked for the write in the first place. Bound it, or the 
dock is rewritten on
+    // every probe for as long as it stays plugged in. The forced path is a 
deliberate act and is
+    // not limited.
+    if !force && !claim_update_attempt(dev) {
+        dev_warn!(
+            dev,
+            "dock still reports firmware {} after {} update attempt(s); 
leaving it alone\n",
+            identity.version,
+            MAX_UPDATE_ATTEMPTS
+        );
+        return Ok(());
+    }
+    dev_info!(
+        dev,
+        "updating dock firmware {} -> {}{}\n",
+        identity.version,
+        packaged,
+        if force { " (forced)" } else { "" }
+    );
+    flash(io, iface, fw.data())
+}
+
+/// Whether the automatic check will write this device the next time it runs.
+///
+/// A write reboots the dock, so the display function asks before establishing 
a control session
+/// that the dock is about to drop. Deliberately does not consume an attempt; 
the write does that.
+#[cfg(CONFIG_RUST_FW_LOADER_ABSTRACTIONS)]
+pub(crate) fn update_pending(dev: &Device, identity: &Identity, force: bool) 
-> bool {
+    let Some(name) = identity.firmware_name() else {
+        return false;
+    };
+    let Ok(fw) = Firmware::request_nowarn(name, dev) else {
+        return false;
+    };
+    // Mirror every test the write itself makes, so this cannot claim a write 
that will not happen
+    // and leave the display function unbound waiting for it.
+    if !is_package(fw.data()) {
+        return false;
+    }
+    let Some(packaged) = package_version(fw.data()) else {
+        return false;
+    };
+    if packaged <= identity.version && !force {
+        return false;
+    }
+    force || update_attempts_left(dev)
+}
+
+#[cfg(not(CONFIG_RUST_FW_LOADER_ABSTRACTIONS))]
+pub(crate) fn update_pending(_dev: &Device, _identity: &Identity, _force: 
bool) -> bool {
+    false
+}
+
+/// The `/sys/class/firmware/vino-<dock>/` upload interface.
+///
+/// This is the deliberate path: userspace hands vino an image and it is 
written, whatever version
+/// it is. That is what makes a re-flash of the running version, or an 
attempted downgrade,
+/// possible at all -- [`update_if_newer`] refuses both by design.
+///
+/// The checks below are the only thing between a mistyped `cat` and a dock 
that no longer works:
+/// the DFU interface does not support upload, so the running image cannot be 
read back and there
+/// is nothing to restore from.
+pub(crate) struct Upload;
+
+/// What an upload needs to reach the dock, and the cancel flag userspace can 
set.
+pub(crate) struct UploadCtx {
+    /// The window the dock's I/O is issued through, so a write can be refused 
once it closes.
+    pub(crate) window: Arc<usb::IoWindow>,
+    /// Set by `cancel`, observed between blocks.
+    pub(crate) cancelled: AtomicBool,
+    /// The family this dock is, which an image must match.
+    pub(crate) family: Family,
+}
+
+impl kernel::firmware::upload::Upload for Upload {
+    type Data = Arc<UploadCtx>;
+
+    fn prepare(
+        ctx: ArcBorrow<'_, UploadCtx>,
+        image: &[u8],
+    ) -> core::result::Result<(), kernel::firmware::upload::Error> {
+        use kernel::firmware::upload::Error as UErr;
+        ctx.cancelled.store(false, Ordering::Release);
+        if !is_package(image) {
+            pr_err!("vino: refusing upload: not a DisplayLink firmware 
package\n");
+            return Err(UErr::InvalidFirmware);
+        }
+        // The package says which dock it is for. Writing another family's 
image is the one
+        // mistake here that cannot be undone, so it is refused before the 
dock is touched.
+        match package_family(image) {
+            Some(f) if f == ctx.family => {}
+            _ => {
+                pr_err!("vino: refusing upload: image is not for this dock 
family\n");
+                return Err(UErr::InvalidFirmware);
+            }
+        }
+        if let Some(v) = package_version(image) {
+            pr_info!("vino: upload accepted: firmware {v}\n");
+        }
+        Ok(())
+    }
+
+    fn write(
+        ctx: ArcBorrow<'_, UploadCtx>,
+        image: &[u8],
+        offset: u32,
+        _chunk: &[u8],
+    ) -> core::result::Result<u32, kernel::firmware::upload::Error> {
+        use kernel::firmware::upload::Error as UErr;
+        // The dock takes the package as a whole -- block numbers are an index 
into the image, and
+        // the transfer ends with a manifest -- so it is written in one pass 
on the first call
+        // rather than chunk by chunk as the core offers it.
+        if offset != 0 {
+            return Ok(image.len() as u32 - offset);
+        }
+        if ctx.cancelled.load(Ordering::Acquire) {
+            return Err(UErr::Canceled);
+        }
+        let Ok(io) = ctx.window.enter() else {
+            return Err(UErr::Hardware);
+        };
+        match flash(&io, u16::from(DFU_INTERFACE), image) {
+            Ok(()) => Ok(image.len() as u32),
+            Err(e) => {
+                pr_err!("vino: firmware upload failed ({e:?})\n");
+                Err(UErr::ReadWrite)
+            }
+        }
+    }
+
+    fn poll_complete(
+        _ctx: ArcBorrow<'_, UploadCtx>,
+    ) -> core::result::Result<(), kernel::firmware::upload::Error> {
+        // `flash()` is synchronous and has already polled the dock's own DFU 
status to completion.
+        Ok(())
+    }
+
+    fn cancel(ctx: ArcBorrow<'_, UploadCtx>) {
+        // Runs on another thread. The write loop reads this between blocks; a 
transfer already in
+        // flight still completes, because abandoning one mid-image is what 
leaves a dock unusable.
+        ctx.cancelled.store(true, Ordering::Release);
+        pr_info!("vino: firmware upload cancellation requested\n");
+    }
+}
+
+#[cfg(CONFIG_DRM_VINO_KUNIT_TEST)]
+#[kunit_tests(vino_firmware)]
+mod tests {
+    use super::*;
+    use kernel::error::code::EINVAL;
+
+    #[test]
+    fn firmware_identity_and_package_versions_parse() -> Result {
+        // The dock's identity descriptor, as read from a DL-7400: 16 bytes, 
type 0x40, the running
+        // version at offsets 2..5, and the platform name at 8..16.
+        let raw = [
+            0x09u8, 0x02, 0x20, 0x00, 0x01, 0x01, 0x00, 0x80,
+            0x32, // a config descriptor first
+            0x10, 0x40, 0x0c, 0x02, 0x1a, 0x0b, 0x03, 0x22, b'N', b'a', b'v', 
b'a', b'D', b'o',
+            b'c', b'k',
+        ];
+        let id = Identity::parse(&raw).ok_or(EINVAL)?;
+        assert_eq!(id.version, Version(12, 2, 26));
+        assert_eq!(id.platform(), b"NavaDock");
+        assert_eq!(
+            id.firmware_name().ok_or(EINVAL)?,
+            c"vino/navarro-dock-release.spkg"
+        );
+
+        // A package's version is a `VE` tag with a three-byte value, at no 
fixed offset -- the
+        // shipped images carry it at 100 and at 24574.
+        let mut pkg = KVec::new();
+        pkg.extend_from_slice(b"ELLA\0\0\0\0", GFP_KERNEL)?;
+        pkg.extend_from_slice(&[0u8; 64], GFP_KERNEL)?;
+        pkg.extend_from_slice(b"VE\x03\0", GFP_KERNEL)?;
+        pkg.extend_from_slice(&[12, 2, 27], GFP_KERNEL)?;
+        assert!(is_package(&pkg));
+        assert_eq!(package_version(&pkg).ok_or(EINVAL)?, Version(12, 2, 27));
+
+        // Ordering is major-minor-patch, which is what decides whether an 
update is due.
+        assert!(Version(12, 2, 27) > Version(12, 2, 26));
+        assert!(Version(12, 3, 0) > Version(12, 2, 99));
+        assert!(Version(13, 0, 0) > Version(12, 9, 9));
+        assert!(!(Version(12, 2, 26) > Version(12, 2, 26)));
+
+        // Anything that is not a package must be refused before a byte 
reaches the dock.
+        assert!(!is_package(b"not a firmware image"));
+        Ok(())
+    }
+}

Reply via email to