use alloc::format;
use alloc::vec::Vec;
#[cfg(feature = "uefi")]
use core::prelude::rust_2021::derive;
use crate::ccgx::{AppVersion, BaseVersion};
use zerocopy::byteorder::little_endian::{U16, U32};
use zerocopy::{FromBytes, KnownLayout};
use super::*;
const FW_VERSION_OFFSET: usize = 0xE0;
const SMALL_ROW: usize = 0x80;
const LARGE_ROW: usize = 0x100;
#[repr(C, packed)]
#[derive(FromBytes, KnownLayout, Debug, Copy, Clone)]
struct VersionInfo {
base_version: U32,
app_version: U32,
silicon_id: U16,
silicon_family: U16,
}
pub const CCG5_PD_LEN: usize = 0x20_000;
pub const CCG6_PD_LEN: usize = 0x20_000;
pub const CCG8_PD_LEN: usize = 0x40_000;
#[derive(Debug, PartialEq)]
pub struct PdFirmwareFile {
pub backup_fw: PdFirmware,
pub main_fw: PdFirmware,
}
#[derive(Debug, PartialEq)]
pub struct PdFirmware {
pub silicon_id: u16,
pub silicon_family: u16,
pub base_version: BaseVersion,
pub app_version: AppVersion,
pub start_row: u32,
pub size: usize,
pub row_size: usize,
}
fn read_metadata(
file_buffer: &[u8],
flash_row_size: usize,
metadata_offset: u32,
ccgx: SiliconId,
) -> Option<(u32, u32)> {
let buffer = read_256_bytes(file_buffer, metadata_offset, flash_row_size)?;
match ccgx {
SiliconId::Ccg3 => parse_metadata_ccg3(&buffer),
SiliconId::Ccg5 | SiliconId::Ccg6Adl | SiliconId::Ccg6 => parse_metadata_cyacd(&buffer),
SiliconId::Ccg8 => parse_metadata_cyacd2(&buffer)
.map(|(fw_row_start, fw_size)| (fw_row_start / (flash_row_size as u32), fw_size)),
}
}
fn read_256_bytes(file_buffer: &[u8], row_no: u32, flash_row_size: usize) -> Option<Vec<u8>> {
let file_read_pointer = (row_no as usize) * flash_row_size;
let file_len = file_buffer.len();
let read_len = if file_read_pointer + LARGE_ROW <= file_len {
LARGE_ROW
} else if file_read_pointer + SMALL_ROW <= file_len {
SMALL_ROW
} else {
return None;
};
Some(file_buffer[file_read_pointer..file_read_pointer + read_len].to_vec())
}
fn read_version(
file_buffer: &[u8],
flash_row_size: usize,
metadata_offset: u32,
ccgx: SiliconId,
) -> Option<PdFirmware> {
let (fw_row_start, fw_size) =
read_metadata(file_buffer, flash_row_size, metadata_offset, ccgx)?;
let data = read_256_bytes(file_buffer, fw_row_start, flash_row_size)?;
trace!("First row of firmware: {:X?}", data);
let data = &data[FW_VERSION_OFFSET..];
let (version_info, _) = VersionInfo::read_from_prefix(data).ok()?;
let base_version = BaseVersion::from(version_info.base_version.get());
let app_version = AppVersion::from(version_info.app_version.get());
let fw_silicon_id = version_info.silicon_id.get();
let fw_silicon_family = version_info.silicon_family.get();
Some(PdFirmware {
silicon_id: fw_silicon_id,
silicon_family: fw_silicon_family,
base_version,
app_version,
start_row: fw_row_start,
size: fw_size as usize,
row_size: flash_row_size,
})
}
pub fn read_versions(file_buffer: &[u8], ccgx: SiliconId) -> Option<PdFirmwareFile> {
let (flash_row_size, f1_metadata_row, fw2_metadata_row) = match ccgx {
SiliconId::Ccg3 => (SMALL_ROW, 0x03FF, 0x03FE),
SiliconId::Ccg5 => (LARGE_ROW, FW1_METADATA_ROW, FW2_METADATA_ROW_CCG5),
SiliconId::Ccg6Adl => (SMALL_ROW, FW1_METADATA_ROW, FW2_METADATA_ROW_CCG6),
SiliconId::Ccg6 => (SMALL_ROW, FW1_METADATA_ROW, FW2_METADATA_ROW_CCG6),
SiliconId::Ccg8 => (LARGE_ROW, FW1_METADATA_ROW_CCG8, FW2_METADATA_ROW_CCG8),
};
let backup_fw = read_version(file_buffer, flash_row_size, f1_metadata_row, ccgx)?;
let main_fw = read_version(file_buffer, flash_row_size, fw2_metadata_row, ccgx)?;
Some(PdFirmwareFile { backup_fw, main_fw })
}
pub fn print_fw(fw: &PdFirmware) {
let silicon_id = format!("{:#06x}", fw.silicon_id);
let silicon_family = format!("{:#06x}", fw.silicon_family);
println!(" Silicon ID: {:>20}", silicon_id);
println!(" Silicon Family: {:>16}", silicon_family);
println!(" Version: {:>20}", fw.app_version);
println!(" Base Ver: {:>20}", fw.base_version);
println!(" Row size: {:>20} B", fw.row_size);
println!(" Start Row: {:>20}", fw.start_row);
println!(" Rows: {:>20}", fw.size / fw.row_size);
println!(" Size: {:>20} B", fw.size);
println!(" Size: {:>20} KB", fw.size / 1024);
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ccgx::Application;
use std::fs;
use std::path::PathBuf;
#[test]
fn can_parse_ccg3_binary() {
let mut pd_bin_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
pd_bin_path.push("test_bins/dp-pd-3.0.17.100.bin");
let data = fs::read(pd_bin_path).unwrap();
let ccg3_ver = read_versions(&data, SiliconId::Ccg3);
let ccg5_ver = read_versions(&data, SiliconId::Ccg5);
let ccg6_ver = read_versions(&data, SiliconId::Ccg6);
let ccg8_ver = read_versions(&data, SiliconId::Ccg8);
assert!(ccg3_ver.is_some());
assert!(ccg5_ver.is_none());
assert!(ccg6_ver.is_none());
assert!(ccg8_ver.is_none());
assert_eq!(
ccg3_ver,
Some({
PdFirmwareFile {
backup_fw: PdFirmware {
silicon_id: 0x11AD,
silicon_family: 0x1D00,
base_version: BaseVersion {
major: 3,
minor: 0,
patch: 17,
build_number: 100,
},
app_version: AppVersion {
application: Application::AA,
major: 0,
minor: 0,
circuit: 2,
},
start_row: 48,
size: 58624,
row_size: 128,
},
main_fw: PdFirmware {
silicon_id: 0x11AD,
silicon_family: 0x1D00,
base_version: BaseVersion {
major: 3,
minor: 0,
patch: 17,
build_number: 100,
},
app_version: AppVersion {
application: Application::AA,
major: 0,
minor: 0,
circuit: 2,
},
start_row: 512,
size: 58624,
row_size: 128,
},
}
})
);
}
#[test]
fn can_parse_ccg5_binary() {
let mut pd_bin_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
pd_bin_path.push("test_bins/tgl-pd-3.8.0.bin");
let data = fs::read(pd_bin_path).unwrap();
let ccg3_ver = read_versions(&data, SiliconId::Ccg3);
let ccg5_ver = read_versions(&data, SiliconId::Ccg5);
let ccg6_ver = read_versions(&data, SiliconId::Ccg6);
let ccg8_ver = read_versions(&data, SiliconId::Ccg8);
assert!(ccg3_ver.is_none());
assert!(ccg5_ver.is_some());
assert!(ccg6_ver.is_none());
assert!(ccg8_ver.is_none());
assert_eq!(
ccg5_ver,
Some({
PdFirmwareFile {
backup_fw: PdFirmware {
silicon_id: 0x11B1,
silicon_family: 0x2100,
base_version: BaseVersion {
major: 3,
minor: 4,
patch: 0,
build_number: 2575,
},
app_version: AppVersion {
application: Application::Notebook,
major: 3,
minor: 8,
circuit: 0,
},
start_row: 163,
size: 88832,
row_size: 256,
},
main_fw: PdFirmware {
silicon_id: 0x11B1,
silicon_family: 0x2100,
base_version: BaseVersion {
major: 3,
minor: 4,
patch: 0,
build_number: 2575,
},
app_version: AppVersion {
application: Application::Notebook,
major: 3,
minor: 8,
circuit: 0,
},
start_row: 20,
size: 36352,
row_size: 256,
},
}
})
);
}
#[test]
fn can_parse_ccg6_binary_adl() {
let mut pd_bin_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
pd_bin_path.push("test_bins/adl-pd-0.1.33.bin");
let data = fs::read(pd_bin_path).unwrap();
let ccg3_ver = read_versions(&data, SiliconId::Ccg3);
let ccg5_ver = read_versions(&data, SiliconId::Ccg5);
let ccg6_ver = read_versions(&data, SiliconId::Ccg6);
let ccg8_ver = read_versions(&data, SiliconId::Ccg8);
assert!(ccg3_ver.is_none());
assert!(ccg5_ver.is_none());
assert!(ccg6_ver.is_some());
assert!(ccg8_ver.is_none());
assert_eq!(
ccg6_ver,
Some({
PdFirmwareFile {
backup_fw: PdFirmware {
silicon_id: 0x11C0,
silicon_family: 0x3000,
base_version: BaseVersion {
major: 3,
minor: 4,
patch: 0,
build_number: 425,
},
app_version: AppVersion {
application: Application::Notebook,
major: 0,
minor: 1,
circuit: 33,
},
start_row: 22,
size: 12160,
row_size: 128,
},
main_fw: PdFirmware {
silicon_id: 0x11C0,
silicon_family: 0x3000,
base_version: BaseVersion {
major: 3,
minor: 4,
patch: 0,
build_number: 425,
},
app_version: AppVersion {
application: Application::Notebook,
major: 0,
minor: 1,
circuit: 33,
},
start_row: 118,
size: 49408,
row_size: 128,
},
}
})
);
}
#[test]
fn can_parse_ccg6_binary_mtl() {
let mut pd_bin_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
pd_bin_path.push("test_bins/mtl-pd-0.0.A.bin");
let data = fs::read(pd_bin_path).unwrap();
let ccg3_ver = read_versions(&data, SiliconId::Ccg3);
let ccg5_ver = read_versions(&data, SiliconId::Ccg5);
let ccg6_ver = read_versions(&data, SiliconId::Ccg6);
let ccg8_ver = read_versions(&data, SiliconId::Ccg8);
assert!(ccg3_ver.is_none());
assert!(ccg5_ver.is_none());
assert!(ccg6_ver.is_some());
assert!(ccg8_ver.is_none());
assert_eq!(
ccg6_ver,
Some({
PdFirmwareFile {
backup_fw: PdFirmware {
silicon_id: 0x11C0,
silicon_family: 0x3000,
base_version: BaseVersion {
major: 3,
minor: 6,
patch: 0,
build_number: 115,
},
app_version: AppVersion {
application: Application::Notebook,
major: 0,
minor: 0,
circuit: 0x0A,
},
start_row: 10,
size: 12288,
row_size: 128,
},
main_fw: PdFirmware {
silicon_id: 0x11C0,
silicon_family: 0x3000,
base_version: BaseVersion {
major: 3,
minor: 6,
patch: 0,
build_number: 115,
},
app_version: AppVersion {
application: Application::Notebook,
major: 0,
minor: 0,
circuit: 0x0A,
},
start_row: 112,
size: 47744,
row_size: 128,
},
}
})
);
}
#[test]
fn can_parse_ccg6_binary_desktop() {
let mut pd_bin_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
pd_bin_path.push("test_bins/dogwood-pd-0.0E.bin");
let data = fs::read(pd_bin_path).unwrap();
let ccg3_ver = read_versions(&data, SiliconId::Ccg3);
let ccg5_ver = read_versions(&data, SiliconId::Ccg5);
let ccg6_ver = read_versions(&data, SiliconId::Ccg6);
let ccg8_ver = read_versions(&data, SiliconId::Ccg8);
assert!(ccg3_ver.is_none());
assert!(ccg5_ver.is_none());
assert!(ccg6_ver.is_some());
assert!(ccg8_ver.is_none());
assert_eq!(
ccg6_ver,
Some({
PdFirmwareFile {
backup_fw: PdFirmware {
silicon_id: 0x11C0,
silicon_family: 0x3000,
base_version: BaseVersion {
major: 3,
minor: 7,
patch: 0,
build_number: 159,
},
app_version: AppVersion {
application: Application::Notebook,
major: 0,
minor: 0,
circuit: 0x0E,
},
start_row: 10,
size: 9344,
row_size: 128,
},
main_fw: PdFirmware {
silicon_id: 0x11C0,
silicon_family: 0x3000,
base_version: BaseVersion {
major: 3,
minor: 7,
patch: 0,
build_number: 159,
},
app_version: AppVersion {
application: Application::Notebook,
major: 0,
minor: 0,
circuit: 0x0E,
},
start_row: 112,
size: 50816,
row_size: 128,
},
}
})
);
}
#[test]
fn can_parse_ccg8_binary() {
let mut pd_bin_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
pd_bin_path.push("test_bins/fl16-pd-0.0.03.bin");
let data = fs::read(pd_bin_path).unwrap();
let ccg3_ver = read_versions(&data, SiliconId::Ccg3);
let ccg5_ver = read_versions(&data, SiliconId::Ccg5);
let ccg6_ver = read_versions(&data, SiliconId::Ccg6);
let ccg8_ver = read_versions(&data, SiliconId::Ccg8);
assert!(ccg3_ver.is_none());
assert!(ccg5_ver.is_none());
assert!(ccg6_ver.is_none());
assert!(ccg8_ver.is_some());
assert_eq!(
ccg8_ver,
Some({
PdFirmwareFile {
backup_fw: PdFirmware {
silicon_id: 0x11C5,
silicon_family: 0x3580,
base_version: BaseVersion {
major: 3,
minor: 6,
patch: 0,
build_number: 160,
},
app_version: AppVersion {
application: Application::Notebook,
major: 0,
minor: 0,
circuit: 3,
},
start_row: 290,
size: 111536,
row_size: 0x100,
},
main_fw: PdFirmware {
silicon_id: 0x11C5,
silicon_family: 0x3580,
base_version: BaseVersion {
major: 3,
minor: 6,
patch: 0,
build_number: 160,
},
app_version: AppVersion {
application: Application::Notebook,
major: 0,
minor: 0,
circuit: 3,
},
start_row: 29,
size: 42312,
row_size: 0x100,
},
}
})
);
}
#[test]
fn can_parse_ccg8_binary_fl16_ai300() {
let mut pd_bin_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
pd_bin_path.push("test_bins/fl16-ai300-pd-0.0.22.bin");
let data = fs::read(pd_bin_path).unwrap();
let ccg3_ver = read_versions(&data, SiliconId::Ccg3);
let ccg5_ver = read_versions(&data, SiliconId::Ccg5);
let ccg6_ver = read_versions(&data, SiliconId::Ccg6);
let ccg8_ver = read_versions(&data, SiliconId::Ccg8);
assert!(ccg3_ver.is_none());
assert!(ccg5_ver.is_none());
assert!(ccg6_ver.is_none());
assert!(ccg8_ver.is_some());
assert_eq!(
ccg8_ver,
Some({
PdFirmwareFile {
backup_fw: PdFirmware {
silicon_id: 0x11C5,
silicon_family: 0x3580,
base_version: BaseVersion {
major: 3,
minor: 7,
patch: 0,
build_number: 407,
},
app_version: AppVersion {
application: Application::Notebook,
major: 0,
minor: 0,
circuit: 0x22,
},
start_row: 290,
size: 129912,
row_size: 0x100,
},
main_fw: PdFirmware {
silicon_id: 0x11C5,
silicon_family: 0x3580,
base_version: BaseVersion {
major: 3,
minor: 7,
patch: 0,
build_number: 407,
},
app_version: AppVersion {
application: Application::Notebook,
major: 0,
minor: 0,
circuit: 0x22,
},
start_row: 29,
size: 42816,
row_size: 0x100,
},
}
})
);
}
#[test]
fn can_parse_ccg8_binary_gnss() {
let mut pd_bin_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
pd_bin_path.push("test_bins/gn22-pd-0.0.22.bin");
let data = fs::read(pd_bin_path).unwrap();
let ccg3_ver = read_versions(&data, SiliconId::Ccg3);
let ccg5_ver = read_versions(&data, SiliconId::Ccg5);
let ccg6_ver = read_versions(&data, SiliconId::Ccg6);
let ccg8_ver = read_versions(&data, SiliconId::Ccg8);
assert!(ccg3_ver.is_none());
assert!(ccg5_ver.is_none());
assert!(ccg6_ver.is_none());
assert!(ccg8_ver.is_some());
assert_eq!(
ccg8_ver,
Some({
PdFirmwareFile {
backup_fw: PdFirmware {
silicon_id: 0x11C5,
silicon_family: 0x3581,
base_version: BaseVersion {
major: 3,
minor: 7,
patch: 0,
build_number: 407,
},
app_version: AppVersion {
application: Application::Notebook,
major: 0,
minor: 0,
circuit: 0x22,
},
start_row: 290,
size: 126700,
row_size: 0x100,
},
main_fw: PdFirmware {
silicon_id: 0x11C5,
silicon_family: 0x3581,
base_version: BaseVersion {
major: 3,
minor: 7,
patch: 0,
build_number: 407,
},
app_version: AppVersion {
application: Application::Notebook,
major: 0,
minor: 0,
circuit: 0x22,
},
start_row: 29,
size: 41588,
row_size: 0x100,
},
}
})
);
}
}