Skip to content

Commit 7619368

Browse files
committed
pldm-fw: ua: send actual component count in RequestUpdate
request_update() hardcoded the RequestUpdate NumberOfComponents field to 1. DSP0267 Table 27 defines this field as "the number of components that will be passed to the FD during the update", and the FD may use it to compare against the number of PassComponentTable/UpdateComponent commands it receives. When a package applies more than one component, the UA passes all of them (pass_component_table and update_components_progress iterate over update.components) while still announcing only 1, which a conformant FD can reject as a mismatch. Derive the count from update.components, the same list driven through the rest of the update flow, so the announced value matches what is actually sent. The value is fallibly converted to u16 to guard the (practically impossible) >65535 component case rather than truncating. Add a unit test (request_update_reports_actual_component_count) that drives request_update over a mock MCTP ReqChannel and asserts the RequestUpdate NumberOfComponents field reflects the actual component count, along with the cfg(test) package-builder and temp-file helpers it depends on. Signed-off-by: Brian Carr <brcarr@nvidia.com>
1 parent 574e3a9 commit 7619368

2 files changed

Lines changed: 286 additions & 1 deletion

File tree

pldm-fw/src/pkg.rs

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,3 +313,137 @@ impl Package {
313313
Ok(self.file.read_at(buf, file_offset)?)
314314
}
315315
}
316+
317+
/// Write `bytes` to a fresh temporary file and return a read handle to it.
318+
///
319+
/// The file is unlinked immediately; the returned handle keeps it alive, so
320+
/// the on-disk entry is cleaned up automatically once the handle is dropped.
321+
#[cfg(test)]
322+
pub(crate) fn temp_file_with(bytes: &[u8]) -> std::fs::File {
323+
use std::io::Write;
324+
use std::sync::atomic::{AtomicU64, Ordering};
325+
326+
static CTR: AtomicU64 = AtomicU64::new(0);
327+
let n = CTR.fetch_add(1, Ordering::Relaxed);
328+
let mut path = std::env::temp_dir();
329+
path.push(format!("pldm-fw-test-{}-{}.bin", std::process::id(), n));
330+
331+
{
332+
let mut f = std::fs::File::create(&path).unwrap();
333+
f.write_all(bytes).unwrap();
334+
f.flush().unwrap();
335+
}
336+
let f = std::fs::File::open(&path).unwrap();
337+
// The open handle keeps the inode alive after unlink.
338+
let _ = std::fs::remove_file(&path);
339+
f
340+
}
341+
342+
/// Build the bytes of a minimal, well-formed v1.1.x firmware update package.
343+
///
344+
/// The package describes a single device identified by one PCI Vendor ID
345+
/// descriptor (`vid`), whose component bitmap selects every supplied
346+
/// component. Each entry of `components` becomes a component image appended
347+
/// after the (CRC-protected) package header, with matching file offset/size
348+
/// fields.
349+
#[cfg(test)]
350+
pub(crate) fn build_v11_package(vid: u16, components: &[&[u8]]) -> Vec<u8> {
351+
let ncomp = components.len();
352+
assert!(ncomp >= 1);
353+
354+
// --- single device record ---
355+
let bitmap_bytes = ncomp.div_ceil(8);
356+
let mut bitmap = vec![0u8; bitmap_bytes];
357+
for i in 0..ncomp {
358+
bitmap[i / 8] |= 1u8 << (i % 8);
359+
}
360+
let set_ver = b"0000";
361+
362+
let mut descs = Vec::new();
363+
descs.extend_from_slice(&0x0000u16.to_le_bytes()); // type: PCI Vendor ID
364+
descs.extend_from_slice(&2u16.to_le_bytes()); // length
365+
descs.extend_from_slice(&vid.to_le_bytes()); // data
366+
let desc_count = 1u8;
367+
let pkg_data_len = 0u16;
368+
369+
let mut rec_body = Vec::new();
370+
rec_body.extend_from_slice(&bitmap);
371+
rec_body.extend_from_slice(set_ver);
372+
rec_body.extend_from_slice(&descs);
373+
let rec_len = (11 + rec_body.len()) as u16;
374+
375+
let mut device = Vec::new();
376+
device.extend_from_slice(&rec_len.to_le_bytes());
377+
device.push(desc_count);
378+
device.extend_from_slice(&0u32.to_le_bytes()); // option flags
379+
device.push(1u8); // set version string type (utf-8)
380+
device.push(set_ver.len() as u8);
381+
device.extend_from_slice(&pkg_data_len.to_le_bytes());
382+
device.extend_from_slice(&rec_body);
383+
384+
// Header bytes following the 19-byte init region, excluding the trailing
385+
// 4-byte checksum. `offsets` carries each component's absolute file offset.
386+
let build_pre = |offsets: &[usize]| -> Vec<u8> {
387+
let mut pre = Vec::new();
388+
pre.extend_from_slice(&[0u8; 13]); // release date/time
389+
pre.extend_from_slice(&(ncomp as u16).to_le_bytes()); // bitmap length (bits)
390+
// package version string (type, length, data)
391+
pre.push(1u8);
392+
pre.push(4u8);
393+
pre.extend_from_slice(b"0000");
394+
// device id record area
395+
pre.push(1u8); // device count
396+
pre.extend_from_slice(&device);
397+
// downstream device id record area (1.1.x): none
398+
pre.push(0u8);
399+
// component image information area
400+
pre.extend_from_slice(&(ncomp as u16).to_le_bytes());
401+
for (i, c) in components.iter().enumerate() {
402+
pre.extend_from_slice(&0x000au16.to_le_bytes()); // classification: firmware
403+
pre.extend_from_slice(&(i as u16).to_le_bytes()); // identifier
404+
pre.extend_from_slice(&0u32.to_le_bytes()); // comparison stamp
405+
pre.extend_from_slice(&0u16.to_le_bytes()); // options
406+
pre.extend_from_slice(&0u16.to_le_bytes()); // activation method
407+
pre.extend_from_slice(&(offsets[i] as u32).to_le_bytes());
408+
pre.extend_from_slice(&(c.len() as u32).to_le_bytes());
409+
// component version string (type, length, data)
410+
pre.push(1u8);
411+
pre.push(4u8);
412+
pre.extend_from_slice(b"0000");
413+
}
414+
pre
415+
};
416+
417+
const HDR_INIT_SIZE: usize = 16 + 1 + 2;
418+
419+
// First pass with placeholder offsets to learn the header size, which is
420+
// independent of the (fixed-size) offset field values.
421+
let pre_len = build_pre(&vec![0usize; ncomp]).len();
422+
let hdr_size = HDR_INIT_SIZE + pre_len + 4;
423+
424+
// Second pass: real offsets point past the header into the payload area.
425+
let mut offsets = vec![0usize; ncomp];
426+
let mut cum = hdr_size;
427+
for (i, c) in components.iter().enumerate() {
428+
offsets[i] = cum;
429+
cum += c.len();
430+
}
431+
let pre = build_pre(&offsets);
432+
433+
let mut header = Vec::new();
434+
header.extend_from_slice(PKG_UUID_1_1_X.as_bytes());
435+
header.push(1u8); // header format revision
436+
header.extend_from_slice(&(hdr_size as u16).to_le_bytes());
437+
header.extend_from_slice(&pre);
438+
439+
let crc32 = crc::Crc::<u32>::new(&crc::CRC_32_ISO_HDLC);
440+
let checksum = crc32.checksum(&header);
441+
header.extend_from_slice(&checksum.to_le_bytes());
442+
assert_eq!(header.len(), hdr_size);
443+
444+
let mut file = header;
445+
for c in components {
446+
file.extend_from_slice(c);
447+
}
448+
file
449+
}

