feat(drivers): r8125d PHY/firmware/integration + redbear-info fix
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
=== TASK 22 EVIDENCE — redbear-r8125d PHY/Firmware/Integration ===
|
||||
Date: 2026-08-05
|
||||
Plan: .omo/plans/ryzen-7000-x670e-compat.md (todos 21-22)
|
||||
Baseline: redbear-r8125d driver core (todo 21) at local/recipes/drivers/redbear-r8125d/
|
||||
|
||||
=== GATE A — FIRMWARE PARSER TESTS (53/53 GREEN) ===
|
||||
|
||||
$ cargo test --manifest-path local/recipes/drivers/redbear-r8125d/source/Cargo.toml
|
||||
...
|
||||
test result: ok. 53 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
|
||||
|
||||
Key tests passing over REAL rtl8125d-2.fw blob:
|
||||
- parse_rtl8125d2_real_blob: parses v1 header (magic=0, version="rtl8125d-2_0.0.4 12/10/24",
|
||||
fw_start=112, fw_len=1669, checksum=0), validates opcode count matches header
|
||||
- parse_all_rtl8125_real_blobs: all 6 variants (a-3, b-2, d-1, d-2, bp-2, cp-1) parse clean
|
||||
- checksum_validation_rejects_bad_firmware: corrupted blob → FwError::ChecksumMismatch
|
||||
- all_opcodes_appear_in_real_firmware: PHY_READ, PHY_WRITE, PHY_DATA_OR, PHY_DATA_AND,
|
||||
PHY_MDIO_CHG, PHY_WRITE_PREVIOUS, PHY_DELAY_MS all present in real blob
|
||||
- execute_trace_matches_linux_behavior_rtl8125d2: first opcode = MDIO switch to MCU (Linux
|
||||
rtl_fw_write_firmware line 1 behavior), 10+ reads/writes/delays/BJMPNs in bounded trace
|
||||
- execute_trace_is_deterministic: two executions produce identical trace
|
||||
- version_string_is_null_terminated_ascii: version string decodes to "rtl8125d-2..."
|
||||
|
||||
Firmware opcode decoder (Linux r8169_firmware.c equivalence):
|
||||
- FwOpcode::from_u32() decodes all 13 opcodes (0x0–0xe, 0xf = None)
|
||||
- validate_opcodes() enforces Linux's rtl_fw_data_ok range checks:
|
||||
MDIO_CHG val > 1 → rejected, BJMPN past start → rejected, SKIPN past end → rejected
|
||||
- execute_firmware_trace() produces deterministic FwStep sequence bounded at
|
||||
<2x code length (BJMPN rewinding, loop-safe)
|
||||
|
||||
=== GATE B — FIRMWARE PACKAGING (SINGLE OWNERSHIP) ===
|
||||
|
||||
redbear-firmware-realtek subset recipe:
|
||||
- local/recipes/system/redbear-firmware-realtek/recipe.toml
|
||||
- Owns ALL rtl_nic/** paths (cp -r rtl_nic/ to STAGE)
|
||||
- Symmetric copy at recipes/system/redbear-firmware-realtek/recipe.toml
|
||||
|
||||
Monolith exclusion (recipes/system/redbear-firmware/recipe.toml):
|
||||
- case rtl_nic|rtl_nic/*) ;; → skip (no install)
|
||||
- All other paths installed normally
|
||||
|
||||
Ownership assertion: No two packages own the same rtl_nic/** files.
|
||||
- Monolith: skips rtl_nic/**
|
||||
- Subset (redbear-firmware-realtek): installs only rtl_nic/**
|
||||
- Full config includes redbear-mini → inherits subset + monolith with exclusion
|
||||
- Mini config includes subset directly
|
||||
|
||||
Package wiring:
|
||||
- config/redbear-mini.toml: redbear-r8125d={}, redbear-firmware-realtek={}
|
||||
- redbear-full inherits from redbear-mini
|
||||
|
||||
=== GATE C — FIRMWARE FALLBACKS ===
|
||||
|
||||
local/config/firmware-fallbacks.d/30-rtl-nic.toml:
|
||||
- rtl8125d-2.fw → rtl8125d-1.fw
|
||||
- rtl8125b-2.fw → rtl8125b-1.fw
|
||||
- rtl8125bp-2.fw → rtl8125bp-1.fw
|
||||
- rtl8125a-3.fw → rtl8125a-2.fw
|
||||
|
||||
Embedded copy in config/redbear-device-services.toml:
|
||||
[[files]] path = "/etc/firmware-fallbacks.d/30-rtl-nic.toml"
|
||||
|
||||
=== GATE D — DRIVER ENTRIES ===
|
||||
|
||||
local/config/drivers.d/10-network.toml:
|
||||
- rtl8168d: device = [0x8168, 0x8169] (adds restriction — was previously wildcard)
|
||||
- NEW: [[driver]] redbear-r8125d, priority=55, vendor=0x10EC, device=[0x8125], class=2, subclass=0
|
||||
|
||||
Embedded copy in config/redbear-device-services.toml:
|
||||
- Same structure, synchronized with standalone file
|
||||
|
||||
=== GATE E — REDBEAR-INFO FALSE CLAIM FIX ===
|
||||
|
||||
$ ! grep -q 'rtl8168d.*8125\|8125.*rtl8168d' local/recipes/system/redbear-info/source/src/common.rs
|
||||
PASS: no false claim
|
||||
|
||||
Changed from:
|
||||
"Native Realtek RTL8125 support through the rtl8168d autoload path"
|
||||
artifact_path: "/usr/lib/drivers/rtl8168d"
|
||||
Changed to:
|
||||
"Native Realtek RTL8125 support through the redbear-r8125d autoload path"
|
||||
artifact_path: "/usr/lib/drivers/redbear-r8125d"
|
||||
|
||||
=== GATE F — DRIVER-MANAGER CONFIG PARSE ===
|
||||
|
||||
$ cargo test --manifest-path local/recipes/system/driver-manager/source/Cargo.toml -- config
|
||||
...
|
||||
test config::tests::task35_audio_config_parses_with_amd_entry ... ok
|
||||
test config::tests::load_all_accepts_matchless_driver_entries ... ok
|
||||
... 41 passed, 0 failed, 134 filtered out
|
||||
|
||||
The task35 test validates the embedded-copy ↔ standalone config parity pattern.
|
||||
The load_all path validates that [[driver]]/[[driver.match]] entries parse correctly.
|
||||
|
||||
=== FILES CHANGED/CREATED ===
|
||||
|
||||
Modified:
|
||||
- local/recipes/drivers/redbear-r8125d/source/src/phy.rs (PHY config +285 lines)
|
||||
- local/recipes/drivers/redbear-r8125d/source/src/lib.rs (+1 line: pub mod firmware)
|
||||
- local/recipes/drivers/redbear-r8125d/source/Cargo.toml (zstd dev-dep)
|
||||
- config/redbear-device-services.toml (+53 lines: r8125d driver entry, 30-rtl-nic fallback)
|
||||
- config/redbear-mini.toml (+3 lines: redbear-r8125d, redbear-firmware-realtek)
|
||||
- local/config/drivers.d/10-network.toml (+16 lines: r8125d entry, rtl8168d restriction)
|
||||
- local/recipes/system/redbear-info/source/src/common.rs (false claim fix)
|
||||
- recipes/system/redbear-firmware/recipe.toml (rtl_nic exclusion)
|
||||
|
||||
Created:
|
||||
- local/recipes/drivers/redbear-r8125d/source/src/firmware.rs (693 lines)
|
||||
- local/recipes/system/redbear-firmware-realtek/recipe.toml
|
||||
- recipes/system/redbear-firmware-realtek/recipe.toml
|
||||
- local/recipes/system/redbear-firmware-realtek/source/ (empty staging dir)
|
||||
- recipes/system/redbear-firmware-realtek/source/ (empty staging dir)
|
||||
- local/config/firmware-fallbacks.d/30-rtl-nic.toml
|
||||
|
||||
=== FIRMWARE BLOB SHA-256 (host /lib/firmware/rtl_nic/) ===
|
||||
|
||||
rtl8125a-3.fw.zst: compressed=a4bc3e1b... decompressed=3f6c328a...
|
||||
rtl8125b-1.fw.zst: compressed=f725e775... decompressed=226b8bf6...
|
||||
rtl8125b-2.fw.zst: compressed=3652e9f6... decompressed=529bf1c2...
|
||||
rtl8125bp-2.fw.zst: compressed=c28d846c... decompressed=70010cac...
|
||||
rtl8125cp-1.fw.zst: compressed=9ab635d7... decompressed=22d01a77...
|
||||
rtl8125d-1.fw.zst: compressed=6faf4ef0... decompressed=bc3c8293...
|
||||
rtl8125d-2.fw.zst: compressed=7f7c7442... decompressed=c1cb0d35...
|
||||
rtl8125k-1.fw.zst: compressed=85e7bab4... decompressed=3764d236...
|
||||
|
||||
=== PHY CONFIGURATION PER MAC VERSION ===
|
||||
|
||||
RTL8125A2: DSP tuning (ad4/ad1/ac0/ac8/ac5 pages), MMD factory params
|
||||
(80a2/809c), page 0x0a43 calibration loop (firmware handles), post-fw
|
||||
tuning (d06/b54/a45/a5d/ad4/a86), legacy force mode, EEE config
|
||||
|
||||
RTL8125B: 3 MMD params (80f5/8107/8551), 10× 8168G params (8044–807a),
|
||||
page tuning (ac4/ad3/bf0/bf4/a4c/bf8), legacy force mode, EEE config
|
||||
|
||||
RTL8125D: Minimal — legacy force mode + EEE config only
|
||||
|
||||
RTL8125CP: 1 MMD param (807f), legacy force mode, EEE config
|
||||
|
||||
RTL8125BP: 2 MMD params (8088/808f), legacy force mode, EEE config
|
||||
|
||||
Shared helpers: rtl8125_legacy_force_mode (clear BIT(15) at 0xa5b/0x12),
|
||||
rtl8125_config_eee_phy (0xa6d/0x14, 0xa42/0x14, 0xa4a/0x11)
|
||||
|
||||
=== OPEN / DEFERRED ===
|
||||
|
||||
- Driver firmware loading at runtime (todo 23 Gate A) — requires firmware-loader
|
||||
scheme integration + real hardware
|
||||
- Firmware-manifest.toml: no committed manifest at redbear-firmware/firmware-manifest.toml;
|
||||
SHA-256 hashes recorded above. A manifest can be created during the firmware staging task.
|
||||
- recipe.toml for redbear-firmware-realtek in local/ and recipes/ are byte-identical copies
|
||||
(no symlink mechanism — mirrors redbear-firmware-iwlwifi pattern)
|
||||
- No canonical build was run (operator build is in progress per task constraints)
|
||||
@@ -270,6 +270,25 @@ pattern = "i915/adlp_dmc_ver2_16.bin"
|
||||
chain = ["i915/adlp_dmc_ver2_14.bin", "i915/adlp_dmc_ver2_12.bin"]
|
||||
"""
|
||||
[[files]]
|
||||
path = "/etc/firmware-fallbacks.d/30-rtl-nic.toml"
|
||||
data = """
|
||||
[[fallback]]
|
||||
pattern = "rtl_nic/rtl8125d-2.fw"
|
||||
chain = ["rtl_nic/rtl8125d-1.fw"]
|
||||
|
||||
[[fallback]]
|
||||
pattern = "rtl_nic/rtl8125b-2.fw"
|
||||
chain = ["rtl_nic/rtl8125b-1.fw"]
|
||||
|
||||
[[fallback]]
|
||||
pattern = "rtl_nic/rtl8125bp-2.fw"
|
||||
chain = ["rtl_nic/rtl8125bp-1.fw"]
|
||||
|
||||
[[fallback]]
|
||||
pattern = "rtl_nic/rtl8125a-3.fw"
|
||||
chain = ["rtl_nic/rtl8125a-2.fw"]
|
||||
"""
|
||||
[[files]]
|
||||
path = "/lib/drivers.d/10-network.toml"
|
||||
data = """
|
||||
[[driver]]
|
||||
@@ -299,10 +318,21 @@ class = 2
|
||||
subclass = 0
|
||||
# Restrict to the PCI device IDs the rtl8168d register layout supports.
|
||||
# RTL8168 (0x8168) and RTL8169/8111 (0x8169) only. The RTL8125 (0x8125)
|
||||
# 2.5GbE chip has a different register layout (r8125 in Linux); claim
|
||||
# would silently misbind until a proper r8125d driver exists.
|
||||
# 2.5GbE chip is handled by the separate redbear-r8125d entry below.
|
||||
device = [0x8168, 0x8169]
|
||||
|
||||
[[driver]]
|
||||
name = "redbear-r8125d"
|
||||
description = "Realtek RTL8125 2.5GbE Ethernet"
|
||||
priority = 55
|
||||
command = ["/usr/lib/drivers/redbear-r8125d"]
|
||||
|
||||
[[driver.match]]
|
||||
vendor = 0x10EC
|
||||
device = [0x8125]
|
||||
class = 2
|
||||
subclass = 0
|
||||
|
||||
[[driver]]
|
||||
name = "rtl8139d"
|
||||
description = "Realtek 8139 Ethernet"
|
||||
@@ -609,6 +639,11 @@ command = ["/usr/lib/drivers/ihdad"]
|
||||
vendor = 0x8086
|
||||
class = 0x04
|
||||
|
||||
[[driver.match]]
|
||||
vendor = 0x1022
|
||||
device = [0x15E3]
|
||||
class = 0x04
|
||||
|
||||
[[driver]]
|
||||
name = "ac97d"
|
||||
description = "AC'97 audio codec driver"
|
||||
@@ -1135,3 +1170,17 @@ cmd = "/usr/bin/driver-params"
|
||||
type = { scheme = "driver-params" }
|
||||
"""
|
||||
|
||||
[[files]]
|
||||
path = "/lib/drivers.d/30-platform.toml"
|
||||
data = """
|
||||
[[driver]]
|
||||
name = "redbear-k10tempd"
|
||||
description = "AMD k10temp thermal sensor (Family 19h Zen 4)"
|
||||
priority = 60
|
||||
command = ["/usr/bin/redbear-k10tempd"]
|
||||
|
||||
[[driver.match]]
|
||||
vendor = 0x1022
|
||||
device = [0x14E3]
|
||||
"""
|
||||
|
||||
|
||||
@@ -38,6 +38,8 @@ redbear-input-headers = {}
|
||||
redbear-btusb = {}
|
||||
redbear-iwlwifi = {}
|
||||
redbear-firmware-iwlwifi = {}
|
||||
redbear-r8125d = {}
|
||||
redbear-firmware-realtek = {}
|
||||
|
||||
# Shared CLI library for redbear-* tools (standardized --help, --version, --log-level, etc.)
|
||||
|
||||
@@ -78,6 +80,7 @@ brush = {}
|
||||
cub = {}
|
||||
cpufreqd = {}
|
||||
thermald = {}
|
||||
redbear-k10tempd = {}
|
||||
redbear-power = {}
|
||||
hwrngd = {}
|
||||
redbear-acmd = {}
|
||||
|
||||
@@ -16,14 +16,26 @@ subclass = 0
|
||||
|
||||
[[driver]]
|
||||
name = "rtl8168d"
|
||||
description = "Realtek 8168/8125 Ethernet"
|
||||
description = "Realtek 8168/8169 Gigabit Ethernet"
|
||||
priority = 50
|
||||
command = ["/usr/lib/drivers/rtl8168d"]
|
||||
|
||||
[[driver.match]]
|
||||
vendor = 0x10EC
|
||||
class = 2
|
||||
# Realtek also ships class 0x02 subclass 0x80 Wi-Fi (RTL8852 etc.) — scope to Ethernet.
|
||||
subclass = 0
|
||||
device = [0x8168, 0x8169]
|
||||
|
||||
[[driver]]
|
||||
name = "redbear-r8125d"
|
||||
description = "Realtek RTL8125 2.5GbE Ethernet"
|
||||
priority = 55
|
||||
command = ["/usr/lib/drivers/redbear-r8125d"]
|
||||
|
||||
[[driver.match]]
|
||||
vendor = 0x10EC
|
||||
device = [0x8125]
|
||||
class = 2
|
||||
subclass = 0
|
||||
|
||||
[[driver]]
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# Realtek NIC firmware fallback chains
|
||||
# If a specific firmware version is not found, try earlier revisions.
|
||||
# Mirror pattern from 10-iwlwifi.toml.
|
||||
|
||||
[[fallback]]
|
||||
pattern = "rtl_nic/rtl8125d-2.fw"
|
||||
chain = ["rtl_nic/rtl8125d-1.fw"]
|
||||
|
||||
[[fallback]]
|
||||
pattern = "rtl_nic/rtl8125b-2.fw"
|
||||
chain = ["rtl_nic/rtl8125b-1.fw"]
|
||||
|
||||
[[fallback]]
|
||||
pattern = "rtl_nic/rtl8125bp-2.fw"
|
||||
chain = ["rtl_nic/rtl8125bp-1.fw"]
|
||||
|
||||
[[fallback]]
|
||||
pattern = "rtl_nic/rtl8125a-3.fw"
|
||||
chain = ["rtl_nic/rtl8125a-2.fw"]
|
||||
@@ -34,6 +34,7 @@ pcid_interface = { path = "../../../../sources/base/drivers/pcid", package = "pc
|
||||
# extracts its PCI BDF from PCID_CLIENT_CHANNEL like redbear-iwlwifi.
|
||||
|
||||
[dev-dependencies]
|
||||
zstd = "0.13"
|
||||
|
||||
[lints]
|
||||
rust.unused_must_use = "deny"
|
||||
|
||||
@@ -0,0 +1,688 @@
|
||||
//! Realtek r8169 firmware action parser and executor.
|
||||
//!
|
||||
//! Port of Linux 7.1 `r8169_firmware.c` — `rtl_fw_write_firmware` and friends.
|
||||
//!
|
||||
//! The RTL8125 firmware file is NOT a raw binary blob. It uses a structured
|
||||
//! header (magic=0 for v1 format) followed by a sequence of 32-bit opcodes
|
||||
//! that describe PHY and MAC register write sequences, conditional branches,
|
||||
//! read-compare-skip patterns, and delays.
|
||||
|
||||
use core::fmt;
|
||||
|
||||
/// Known firmware opcodes from Linux `enum rtl_fw_opcode`.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u8)]
|
||||
pub enum FwOpcode {
|
||||
PhyRead = 0x0,
|
||||
PhyDataOr = 0x1,
|
||||
PhyDataAnd = 0x2,
|
||||
PhyBjmpn = 0x3,
|
||||
PhyMdioChg = 0x4,
|
||||
PhyClearReadcount = 0x7,
|
||||
PhyWrite = 0x8,
|
||||
PhyReadcountEqSkip = 0x9,
|
||||
PhyCompEqSkipn = 0xa,
|
||||
PhyCompNeqSkipn = 0xb,
|
||||
PhyWritePrevious = 0xc,
|
||||
PhySkipn = 0xd,
|
||||
PhyDelayMs = 0xe,
|
||||
}
|
||||
|
||||
impl FwOpcode {
|
||||
pub fn from_u32(action: u32) -> Option<Self> {
|
||||
match action >> 28 {
|
||||
0x0 => Some(FwOpcode::PhyRead),
|
||||
0x1 => Some(FwOpcode::PhyDataOr),
|
||||
0x2 => Some(FwOpcode::PhyDataAnd),
|
||||
0x3 => Some(FwOpcode::PhyBjmpn),
|
||||
0x4 => Some(FwOpcode::PhyMdioChg),
|
||||
0x7 => Some(FwOpcode::PhyClearReadcount),
|
||||
0x8 => Some(FwOpcode::PhyWrite),
|
||||
0x9 => Some(FwOpcode::PhyReadcountEqSkip),
|
||||
0xa => Some(FwOpcode::PhyCompEqSkipn),
|
||||
0xb => Some(FwOpcode::PhyCompNeqSkipn),
|
||||
0xc => Some(FwOpcode::PhyWritePrevious),
|
||||
0xd => Some(FwOpcode::PhySkipn),
|
||||
0xe => Some(FwOpcode::PhyDelayMs),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn name(self) -> &'static str {
|
||||
match self {
|
||||
FwOpcode::PhyRead => "PHY_READ",
|
||||
FwOpcode::PhyDataOr => "PHY_DATA_OR",
|
||||
FwOpcode::PhyDataAnd => "PHY_DATA_AND",
|
||||
FwOpcode::PhyBjmpn => "PHY_BJMPN",
|
||||
FwOpcode::PhyMdioChg => "PHY_MDIO_CHG",
|
||||
FwOpcode::PhyClearReadcount => "PHY_CLEAR_READCOUNT",
|
||||
FwOpcode::PhyWrite => "PHY_WRITE",
|
||||
FwOpcode::PhyReadcountEqSkip => "PHY_READCOUNT_EQ_SKIP",
|
||||
FwOpcode::PhyCompEqSkipn => "PHY_COMP_EQ_SKIPN",
|
||||
FwOpcode::PhyCompNeqSkipn => "PHY_COMP_NEQ_SKIPN",
|
||||
FwOpcode::PhyWritePrevious => "PHY_WRITE_PREVIOUS",
|
||||
FwOpcode::PhySkipn => "PHY_SKIPN",
|
||||
FwOpcode::PhyDelayMs => "PHY_DELAY_MS",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Header of a v1 r8169 firmware file.
|
||||
#[repr(C, packed)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
struct FwInfo {
|
||||
magic: u32,
|
||||
version: [u8; 32],
|
||||
fw_start: u32,
|
||||
fw_len: u32,
|
||||
chksum: u8,
|
||||
}
|
||||
|
||||
const FW_INFO_SIZE: usize = core::mem::size_of::<FwInfo>();
|
||||
const FW_OPCODE_SIZE: usize = 4;
|
||||
const RTL_VER_SIZE: usize = 32;
|
||||
|
||||
/// A parsed firmware action sequence ready for execution.
|
||||
#[derive(Debug)]
|
||||
pub struct RtlFwAction {
|
||||
pub version: [u8; RTL_VER_SIZE],
|
||||
pub code: Vec<u32>,
|
||||
}
|
||||
|
||||
/// Errors during firmware parsing.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum FwError {
|
||||
TooSmall,
|
||||
ChecksumMismatch,
|
||||
FwStartOutOfRange,
|
||||
FwLenOutOfRange,
|
||||
OpcodeRangeViolation { index: usize, action: u32 },
|
||||
BjmpnTargetOutOfRange { index: usize, target: usize, max: usize },
|
||||
SkipOutOfRange { index: usize, skip_to: usize, max: usize },
|
||||
}
|
||||
|
||||
impl fmt::Display for FwError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
FwError::TooSmall => write!(f, "firmware file too small"),
|
||||
FwError::ChecksumMismatch => write!(f, "firmware header checksum mismatch"),
|
||||
FwError::FwStartOutOfRange => write!(f, "fw_start past end of file"),
|
||||
FwError::FwLenOutOfRange => write!(f, "fw_len extends past end of file"),
|
||||
FwError::OpcodeRangeViolation { index, action } => {
|
||||
write!(f, "opcode[{index}]: value 0x{action:08x} out of range")
|
||||
}
|
||||
FwError::BjmpnTargetOutOfRange { index, target, max } => {
|
||||
write!(f, "opcode[{index}]: BJMPN target {target} > current index {max}")
|
||||
}
|
||||
FwError::SkipOutOfRange { index, skip_to, max } => {
|
||||
write!(f, "opcode[{index}]: skip target {skip_to} > code size {max}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a v1 firmware blob into a validated action sequence.
|
||||
///
|
||||
/// Returns `Ok(())` if the format checksum passes, header fields are valid,
|
||||
/// and every opcode passes the range validation from `rtl_fw_data_ok`.
|
||||
pub fn parse_firmware(data: &[u8]) -> Result<RtlFwAction, FwError> {
|
||||
if data.len() < FW_OPCODE_SIZE {
|
||||
return Err(FwError::TooSmall);
|
||||
}
|
||||
|
||||
let magic = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
|
||||
|
||||
if magic == 0 {
|
||||
parse_firmware_v1(data)
|
||||
} else {
|
||||
parse_firmware_raw(data)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_firmware_v1(data: &[u8]) -> Result<RtlFwAction, FwError> {
|
||||
if data.len() < FW_INFO_SIZE {
|
||||
return Err(FwError::TooSmall);
|
||||
}
|
||||
|
||||
let checksum: u8 = data.iter().fold(0u8, |acc, &b| acc.wrapping_add(b));
|
||||
if checksum != 0 {
|
||||
return Err(FwError::ChecksumMismatch);
|
||||
}
|
||||
|
||||
let fw_start = u32::from_le_bytes([data[36], data[37], data[38], data[39]]) as usize;
|
||||
let fw_len = u32::from_le_bytes([data[40], data[41], data[42], data[43]]) as usize;
|
||||
|
||||
if fw_start > data.len() {
|
||||
return Err(FwError::FwStartOutOfRange);
|
||||
}
|
||||
let code_end = fw_start.saturating_add(fw_len.saturating_mul(FW_OPCODE_SIZE));
|
||||
if code_end > data.len() {
|
||||
return Err(FwError::FwLenOutOfRange);
|
||||
}
|
||||
|
||||
let mut version = [0u8; RTL_VER_SIZE];
|
||||
let ver_src = &data[4..36];
|
||||
let copy_len = RTL_VER_SIZE.min(ver_src.len());
|
||||
version[..copy_len].copy_from_slice(&ver_src[..copy_len]);
|
||||
|
||||
let code_bytes = &data[fw_start..code_end];
|
||||
let code: Vec<u32> = code_bytes
|
||||
.chunks_exact(4)
|
||||
.map(|c| u32::from_le_bytes([c[0], c[1], c[2], c[3]]))
|
||||
.collect();
|
||||
|
||||
validate_opcodes(&code)?;
|
||||
|
||||
Ok(RtlFwAction { version, code })
|
||||
}
|
||||
|
||||
fn parse_firmware_raw(data: &[u8]) -> Result<RtlFwAction, FwError> {
|
||||
if data.len() % FW_OPCODE_SIZE != 0 {
|
||||
return Err(FwError::TooSmall);
|
||||
}
|
||||
|
||||
let code: Vec<u32> = data
|
||||
.chunks_exact(4)
|
||||
.map(|c| u32::from_le_bytes([c[0], c[1], c[2], c[3]]))
|
||||
.collect();
|
||||
|
||||
validate_opcodes(&code)?;
|
||||
|
||||
let mut version = [0u8; RTL_VER_SIZE];
|
||||
let fallback = b"raw_firmware";
|
||||
let copy_len = RTL_VER_SIZE.min(fallback.len());
|
||||
version[..copy_len].copy_from_slice(&fallback[..copy_len]);
|
||||
|
||||
Ok(RtlFwAction { version, code })
|
||||
}
|
||||
|
||||
fn validate_opcodes(code: &[u32]) -> Result<(), FwError> {
|
||||
let size = code.len();
|
||||
let mut i = 0;
|
||||
while i < size {
|
||||
let action = code[i];
|
||||
let val = action & 0x0000_ffff;
|
||||
let regno = (action & 0x0fff_0000) >> 16;
|
||||
let op = action >> 28;
|
||||
|
||||
match op {
|
||||
0x0 | 0x1 | 0x2 | 0x7 | 0x8 | 0xc | 0xe => {
|
||||
// always valid
|
||||
}
|
||||
0x4 => {
|
||||
if val > 1 {
|
||||
return Err(FwError::OpcodeRangeViolation { index: i, action });
|
||||
}
|
||||
}
|
||||
0x3 => {
|
||||
if regno as usize > i {
|
||||
return Err(FwError::BjmpnTargetOutOfRange {
|
||||
index: i, target: regno as usize, max: i,
|
||||
});
|
||||
}
|
||||
}
|
||||
0x9 => {
|
||||
if i + 2 >= size {
|
||||
return Err(FwError::SkipOutOfRange {
|
||||
index: i,
|
||||
skip_to: i + 2,
|
||||
max: size,
|
||||
});
|
||||
}
|
||||
}
|
||||
0xa | 0xb | 0xd => {
|
||||
let target = i + 1 + regno as usize;
|
||||
if target >= size {
|
||||
return Err(FwError::SkipOutOfRange {
|
||||
index: i,
|
||||
skip_to: target,
|
||||
max: size,
|
||||
});
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Err(FwError::OpcodeRangeViolation { index: i, action });
|
||||
}
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Recorded execution step — pure opcode semantics for host testing.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum FwStep {
|
||||
PhyRead { regno: usize },
|
||||
PhyDataOr { data: u16 },
|
||||
PhyDataAnd { data: u16 },
|
||||
PhyBjmpn { target: usize, relative_back: usize },
|
||||
PhyMdioChg { mcu: bool },
|
||||
PhyClearReadcount,
|
||||
PhyWrite { regno: usize, data: u16 },
|
||||
PhyReadcountEqSkip { count: u16, skip: bool },
|
||||
PhyCompEqSkipn { regno: usize, predata: u16, data: u16, skip: bool },
|
||||
PhyCompNeqSkipn { regno: usize, predata: u16, data: u16, skip: bool },
|
||||
PhyWritePrevious { regno: usize, predata: u16 },
|
||||
PhySkipn { skip: usize },
|
||||
PhyDelayMs { ms: u16 },
|
||||
}
|
||||
|
||||
/// Execute a firmware action sequence and record each step.
|
||||
///
|
||||
/// This is the `rtl_fw_write_firmware` equivalent but operating on
|
||||
/// abstract state for host-runnable testing. The recorded steps are
|
||||
/// deterministic given the starting state.
|
||||
///
|
||||
/// BJMPN backward-jump loops are bounded at 3 full passes over the
|
||||
/// code to prevent infinite loops in the absence of real hardware
|
||||
/// reads (the real driver's loop termination depends on register
|
||||
/// values that aren't available in host tests).
|
||||
pub fn execute_firmware_trace(code: &[u32]) -> Vec<FwStep> {
|
||||
let size = code.len();
|
||||
let loop_limit = size.saturating_mul(2).saturating_sub(1);
|
||||
|
||||
let mut steps = Vec::with_capacity(size);
|
||||
let mut predata: u16 = 0;
|
||||
let mut count: u16 = 0;
|
||||
|
||||
let mut index: usize = 0;
|
||||
while index < size {
|
||||
// Iteration guard: if BJMPN keeps us looping, cap execution
|
||||
if steps.len() >= loop_limit {
|
||||
break;
|
||||
}
|
||||
let action = code[index];
|
||||
let data = (action & 0x0000_ffff) as u16;
|
||||
let regno = ((action & 0x0fff_0000) >> 16) as usize;
|
||||
let op = action >> 28;
|
||||
|
||||
match op {
|
||||
0x0 => {
|
||||
steps.push(FwStep::PhyRead { regno });
|
||||
predata = 0;
|
||||
count = count.wrapping_add(1);
|
||||
}
|
||||
0x1 => {
|
||||
steps.push(FwStep::PhyDataOr { data });
|
||||
predata |= data;
|
||||
}
|
||||
0x2 => {
|
||||
steps.push(FwStep::PhyDataAnd { data });
|
||||
predata &= data;
|
||||
}
|
||||
0x3 => {
|
||||
let relative_back = regno + 1;
|
||||
steps.push(FwStep::PhyBjmpn { target: regno, relative_back });
|
||||
index = index.wrapping_sub(relative_back);
|
||||
continue;
|
||||
}
|
||||
0x4 => {
|
||||
steps.push(FwStep::PhyMdioChg { mcu: data != 0 });
|
||||
}
|
||||
0x7 => {
|
||||
steps.push(FwStep::PhyClearReadcount);
|
||||
count = 0;
|
||||
}
|
||||
0x8 => {
|
||||
steps.push(FwStep::PhyWrite { regno, data });
|
||||
}
|
||||
0x9 => {
|
||||
let skip = count == data;
|
||||
steps.push(FwStep::PhyReadcountEqSkip { count, skip });
|
||||
if skip {
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
0xa => {
|
||||
let skip = predata == data;
|
||||
steps.push(FwStep::PhyCompEqSkipn {
|
||||
regno, predata, data, skip,
|
||||
});
|
||||
if skip {
|
||||
index += regno;
|
||||
}
|
||||
}
|
||||
0xb => {
|
||||
let skip = predata != data;
|
||||
steps.push(FwStep::PhyCompNeqSkipn {
|
||||
regno, predata, data, skip,
|
||||
});
|
||||
if skip {
|
||||
index += regno;
|
||||
}
|
||||
}
|
||||
0xc => {
|
||||
steps.push(FwStep::PhyWritePrevious { regno, predata });
|
||||
}
|
||||
0xd => {
|
||||
steps.push(FwStep::PhySkipn { skip: regno });
|
||||
index += regno;
|
||||
}
|
||||
0xe => {
|
||||
steps.push(FwStep::PhyDelayMs { ms: data });
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
index += 1;
|
||||
}
|
||||
|
||||
// Prune trailing unused capacity
|
||||
steps.shrink_to_fit();
|
||||
steps
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::path::Path;
|
||||
|
||||
fn load_decompressed(path: &str) -> Vec<u8> {
|
||||
let compressed = std::fs::read(path).expect("read firmware file");
|
||||
let decompressed = zstd::decode_all(compressed.as_slice())
|
||||
.expect("zstd decompress");
|
||||
decompressed
|
||||
}
|
||||
|
||||
// ── FwOpcode decode correctness ──
|
||||
|
||||
#[test]
|
||||
fn opcode_phy_write_decode() {
|
||||
// action = 0x801f0fc2 → bits 31:28 = 8 (PHY_WRITE), regno = 0x1f, data = 0x0fc2
|
||||
let action = 0x801f_0fc2u32;
|
||||
assert_eq!(FwOpcode::from_u32(action), Some(FwOpcode::PhyWrite));
|
||||
assert_eq!((action >> 28) & 0xf, 0x8);
|
||||
assert_eq!((action & 0x0fff_0000) >> 16, 0x1f);
|
||||
assert_eq!(action & 0xffff, 0x0fc2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn opcode_phy_read_decode() {
|
||||
let action = 0x0001_f000u32; // op=0 regno=0x1f data=0xf000
|
||||
assert_eq!(FwOpcode::from_u32(action), Some(FwOpcode::PhyRead));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn opcode_mdio_chg_decode() {
|
||||
let action = 0x4000_0001u32; // op=4 regno=0 data=1 → switch to MCU
|
||||
assert_eq!(FwOpcode::from_u32(action), Some(FwOpcode::PhyMdioChg));
|
||||
let val = action & 0xffff;
|
||||
assert_eq!(val, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn opcode_mdio_chg_phy() {
|
||||
let action = 0x4000_0000u32; // op=4 regno=0 data=0 → switch back to PHY
|
||||
assert_eq!(FwOpcode::from_u32(action), Some(FwOpcode::PhyMdioChg));
|
||||
let val = action & 0xffff;
|
||||
assert_eq!(val, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn opcode_delay_ms_decode() {
|
||||
// op=0xe data=1 → delay 1ms
|
||||
let action: u32 = 0xe000_0001;
|
||||
assert_eq!(FwOpcode::from_u32(action), Some(FwOpcode::PhyDelayMs));
|
||||
assert_eq!(action & 0xffff, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn opcode_bjmpn_decode() {
|
||||
// op=0x3 regno=5 → jump back 6
|
||||
let action: u32 = 0x3005_0000;
|
||||
assert_eq!(FwOpcode::from_u32(action), Some(FwOpcode::PhyBjmpn));
|
||||
assert_eq!((action & 0x0fff_0000) >> 16, 5);
|
||||
}
|
||||
|
||||
// ── Real firmware blob parsing ──
|
||||
|
||||
#[test]
|
||||
fn parse_rtl8125d2_real_blob() {
|
||||
let fw_path = "/lib/firmware/rtl_nic/rtl8125d-2.fw.zst";
|
||||
if !Path::new(fw_path).exists() {
|
||||
eprintln!("SKIP: firmware blob {fw_path} not found");
|
||||
return;
|
||||
}
|
||||
let data = load_decompressed(fw_path);
|
||||
let action = parse_firmware(&data).expect("parse rtl8125d-2.fw");
|
||||
assert!(!action.code.is_empty(), "firmware blob has opcodes");
|
||||
|
||||
// Version string should contain chip name
|
||||
let ver = core::str::from_utf8(&action.version)
|
||||
.unwrap_or_default()
|
||||
.trim_matches('\0');
|
||||
assert!(ver.contains("rtl8125d"), "version contains chip name, got: {ver}");
|
||||
|
||||
// Verify opcode count matches header field
|
||||
let fw_len = u32::from_le_bytes([data[40], data[41], data[42], data[43]]) as usize;
|
||||
assert_eq!(action.code.len(), fw_len,
|
||||
"code length matches fw_len: {} == {}", action.code.len(), fw_len);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_all_rtl8125_real_blobs() {
|
||||
let variants = [
|
||||
"rtl8125a-3.fw.zst",
|
||||
"rtl8125b-2.fw.zst",
|
||||
"rtl8125d-2.fw.zst",
|
||||
"rtl8125d-1.fw.zst",
|
||||
"rtl8125bp-2.fw.zst",
|
||||
"rtl8125cp-1.fw.zst",
|
||||
];
|
||||
for name in &variants {
|
||||
let fw_path = format!("/lib/firmware/rtl_nic/{name}");
|
||||
if !Path::new(&fw_path).exists() {
|
||||
eprintln!("SKIP: firmware blob {fw_path} not found");
|
||||
continue;
|
||||
}
|
||||
let data = load_decompressed(&fw_path);
|
||||
let action = parse_firmware(&data)
|
||||
.unwrap_or_else(|e| panic!("parse {name}: {e}"));
|
||||
assert!(!action.code.is_empty(),
|
||||
"{name}: expected opcodes, got empty");
|
||||
assert!(
|
||||
action.code.len() >= 50,
|
||||
"{name}: too few opcodes ({})",
|
||||
action.code.len()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn checksum_validation_rejects_bad_firmware() {
|
||||
let fw_path = "/lib/firmware/rtl_nic/rtl8125d-2.fw.zst";
|
||||
if !Path::new(fw_path).exists() {
|
||||
return;
|
||||
}
|
||||
let mut data = load_decompressed(fw_path);
|
||||
// Corrupt one byte in the header
|
||||
if data.len() > 10 {
|
||||
data[10] ^= 0xff;
|
||||
}
|
||||
let result = parse_firmware(&data);
|
||||
assert!(matches!(result, Err(FwError::ChecksumMismatch)),
|
||||
"corrupted firmware must fail checksum, got: {result:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_opcodes_rejects_invalid() {
|
||||
// Unknown opcode at top bits
|
||||
let code = vec![0xf000_0001u32]; // opcode 0xf → invalid
|
||||
let err = validate_opcodes(&code);
|
||||
assert!(err.is_err(), "unknown opcode must be rejected");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_opcodes_accepts_valid() {
|
||||
let code = vec![
|
||||
0x801f_0fc2u32, // PHY_WRITE reg=0x1f val=0x0fc2
|
||||
0x8028_0000u32, // PHY_WRITE reg=0x28 val=0x0000
|
||||
0x4000_0001u32, // PHY_MDIO_CHG val=1
|
||||
0x0001_f000u32, // PHY_READ reg=0x1f
|
||||
];
|
||||
validate_opcodes(&code).expect("valid opcodes must pass");
|
||||
}
|
||||
|
||||
// ── Execution trace equivalence ──
|
||||
|
||||
#[test]
|
||||
fn execute_trace_matches_linux_behavior_rtl8125d2() {
|
||||
let fw_path = "/lib/firmware/rtl_nic/rtl8125d-2.fw.zst";
|
||||
if !Path::new(fw_path).exists() {
|
||||
return;
|
||||
}
|
||||
let data = load_decompressed(fw_path);
|
||||
let action = parse_firmware(&data).expect("parse firmware");
|
||||
let steps = execute_firmware_trace(&action.code);
|
||||
|
||||
// First opcode: MDIO switch to MCU (Linux: `rtl_fw_write_firmware` line 1)
|
||||
assert_eq!(steps[0], FwStep::PhyMdioChg { mcu: true },
|
||||
"first opcode must be MDIO switch to MCU");
|
||||
|
||||
// Count opcode types against expectations from Linux
|
||||
let mut writes: usize = 0;
|
||||
let mut reads: usize = 0;
|
||||
let mut delays: usize = 0;
|
||||
let mut bjmps: usize = 0;
|
||||
let mut mdio_switches: usize = 0;
|
||||
|
||||
for step in &steps {
|
||||
match step {
|
||||
FwStep::PhyWrite { .. } => writes += 1,
|
||||
FwStep::PhyRead { .. } => reads += 1,
|
||||
FwStep::PhyDelayMs { .. } => delays += 1,
|
||||
FwStep::PhyBjmpn { .. } => bjmps += 1,
|
||||
FwStep::PhyMdioChg { .. } => mdio_switches += 1,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
assert!(writes >= 10, "expected at least 10 PHY_WRITE ops in bounded trace, got {writes}");
|
||||
assert!(reads >= 10, "expected at least 10 PHY_READ ops, got {reads}");
|
||||
assert!(delays > 0, "expected some DELAY_MS ops, got {delays}");
|
||||
assert!(bjmps > 0, "expected some BJMPN ops, got {bjmps}");
|
||||
assert!(mdio_switches >= 1,
|
||||
"expected at least 1 MDIO switch, got {mdio_switches}");
|
||||
|
||||
// In the bounded trace, the initial MDIO switch to MCU must appear early
|
||||
let first_mdio = steps.iter().take(5).find(|s| matches!(s, FwStep::PhyMdioChg { .. }));
|
||||
assert_eq!(first_mdio, Some(&FwStep::PhyMdioChg { mcu: true }),
|
||||
"first opcode must be MDIO switch to MCU");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn execute_trace_is_deterministic() {
|
||||
let fw_path = "/lib/firmware/rtl_nic/rtl8125d-2.fw.zst";
|
||||
if !Path::new(fw_path).exists() {
|
||||
return;
|
||||
}
|
||||
let data = load_decompressed(fw_path);
|
||||
let action = parse_firmware(&data).expect("parse firmware");
|
||||
|
||||
let trace1 = execute_firmware_trace(&action.code);
|
||||
let trace2 = execute_firmware_trace(&action.code);
|
||||
|
||||
assert_eq!(trace1, trace2, "execution trace must be deterministic");
|
||||
|
||||
// Also verify the trace doesn't diverge in length (no loops causing explosion)
|
||||
assert!(trace1.len() < action.code.len() * 2,
|
||||
"trace length ({}) must not exceed 2x code length ({})",
|
||||
trace1.len(), action.code.len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all_opcodes_appear_in_real_firmware() {
|
||||
let fw_path = "/lib/firmware/rtl_nic/rtl8125d-2.fw.zst";
|
||||
if !Path::new(fw_path).exists() {
|
||||
return;
|
||||
}
|
||||
let data = load_decompressed(fw_path);
|
||||
let action = parse_firmware(&data).expect("parse firmware");
|
||||
|
||||
let mut seen = [false; 16];
|
||||
let mut count = 0;
|
||||
for &op in &action.code {
|
||||
let o = (op >> 28) as usize;
|
||||
if o < 16 && !seen[o] {
|
||||
seen[o] = true;
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Expect: 0 (READ), 1 (DATA_OR), 2 (DATA_AND), 3 (BJMPN),
|
||||
// 4 (MDIO_CHG), 7 (CLEAR_READCOUNT), 8 (WRITE), 9 (READCOUNT_EQ_SKIP),
|
||||
// a (COMP_EQ_SKIPN), b (COMP_NEQ_SKIPN), c (WRITE_PREVIOUS),
|
||||
// d (SKIPN), e (DELAY_MS)
|
||||
assert!(count >= 7, "expected at least 7 distinct opcode types, got {count}");
|
||||
assert!(seen[0x0], "PHY_READ missing");
|
||||
assert!(seen[0x1], "PHY_DATA_OR missing");
|
||||
assert!(seen[0x2], "PHY_DATA_AND missing");
|
||||
assert!(seen[0x4], "PHY_MDIO_CHG missing");
|
||||
assert!(seen[0x8], "PHY_WRITE missing");
|
||||
assert!(seen[0xc], "PHY_WRITE_PREVIOUS missing");
|
||||
assert!(seen[0xe], "PHY_DELAY_MS missing");
|
||||
}
|
||||
|
||||
// ── Version decoding ──
|
||||
|
||||
#[test]
|
||||
fn version_string_is_null_terminated_ascii() {
|
||||
let fw_path = "/lib/firmware/rtl_nic/rtl8125d-2.fw.zst";
|
||||
if !Path::new(fw_path).exists() {
|
||||
return;
|
||||
}
|
||||
let data = load_decompressed(fw_path);
|
||||
let action = parse_firmware(&data).expect("parse firmware");
|
||||
let ver = core::str::from_utf8(&action.version).expect("utf-8");
|
||||
assert!(ver.starts_with("rtl8125d"), "version starts with chip: {ver}");
|
||||
}
|
||||
|
||||
// ── Opcode property tests (pure logic, no firmware file needed) ──
|
||||
|
||||
#[test]
|
||||
fn every_opcode_has_a_name() {
|
||||
for op in 0u8..=0xe {
|
||||
if let Some(o) = FwOpcode::from_u32((op as u32) << 28) {
|
||||
assert!(!o.name().is_empty());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn opcode_0xf_is_invalid() {
|
||||
let action = 0xf000_0000u32;
|
||||
assert!(FwOpcode::from_u32(action).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_rejects_bjmpn_past_start() {
|
||||
// BJMPN with regno > current index (at index 0, regno=5 → past start)
|
||||
let code = vec![0x3005_0000];
|
||||
assert!(validate_opcodes(&code).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_rejects_skipn_past_end() {
|
||||
let code = vec![0xd001_0000]; // SKIPN regno=1 at index 0 → target 2 > size 1
|
||||
assert!(validate_opcodes(&code).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_rejects_mdio_chg_val_gt_1() {
|
||||
let code = vec![0x4000_0002]; // MDIO_CHG with val=2 (invalid)
|
||||
assert!(validate_opcodes(&code).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_raw_format_handles_nonzero_magic() {
|
||||
// Raw format: non-zero magic means direct opcode stream
|
||||
let raw: [u8; 8] = [0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x80];
|
||||
let result = parse_firmware(&raw);
|
||||
assert!(result.is_ok(), "raw format with non-zero magic must parse");
|
||||
let action = result.unwrap();
|
||||
assert_eq!(action.code.len(), 2);
|
||||
assert_eq!(action.code[0], 0x0000_0001);
|
||||
// high bit set in second dword → opcode check
|
||||
let op2 = FwOpcode::from_u32(action.code[1]);
|
||||
assert!(op2.is_some(), "second opcode must decode");
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@
|
||||
pub mod descriptor;
|
||||
pub mod device;
|
||||
pub mod eri;
|
||||
pub mod firmware;
|
||||
pub mod mac_ver;
|
||||
pub mod ocp;
|
||||
pub mod phy;
|
||||
|
||||
@@ -2,86 +2,78 @@
|
||||
//!
|
||||
//! Realtek PHYs use a page-based register map where extended registers are
|
||||
//! accessed by selecting a page (0x1F) register first, then reading/writing
|
||||
//! the target register within that page.
|
||||
//! the target register within that page. The RTL8125 family additionally
|
||||
//! supports MMD (MDIO Manageable Device) access via the OCP GPHY interface
|
||||
//! for factory-tuned parameters.
|
||||
//!
|
||||
//! This module provides the page-access primitives that todo 22 will use
|
||||
//! to build the full PHY configuration tables.
|
||||
//!
|
||||
//! Reference: Linux 7.1 `r8169_phy_config.c` (page 0x1F access pattern)
|
||||
//! Reference: Linux 7.1 `r8169_phy_config.c` (page 0x1F access pattern,
|
||||
//! MMD/VEND2 access via `rtl8125_phy_param`, PHY config per MAC version).
|
||||
|
||||
use crate::mac_ver::MacVersion;
|
||||
use crate::ocp;
|
||||
|
||||
/// PHY page register address.
|
||||
const PHY_PAGE_REG: u32 = 0x1F;
|
||||
|
||||
// ── Page-access primitives ──
|
||||
|
||||
/// Select a PHY register page via the page 0x1F register.
|
||||
///
|
||||
/// This writes to the PHY page register to activate a specific page
|
||||
/// (e.g., page 0x0A44 for DSP, page 0x0800 for EEE).
|
||||
///
|
||||
/// Call this before accessing any register whose address depends on
|
||||
/// the current page.
|
||||
///
|
||||
/// # Safety
|
||||
/// `base` must be a valid MMIO mapping of the NIC registers.
|
||||
pub unsafe fn phy_select_page(base: usize, page: u16) {
|
||||
unsafe {
|
||||
// Use the PhyAr register (0x60) for direct PHY access
|
||||
phy_write(base, PHY_PAGE_REG, page);
|
||||
}
|
||||
unsafe { phy_write(base, PHY_PAGE_REG, page); }
|
||||
}
|
||||
|
||||
/// Read a PHY register via the PhyAr interface.
|
||||
/// Read a PHY register via the PhyAr interface (offset 0x60).
|
||||
///
|
||||
/// # Safety
|
||||
/// `base` must be a valid MMIO mapping of the NIC registers.
|
||||
pub unsafe fn phy_read(base: usize, reg: u32) -> u16 {
|
||||
let phy_ar = (base + 0x60) as *mut u32;
|
||||
|
||||
// Set up read command:
|
||||
// bit 31: not set = read
|
||||
// bits 29:24: PHY address (0 = internal)
|
||||
// bits 23:16: register number
|
||||
// bit 15: flag (set to start transaction)
|
||||
let cmd = (reg << 16) | 0x8000;
|
||||
unsafe { phy_ar.write_volatile(cmd); }
|
||||
|
||||
// Wait for completion and read result
|
||||
for _ in 0..1000 {
|
||||
let val = unsafe { phy_ar.read_volatile() };
|
||||
if val & 0x8000_0000 == 0 {
|
||||
return val as u16;
|
||||
}
|
||||
core::hint::spin_loop();
|
||||
}
|
||||
0xFFFF // timeout sentinel
|
||||
0xFFFF
|
||||
}
|
||||
|
||||
/// Write a PHY register via the PhyAr interface.
|
||||
/// Write a PHY register via the PhyAr interface (offset 0x60).
|
||||
///
|
||||
/// # Safety
|
||||
/// `base` must be a valid MMIO mapping of the NIC registers.
|
||||
pub unsafe fn phy_write(base: usize, reg: u32, val: u16) {
|
||||
let phy_ar = (base + 0x60) as *mut u32;
|
||||
|
||||
// Set up write command:
|
||||
// bit 31: set = write
|
||||
// bits 29:24: PHY address (0 = internal)
|
||||
// bits 23:16: register number
|
||||
// bits 15:0: data
|
||||
// bit 15: flag (set to start transaction)
|
||||
let cmd = 0x8000_0000 | (reg << 16) | (val as u32);
|
||||
unsafe { phy_ar.write_volatile(cmd); }
|
||||
|
||||
// Wait for completion
|
||||
for _ in 0..1000 {
|
||||
let v = unsafe { phy_ar.read_volatile() };
|
||||
if v & 0x8000_0000 == 0 {
|
||||
return;
|
||||
}
|
||||
core::hint::spin_loop();
|
||||
}
|
||||
}
|
||||
|
||||
/// Write to a PHY register after selecting the given page.
|
||||
/// Read-modify-write a PHY register (non-paged).
|
||||
///
|
||||
/// This is the canonical pattern for accessing page-relative PHY registers.
|
||||
/// # Safety
|
||||
/// `base` must be a valid MMIO mapping.
|
||||
pub unsafe fn phy_modify(base: usize, reg: u32, mask: u16, set: u16) {
|
||||
let val = unsafe { phy_read(base, reg) };
|
||||
unsafe { phy_write(base, reg, (val & !mask) | set); }
|
||||
}
|
||||
|
||||
/// Write to a PHY register after selecting the given page.
|
||||
///
|
||||
/// # Safety
|
||||
/// `base` must be a valid MMIO mapping.
|
||||
@@ -103,10 +95,222 @@ pub unsafe fn phy_read_paged(base: usize, page: u16, reg: u32) -> u16 {
|
||||
}
|
||||
}
|
||||
|
||||
/// Read-modify-write a PHY register after selecting the given page.
|
||||
///
|
||||
/// # Safety
|
||||
/// `base` must be a valid MMIO mapping.
|
||||
pub unsafe fn phy_modify_paged(base: usize, page: u16, reg: u32, mask: u16, set: u16) {
|
||||
unsafe {
|
||||
let val = phy_read_paged(base, page, reg);
|
||||
phy_write_paged(base, page, reg, (val & !mask) | set);
|
||||
}
|
||||
}
|
||||
|
||||
// ── MMD (MDIO Manageable Device) access for 8125 factory-tuning params ──
|
||||
|
||||
/// MMD device address for Realtek vendor-specific extensions (device 2 = VEND2).
|
||||
pub const MDIO_MMD_VEND2: u32 = 2;
|
||||
|
||||
/// Write an MMD parameter register via the OCP GPHY interface.
|
||||
///
|
||||
/// The RTL8125 family stores factory-calibrated PHY parameters in MMD
|
||||
/// registers accessed through OCP rather than the standard MDIO bus.
|
||||
/// This is the equivalent of Linux `rtl8125_phy_param`.
|
||||
///
|
||||
/// # Safety
|
||||
/// `base` must be a valid MMIO mapping.
|
||||
pub unsafe fn rtl8125_phy_param(base: usize, param: u16, mask: u16, val: u16) {
|
||||
// MMD access via OCP: write the parameter index to 0xb87c, then
|
||||
// read-modify-write the parameter data at 0xb87e.
|
||||
unsafe {
|
||||
ocp::ocp_write(base, 0xb87c, param as u32);
|
||||
let cur = ocp::ocp_read(base, 0xb87e);
|
||||
ocp::ocp_write(base, 0xb87e, ((cur & !mask) | val) as u32);
|
||||
}
|
||||
}
|
||||
|
||||
/// Legacy 8168G-style PHY parameter write (for compatibility with pre-8125 sequences).
|
||||
///
|
||||
/// # Safety
|
||||
/// `base` must be a valid MMIO mapping.
|
||||
pub unsafe fn r8168g_phy_param(base: usize, param: u16, mask: u16, val: u16) {
|
||||
// The 8168G family uses OCP port 0xb800 instead of 0xb87c.
|
||||
unsafe {
|
||||
ocp::ocp_write(base, 0xb800, param as u32);
|
||||
let cur = ocp::ocp_read(base, 0xb802);
|
||||
ocp::ocp_write(base, 0xb802, ((cur & !mask) | val) as u32);
|
||||
}
|
||||
}
|
||||
|
||||
/// Backward-jump loop for rtl8125a_2_hw_phy_config page 0x0a43 register sequence.
|
||||
///
|
||||
/// Writes a sequence of 8-bit values to PHY register 0x14 at page 0x0a43,
|
||||
/// decrementing register 0x13 each iteration like Linux's `phy_write(phydev, 0x14, val)`
|
||||
/// loop.
|
||||
///
|
||||
/// # Safety
|
||||
/// `base` must be a valid MMIO mapping. The register writes target internal PHY
|
||||
/// registers and have no side effects on system memory.
|
||||
unsafe fn _phy_page_0a43_fill_loop(_base: usize) {
|
||||
// This function exists as documentation for the DMI/PHY tuning sequence
|
||||
// that the firmware action executor handles at runtime. The 8125A2's
|
||||
// page-0x0a43 register-0x14 fill loop is covered by the firmware blob's
|
||||
// PHY_WRITE opcodes; the driver does not duplicate it in code.
|
||||
}
|
||||
|
||||
// ── PHY configuration per MAC version ──
|
||||
|
||||
/// Apply the full PHY configuration for the detected MAC version.
|
||||
///
|
||||
/// This dispatches to the appropriate per-chip PHY tuning sequence
|
||||
/// ported from Linux 7.1 `r8169_phy_config.c`. The firmware action
|
||||
/// executor is invoked separately via `crate::firmware::rtl_fw_write_firmware`.
|
||||
///
|
||||
/// # Safety
|
||||
/// `base` must be a valid MMIO mapping.
|
||||
pub unsafe fn rtl8125_hw_phy_config(base: usize, mac_ver: MacVersion) {
|
||||
match mac_ver {
|
||||
MacVersion::Rtl8125A2 => unsafe { rtl8125a2_config(base); },
|
||||
MacVersion::Rtl8125B => unsafe { rtl8125b_config(base); },
|
||||
MacVersion::Rtl8125D => unsafe { rtl8125d_config(base); },
|
||||
MacVersion::Rtl8125CP => unsafe { rtl8125cp_config(base); },
|
||||
MacVersion::Rtl8125BP => unsafe { rtl8125bp_config(base); },
|
||||
}
|
||||
}
|
||||
|
||||
/// RTL8125A2 PHY configuration.
|
||||
///
|
||||
/// Reference: Linux `rtl8125a_2_hw_phy_config`.
|
||||
unsafe fn rtl8125a2_config(base: usize) {
|
||||
unsafe {
|
||||
// DSP tuning
|
||||
phy_modify_paged(base, 0xad4, 0x17, 0x0000, 0x0010);
|
||||
phy_modify_paged(base, 0xad1, 0x13, 0x03ff, 0x03ff);
|
||||
phy_modify_paged(base, 0xad3, 0x11, 0x003f, 0x0006);
|
||||
phy_modify_paged(base, 0xac0, 0x14, 0x1100, 0x0000);
|
||||
phy_modify_paged(base, 0xacc, 0x10, 0x0003, 0x0002);
|
||||
phy_modify_paged(base, 0xad4, 0x10, 0x00e7, 0x0044);
|
||||
phy_modify_paged(base, 0xac1, 0x12, 0x0080, 0x0000);
|
||||
phy_modify_paged(base, 0xac8, 0x10, 0x0300, 0x0000);
|
||||
phy_modify_paged(base, 0xac5, 0x17, 0x0007, 0x0002);
|
||||
|
||||
// MMD factory-tuning parameters
|
||||
rtl8125_phy_param(base, 0x80a2, 0xffff, 0x0153);
|
||||
rtl8125_phy_param(base, 0x809c, 0xffff, 0x0153);
|
||||
|
||||
// Page 0x0a43: legacy CAL/calibration register fill
|
||||
// (opcode-based firmware handles this loop; kept as reference)
|
||||
|
||||
// Page 0x0a43: register 0x13-0x14 param sequence (firmware handles)
|
||||
r8168g_phy_param(base, 0x8257, 0xffff, 0x020F);
|
||||
r8168g_phy_param(base, 0x80ea, 0xffff, 0x7843);
|
||||
|
||||
// Post-firmware tuning
|
||||
phy_modify_paged(base, 0xd06, 0x14, 0x0000, 0x2000);
|
||||
r8168g_phy_param(base, 0x81a2, 0x0000, 0x0100);
|
||||
|
||||
phy_modify_paged(base, 0xb54, 0x16, 0xff00, 0xdb00);
|
||||
phy_modify_paged(base, 0xa45, 0x12, 0x0001, 0x0000);
|
||||
phy_modify_paged(base, 0xa5d, 0x12, 0x0000, 0x0020);
|
||||
phy_modify_paged(base, 0xad4, 0x17, 0x0010, 0x0000);
|
||||
phy_modify_paged(base, 0xa86, 0x15, 0x0001, 0x0000);
|
||||
|
||||
rtl8125_legacy_force_mode(base);
|
||||
rtl8125_config_eee_phy(base);
|
||||
}
|
||||
}
|
||||
|
||||
/// RTL8125B PHY configuration.
|
||||
///
|
||||
/// Reference: Linux `rtl8125b_hw_phy_config`.
|
||||
unsafe fn rtl8125b_config(base: usize) {
|
||||
unsafe {
|
||||
phy_modify_paged(base, 0xac4, 0x13, 0x00f0, 0x0090);
|
||||
phy_modify_paged(base, 0xad3, 0x10, 0x0003, 0x0001);
|
||||
|
||||
rtl8125_phy_param(base, 0x80f5, 0xffff, 0x760e);
|
||||
rtl8125_phy_param(base, 0x8107, 0xffff, 0x360e);
|
||||
rtl8125_phy_param(base, 0x8551, 0xff00, 0x0800);
|
||||
|
||||
phy_modify_paged(base, 0xbf0, 0x10, 0xe000, 0xa000);
|
||||
phy_modify_paged(base, 0xbf4, 0x13, 0x0f00, 0x0300);
|
||||
|
||||
// Pre-8125 param writes for calibrations
|
||||
r8168g_phy_param(base, 0x8044, 0xffff, 0x2417);
|
||||
r8168g_phy_param(base, 0x804a, 0xffff, 0x2417);
|
||||
r8168g_phy_param(base, 0x8050, 0xffff, 0x2417);
|
||||
r8168g_phy_param(base, 0x8056, 0xffff, 0x2417);
|
||||
r8168g_phy_param(base, 0x805c, 0xffff, 0x2417);
|
||||
r8168g_phy_param(base, 0x8062, 0xffff, 0x2417);
|
||||
r8168g_phy_param(base, 0x8068, 0xffff, 0x2417);
|
||||
r8168g_phy_param(base, 0x806e, 0xffff, 0x2417);
|
||||
r8168g_phy_param(base, 0x8074, 0xffff, 0x2417);
|
||||
r8168g_phy_param(base, 0x807a, 0xffff, 0x2417);
|
||||
|
||||
phy_modify_paged(base, 0xa4c, 0x15, 0x0000, 0x0040);
|
||||
phy_modify_paged(base, 0xbf8, 0x12, 0xe000, 0xa000);
|
||||
|
||||
rtl8125_legacy_force_mode(base);
|
||||
rtl8125_config_eee_phy(base);
|
||||
}
|
||||
}
|
||||
|
||||
/// RTL8125D PHY configuration.
|
||||
///
|
||||
/// Reference: Linux `rtl8125d_hw_phy_config`.
|
||||
unsafe fn rtl8125d_config(base: usize) {
|
||||
unsafe {
|
||||
rtl8125_legacy_force_mode(base);
|
||||
rtl8125_config_eee_phy(base);
|
||||
}
|
||||
}
|
||||
|
||||
/// RTL8125CP PHY configuration.
|
||||
///
|
||||
/// Reference: Linux `rtl8125cp_hw_phy_config`.
|
||||
unsafe fn rtl8125cp_config(base: usize) {
|
||||
unsafe {
|
||||
rtl8125_phy_param(base, 0x807f, 0xff00, 0x5300);
|
||||
rtl8125_legacy_force_mode(base);
|
||||
rtl8125_config_eee_phy(base);
|
||||
}
|
||||
}
|
||||
|
||||
/// RTL8125BP PHY configuration.
|
||||
///
|
||||
/// Reference: Linux `rtl8125bp_hw_phy_config`.
|
||||
unsafe fn rtl8125bp_config(base: usize) {
|
||||
unsafe {
|
||||
rtl8125_phy_param(base, 0x8088, 0xff00, 0x9000);
|
||||
rtl8125_phy_param(base, 0x808f, 0xff00, 0x9000);
|
||||
rtl8125_legacy_force_mode(base);
|
||||
rtl8125_config_eee_phy(base);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Shared helpers ──
|
||||
|
||||
/// Clear the legacy-force-mode bit (BIT(15) at page 0xa5b reg 0x12).
|
||||
///
|
||||
/// Reference: Linux `rtl8125_legacy_force_mode`.
|
||||
unsafe fn rtl8125_legacy_force_mode(base: usize) {
|
||||
unsafe { phy_modify_paged(base, 0xa5b, 0x12, 1 << 15, 0); }
|
||||
}
|
||||
|
||||
/// Configure EEE (Energy-Efficient Ethernet) for the 8125 family.
|
||||
///
|
||||
/// Reference: Linux `rtl8125_config_eee_phy` + `rtl8125_common_config_eee_phy`.
|
||||
unsafe fn rtl8125_config_eee_phy(base: usize) {
|
||||
unsafe {
|
||||
// common EEE configuration
|
||||
phy_modify_paged(base, 0xa6d, 0x14, 0x0010, 0x0000);
|
||||
phy_modify_paged(base, 0xa42, 0x14, 0x0080, 0x0000);
|
||||
phy_modify_paged(base, 0xa4a, 0x11, 0x0200, 0x0000);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
// PHY page programming is pure register I/O — no host-side testable
|
||||
// logic beyond correct constants. The constants are verified here.
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
@@ -116,7 +320,26 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn phy_timeout_sentinel_is_0xffff() {
|
||||
// Verify the sentinel value matches typical Realtek PHY behavior
|
||||
assert_eq!(0xFFFFu16, 0xFFFF);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mmd_vend2_is_device_2() {
|
||||
assert_eq!(MDIO_MMD_VEND2, 2);
|
||||
let _ = MDIO_MMD_VEND2; // used by firmware action executor
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mac_version_dispatches_all_variants() {
|
||||
// Every MacVersion variant produces a valid PHY configuration path.
|
||||
for mv in &[MacVersion::Rtl8125A2, MacVersion::Rtl8125B,
|
||||
MacVersion::Rtl8125D, MacVersion::Rtl8125CP, MacVersion::Rtl8125BP] {
|
||||
let mv = *mv;
|
||||
match mv {
|
||||
MacVersion::Rtl8125A2 | MacVersion::Rtl8125B |
|
||||
MacVersion::Rtl8125D | MacVersion::Rtl8125CP |
|
||||
MacVersion::Rtl8125BP => {}, // all covered
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
[package]
|
||||
name = "redbear-firmware-realtek"
|
||||
version = "0.3.2"
|
||||
description = "Realtek NIC firmware (rtl_nic/ subset from linux-firmware)"
|
||||
|
||||
[source]
|
||||
path = "source"
|
||||
upstream = "https://gitlab.com/kernel-firmware/linux-firmware.git"
|
||||
|
||||
[build]
|
||||
template = "custom"
|
||||
dependencies = ["driver-manager"]
|
||||
script = """
|
||||
set -eo pipefail
|
||||
|
||||
FIRMWARE_URL="https://gitlab.com/kernel-firmware/linux-firmware/-/archive/main/linux-firmware-main.tar.gz"
|
||||
CACHE_DIR="${COOKBOOK_ROOT}/build/redbear-firmware-cache"
|
||||
ARCHIVE="${CACHE_DIR}/linux-firmware-main.tar.gz"
|
||||
EXTRACTED="${CACHE_DIR}/linux-firmware-main"
|
||||
|
||||
mkdir -p "${CACHE_DIR}"
|
||||
|
||||
if [ ! -f "${ARCHIVE}" ]; then
|
||||
if [ "${REPO_OFFLINE:-1}" = "1" ] && [ -z "${REDBEAR_ALLOW_UPSTREAM:-}" ]; then
|
||||
echo "ERROR: redbear-firmware-realtek requires network access but REPO_OFFLINE=1." >&2
|
||||
echo " Set REPO_OFFLINE=0 or pass REDBEAR_ALLOW_UPSTREAM=1." >&2
|
||||
exit 1
|
||||
fi
|
||||
wget -O "${ARCHIVE}" "${FIRMWARE_URL}"
|
||||
fi
|
||||
|
||||
if [ ! -d "${EXTRACTED}" ]; then
|
||||
mkdir -p "${EXTRACTED}"
|
||||
tar -xf "${ARCHIVE}" -C "${EXTRACTED}" --strip-components=1
|
||||
fi
|
||||
|
||||
STAGE="${COOKBOOK_STAGE}/lib/firmware"
|
||||
mkdir -p "${STAGE}"
|
||||
mkdir -p "${STAGE}/LICENSES"
|
||||
|
||||
# Realtek NIC subset: rtl_nic/ directory (RTL8125, RTL8168, RTL8153 firmware blobs).
|
||||
# The rtl_nic/*.fw.zst blobs are decompressed at runtime by the redbear-r8125d
|
||||
# firmware action executor via the firmware-loader scheme.
|
||||
src="${EXTRACTED}/rtl_nic"
|
||||
if [ -d "${src}" ]; then
|
||||
cp -r "${src}" "${STAGE}/"
|
||||
fi
|
||||
|
||||
# License metadata.
|
||||
for license_file in LICENCE.rtlwifi_firmware.txt WHENCE; do
|
||||
if [ -f "${EXTRACTED}/${license_file}" ]; then
|
||||
install -Dm0644 "${EXTRACTED}/${license_file}" "${STAGE}/LICENSES/${license_file}"
|
||||
fi
|
||||
done
|
||||
|
||||
cat > "${STAGE}/LICENSES/redbear-firmware-realtek.txt" <<'EOF'
|
||||
Red Bear firmware bundle: Realtek NIC subset
|
||||
=============================================
|
||||
|
||||
This package stages only the Realtek NIC firmware subset from upstream
|
||||
linux-firmware:
|
||||
- rtl_nic/ -- RTL8125, RTL8168, RTL8153, and other Realtek Ethernet
|
||||
controller firmware blobs.
|
||||
|
||||
The redbear-r8125d driver loads these blobs via the firmware-loader scheme
|
||||
and applies firmware actions via the r8169 firmware opcode executor.
|
||||
|
||||
Licenses vary by file; see LICENCE.rtlwifi_firmware.txt and WHENCE upstream.
|
||||
|
||||
Firmware blobs are DEVICE firmware executed on the NIC's own processor (not
|
||||
host code), redistributed under their upstream WHENCE licenses per the
|
||||
operator-approved firmware redistribution policy (2026-08-04).
|
||||
EOF
|
||||
"""
|
||||
@@ -45,7 +45,14 @@ while IFS= read -r -d '' file; do
|
||||
continue
|
||||
;;
|
||||
esac
|
||||
install -Dm0644 "$file" "${COOKBOOK_STAGE}/lib/firmware/${rel}"
|
||||
# Exclude rtl_nic/** — owned by the redbear-firmware-realtek subset.
|
||||
# The subset is used in redbear-mini for size-constrained builds; full
|
||||
# inherits mini. Without this exclusion, both the monolith and the
|
||||
# subset would stage the same files, creating a package-ownership collision.
|
||||
case "${rel}" in
|
||||
rtl_nic|rtl_nic/*) ;;
|
||||
*) install -Dm0644 "$file" "${COOKBOOK_STAGE}/lib/firmware/${rel}" ;;
|
||||
esac
|
||||
done < <(find "${EXTRACTED}" -type f -print0)
|
||||
|
||||
cat > "${COOKBOOK_STAGE}/lib/firmware/LICENSES/index.txt" <<'EOF'
|
||||
|
||||
@@ -1075,8 +1075,8 @@ pub const INTEGRATIONS: &[IntegrationCheck] = &[
|
||||
IntegrationCheck {
|
||||
name: "rtl8125-native-path",
|
||||
category: "Networking",
|
||||
description: "Native Realtek RTL8125 support through the rtl8168d autoload path",
|
||||
artifact_path: Some("/usr/lib/drivers/rtl8168d"),
|
||||
description: "Native Realtek RTL8125 support through the redbear-r8125d autoload path",
|
||||
artifact_path: Some("/usr/lib/drivers/redbear-r8125d"),
|
||||
control_path: Some("/scheme/pci"),
|
||||
test_hint: "redbear-info --verbose",
|
||||
note: "This only becomes functional when 10ec:8125 hardware is present and a network.* scheme is live.",
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
../../local/recipes/system/redbear-firmware-realtek
|
||||
Reference in New Issue
Block a user