pldm-fw/src/ua.rs

Lines changed: 152 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,9 +180,13 @@ pub fn request_update(
180180
check_fd_state(comm, PldmFDState::Idle)?;
181181

182182
let sz = XFER_SIZE as u32;
183+
let num_components: u16 =
184+
update.components.len().try_into().map_err(|_| {
185+
PldmUpdateError::new_update("too many components".into())
186+
})?;
183187
let mut data = vec![];
184188
data.extend_from_slice(&sz.to_le_bytes());
185-
data.extend_from_slice(&1u16.to_le_bytes()); // NumberOfComponents
189+
data.extend_from_slice(&num_components.to_le_bytes()); // NumberOfComponents
186190
data.extend_from_slice(&1u8.to_le_bytes()); // MaximumOutstandingTransferRequests
187191
data.extend_from_slice(&0u16.to_le_bytes()); // PackageDataLength
188192
update.package.version.write_utf8_bytes(&mut data);
@@ -582,3 +586,150 @@ fn check_fd_state(
582586

583587
Ok(())
584588
}
589+
590+
#[cfg(test)]
591+
mod tests {
592+
use super::{request_update, Update};
593+
use crate::{
594+
DeviceCapabilities, Descriptor, DescriptorString, DeviceIdentifiers,
595+
FirmwareParameters, GetStatusResponse, PldmFDState, PLDM_TYPE_FW,
596+
};
597+
use mctp::{Eid, MsgIC, MsgType, ReqChannel, MCTP_TYPE_PLDM};
598+
use std::cell::RefCell;
599+
use std::collections::VecDeque;
600+
use std::rc::Rc;
601+
602+
// --- Mock MCTP transport ---------------------------------------------
603+
//
604+
// request_update only drives the UA-initiated `ReqChannel` (`comm`):
605+
// a GetStatus precondition followed by RequestUpdate. The mock replays
606+
// scripted FD responses and records everything the UA sends so the test
607+
// can assert on the wire format.
608+
609+
#[derive(Default)]
610+
struct CommState {
611+
responses: VecDeque<Vec<u8>>,
612+
sent: Vec<Vec<u8>>,
613+
}
614+
615+
#[derive(Clone, Default)]
616+
struct MockComm {
617+
st: Rc<RefCell<CommState>>,
618+
}
619+
620+
impl MockComm {
621+
fn new(responses: Vec<Vec<u8>>) -> Self {
622+
Self {
623+
st: Rc::new(RefCell::new(CommState {
624+
responses: responses.into(),
625+
sent: Vec::new(),
626+
})),
627+
}
628+
}
629+
630+
fn sent(&self) -> Vec<Vec<u8>> {
631+
self.st.borrow().sent.clone()
632+
}
633+
}
634+
635+
impl ReqChannel for MockComm {
636+
fn send_vectored(
637+
&mut self,
638+
_typ: MsgType,
639+
_ic: MsgIC,
640+
bufs: &[&[u8]],
641+
) -> mctp::Result<()> {
642+
let mut msg = Vec::new();
643+
for b in bufs {
644+
msg.extend_from_slice(b);
645+
}
646+
self.st.borrow_mut().sent.push(msg);
647+
Ok(())
648+
}
649+
650+
fn recv<'f>(
651+
&mut self,
652+
buf: &'f mut [u8],
653+
) -> mctp::Result<(MsgType, MsgIC, &'f mut [u8])> {
654+
let resp = self
655+
.st
656+
.borrow_mut()
657+
.responses
658+
.pop_front()
659+
.ok_or(mctp::Error::RxFailure)?;
660+
buf[..resp.len()].copy_from_slice(&resp);
661+
Ok((MCTP_TYPE_PLDM, MsgIC(false), &mut buf[..resp.len()]))
662+
}
663+
664+
fn remote_eid(&self) -> Eid {
665+
Eid(8)
666+
}
667+
}
668+
669+
// --- frame helpers ---------------------------------------------------
670+
671+
/// A PLDM response frame as returned by the FD over `comm`.
672+
fn resp_frame(cmd: u8, cc: u8, data: &[u8]) -> Vec<u8> {
673+
let mut v = vec![0u8, PLDM_TYPE_FW, cmd, cc];
674+
v.extend_from_slice(data);
675+
v
676+
}
677+
678+
fn status_data(state: PldmFDState) -> Vec<u8> {
679+
let s = GetStatusResponse {
680+
current_state: state,
681+
previous_state: PldmFDState::Idle,
682+
aux_state: 0,
683+
aux_state_status: 0,
684+
progress_percent: 0,
685+
reason_code: 0,
686+
update_option_flags_enabled: 0,
687+
};
688+
let mut b = [0u8; 16];
689+
let l = s.write_buf(&mut b).unwrap();
690+
b[..l].to_vec()
691+
}
692+
693+
fn make_update(vid: u16, components: &[&[u8]]) -> Update {
694+
let bytes = crate::pkg::build_v11_package(vid, components);
695+
let pkg =
696+
crate::pkg::Package::parse(crate::pkg::temp_file_with(&bytes))
697+
.unwrap();
698+
let dev = DeviceIdentifiers {
699+
ids: vec![Descriptor::PciVid(vid)],
700+
};
701+
let fwp = FirmwareParameters {
702+
caps: DeviceCapabilities::from_u32(0),
703+
components: Vec::new().into(),
704+
active: DescriptorString::empty(),
705+
pending: DescriptorString::empty(),
706+
};
707+
Update::new(&dev, &fwp, pkg, None, None, vec![]).unwrap()
708+
}
709+
710+
// --- RequestUpdate ---------------------------------------------------
711+
712+
#[test]
713+
fn request_update_reports_actual_component_count() {
714+
let img: &[u8] = &[0u8; 64];
715+
let update = make_update(0xabcd, &[img, img]);
716+
717+
let mut comm = MockComm::new(vec![
718+
resp_frame(0x1b, 0, &status_data(PldmFDState::Idle)),
719+
// RequestUpdateResponse: FirmwareDeviceMetaDataLength + flag
720+
resp_frame(0x10, 0, &[0, 0, 0]),
721+
]);
722+
723+
request_update(&mut comm, &update).unwrap();
724+
725+
let sent = comm.sent();
726+
assert_eq!(sent.len(), 2);
727+
// sent[1] is RequestUpdate: [0x80, type, 0x10, <payload>]
728+
let ru = &sent[1];
729+
assert_eq!(ru[2], 0x10);
730+
let data = &ru[3..];
731+
// payload: MaxTransferSize(4), NumberOfComponents(2), ...
732+
let num_components = u16::from_le_bytes([data[4], data[5]]);
733+
assert_eq!(num_components, 2);
734+
}
735+
}

0 commit comments

Comments
 (0)