Skip to main content

pldm_fw/
ua.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2/*
3 * PLDM firmware update utility.
4 *
5 * Copyright (c) 2023 Code Construct
6 */
7
8//! PLDM Firmware Update Agent
9//!
10//! Update Agent requires `std` feature.
11use log::{debug, error};
12
13use thiserror::Error;
14
15use nom::{
16    combinator::{all_consuming, map},
17    multi::length_value,
18    number::complete::le_u32,
19    sequence::tuple,
20    IResult,
21};
22
23use pldm::PldmError;
24
25use crate::pkg;
26use crate::{
27    DeviceIdentifiers, FirmwareParameters, FwCode, GetStatusResponse,
28    PldmFDState, RequestUpdateResponse, UpdateComponentResponse,
29    UpdateTransferProgress, PLDM_TYPE_FW,
30};
31
32pub type Result<T> = core::result::Result<T, PldmUpdateError>;
33
34#[derive(Error, Debug)]
35pub enum PldmUpdateError {
36    #[error("PLDM error: {0}")]
37    Pldm(#[from] PldmError),
38    #[error("PLDM protocol error: {0}")]
39    Protocol(String),
40    #[error("PLDM command (0x{0:02x}) failed with 0x{1:02x}")]
41    Command(u8, u8),
42    #[error("PLDM Update error: {0}")]
43    Update(String),
44    #[error("PLDM Package error: {0}")]
45    Package(#[from] pkg::PldmPackageError),
46    // #[error("MCTP IO error: {0}")]
47    // MCTPIO(#[from] std::io::Error)
48}
49
50impl PldmUpdateError {
51    fn new_command(cmd: u8, cc: u8) -> Self {
52        Self::Command(cmd, cc)
53    }
54
55    fn new_proto(desc: String) -> Self {
56        Self::Protocol(desc)
57    }
58
59    fn new_update(desc: String) -> Self {
60        Self::Update(desc)
61    }
62}
63
64#[derive(Debug)]
65pub struct Update {
66    pub package: pkg::Package,
67    pub index: u8,
68    pub components: Vec<usize>,
69}
70
71impl Update {
72    pub fn new(
73        dev: &DeviceIdentifiers,
74        _fwp: &FirmwareParameters,
75        pkg: pkg::Package,
76        index: Option<u8>,
77        force_device: Option<usize>,
78        force_components: Vec<usize>,
79    ) -> Result<Self> {
80        let dev = match force_device {
81            Some(n) => {
82                if n >= pkg.devices.len() {
83                    return Err(PldmUpdateError::new_update(
84                        "invalid device index".into(),
85                    ));
86                }
87                &pkg.devices[n]
88            }
89            None => {
90                let fwdevs = pkg
91                    .devices
92                    .iter()
93                    .filter(|d| &d.ids == dev)
94                    .collect::<Vec<_>>();
95
96                if fwdevs.is_empty() {
97                    return Err(PldmUpdateError::new_update(
98                        "no matching devices".into(),
99                    ));
100                }
101
102                if fwdevs.len() != 1 {
103                    return Err(PldmUpdateError::new_update(
104                        "multiple matching devices".into(),
105                    ));
106                }
107
108                *fwdevs.first().unwrap()
109            }
110        };
111
112        let index = index.unwrap_or(0u8);
113
114        let components = if !force_components.is_empty() {
115            if force_components.iter().any(|c| c >= &pkg.components.len()) {
116                return Err(PldmUpdateError::new_update(
117                    "invalid components".into(),
118                ));
119            }
120
121            force_components
122        } else {
123            dev.components.as_index_vec()
124        };
125
126        Ok(Self {
127            package: pkg,
128            components,
129            index,
130        })
131    }
132}
133
134pub fn query_device_identifiers(
135    comm: &mut impl mctp::ReqChannel,
136) -> Result<DeviceIdentifiers> {
137    let req = pldm::PldmRequest::new(PLDM_TYPE_FW, 0x01);
138
139    let rsp = pldm::pldm_xfer(comm, req)?;
140
141    if rsp.cc != 0 {
142        return Err(PldmUpdateError::new_command(0x01, rsp.cc));
143    }
144
145    let f = length_value(map(le_u32, |l| l + 1), DeviceIdentifiers::parse);
146
147    let res = all_consuming(f)(&rsp.data);
148
149    res.map(|(_, d)| d).map_err(|_e| {
150        PldmUpdateError::new_proto("can't parse QDI response".into())
151    })
152}
153
154pub fn query_firmware_parameters(
155    comm: &mut impl mctp::ReqChannel,
156) -> Result<FirmwareParameters<'_>> {
157    let req = pldm::PldmRequest::new(PLDM_TYPE_FW, 0x02);
158
159    let rsp = pldm::pldm_xfer(comm, req)?;
160
161    if rsp.cc != 0 {
162        return Err(PldmUpdateError::new_command(0x02, rsp.cc));
163    }
164
165    let f = FirmwareParameters::parse;
166
167    let res = all_consuming(f)(&rsp.data);
168
169    res.map(|(_, d)| d).map_err(|_e| {
170        PldmUpdateError::new_proto("can't parse QFP response".into())
171    })
172}
173
174const XFER_SIZE: usize = 16 * 1024;
175
176pub fn request_update(
177    comm: &mut impl mctp::ReqChannel,
178    update: &Update,
179) -> Result<RequestUpdateResponse> {
180    check_fd_state(comm, PldmFDState::Idle)?;
181
182    let sz = XFER_SIZE as u32;
183    let mut data = vec![];
184    data.extend_from_slice(&sz.to_le_bytes());
185    data.extend_from_slice(&1u16.to_le_bytes()); // NumberOfComponents
186    data.extend_from_slice(&1u8.to_le_bytes()); // MaximumOutstandingTransferRequests
187    data.extend_from_slice(&0u16.to_le_bytes()); // PackageDataLength
188    update.package.version.write_utf8_bytes(&mut data);
189
190    let req = pldm::PldmRequest::new_data(PLDM_TYPE_FW, 0x10, data);
191    let rsp = pldm::pldm_xfer(comm, req)?;
192
193    if rsp.cc != 0 {
194        return Err(PldmUpdateError::new_command(0x10, rsp.cc));
195    }
196
197    let res = all_consuming(RequestUpdateResponse::parse)(&rsp.data);
198
199    res.map(|(_, d)| d).map_err(|_e| {
200        PldmUpdateError::new_proto("can't parse RU response".into())
201    })
202}
203
204pub fn cancel_update(comm: &mut impl mctp::ReqChannel) -> Result<()> {
205    let req = pldm::PldmRequest::new(PLDM_TYPE_FW, 0x1d);
206    let rsp = pldm::pldm_xfer(comm, req)?;
207    debug!("cancel rsp: cc {:x}, data {:?}", rsp.cc, rsp.data);
208    Ok(())
209}
210
211pub fn update_component(
212    comm: &mut impl mctp::ReqChannel,
213    listener: &mut impl mctp::Listener,
214    package: &pkg::Package,
215    component: &pkg::PackageComponent,
216    index: u8,
217) -> Result<()> {
218    update_component_progress(comm, listener, package, component, index, |_| ())
219}
220
221pub fn pass_component_table(
222    comm: &mut impl mctp::ReqChannel,
223    update: &Update,
224) -> Result<()> {
225    let components = &update.components;
226    let len = components.len();
227
228    check_fd_state(comm, PldmFDState::LearnComponents)?;
229
230    for (n, idx) in components.iter().enumerate() {
231        let component = update.package.components.get(*idx).unwrap();
232
233        let mut data = vec![];
234        data.push(xfer_flags(n, len));
235        let c = u16::from(&component.classification);
236        data.extend_from_slice(&c.to_le_bytes());
237        data.extend_from_slice(&component.identifier.to_le_bytes());
238
239        data.extend_from_slice(&update.index.to_le_bytes());
240
241        data.extend_from_slice(&component.comparison_stamp.to_le_bytes());
242
243        component.version.write_utf8_bytes(&mut data);
244
245        let req = pldm::PldmRequest::new_data(PLDM_TYPE_FW, 0x13, data);
246        let rsp = pldm::pldm_xfer(comm, req)?;
247
248        if rsp.cc != 0 {
249            return Err(PldmUpdateError::new_command(0x13, rsp.cc));
250        }
251
252        if rsp.data.len() < 2 {
253            return Err(PldmUpdateError::new_proto(
254                "Invalid PCT response".into(),
255            ));
256        }
257
258        if rsp.data[0] != 0 {
259            match rsp.data[1] {
260                0x00 => (),
261                0x06 => {
262                    return Err(PldmUpdateError::new_update(format!(
263                        "unsupported component {}",
264                        rsp.data[1]
265                    )))
266                }
267                x => {
268                    return Err(PldmUpdateError::new_proto(format!(
269                        "unknown PCT response {:02x}",
270                        x
271                    )))
272                }
273            }
274        }
275    }
276
277    Ok(())
278}
279
280fn xfer_flags(idx: usize, len: usize) -> u8 {
281    let mut xfer_flags: u8 = 0x0;
282    if idx == 0 {
283        xfer_flags |= 0x1;
284    }
285    if idx == len - 1 {
286        xfer_flags |= 0x4;
287    }
288    if xfer_flags == 0 {
289        xfer_flags = 0x2;
290    }
291    xfer_flags
292}
293
294pub fn update_component_progress<F>(
295    comm: &mut impl mctp::ReqChannel,
296    listener: &mut impl mctp::Listener,
297    package: &pkg::Package,
298    component: &pkg::PackageComponent,
299    index: u8,
300    mut progress: F,
301) -> Result<()>
302where
303    F: FnMut(&UpdateTransferProgress),
304{
305    check_fd_state(comm, PldmFDState::ReadyXfer)?;
306
307    let mut data = vec![];
308    let c = u16::from(&component.classification);
309    data.extend_from_slice(&c.to_le_bytes());
310    data.extend_from_slice(&component.identifier.to_le_bytes());
311
312    data.extend_from_slice(&index.to_le_bytes());
313
314    data.extend_from_slice(&component.comparison_stamp.to_le_bytes());
315
316    let sz: u32 = component.file_size as u32;
317    let mut sz_done: u32 = 0;
318    data.extend_from_slice(&sz.to_le_bytes());
319
320    // todo: flags: request forced update?
321    data.extend_from_slice(&0u32.to_le_bytes());
322
323    component.version.write_utf8_bytes(&mut data);
324
325    let req = pldm::PldmRequest::new_data(PLDM_TYPE_FW, 0x14, data);
326    let rsp = pldm::pldm_xfer(comm, req)?;
327
328    if rsp.cc != 0 {
329        return Err(PldmUpdateError::new_command(0x14, rsp.cc));
330    }
331
332    let (_, res) =
333        all_consuming(UpdateComponentResponse::parse)(rsp.data.as_ref())
334            .map_err(|_e| {
335                PldmUpdateError::new_proto(
336                    "can't parse Update Component response".into(),
337                )
338            })?;
339
340    if res.response_code != 0 {
341        return Err(PldmUpdateError::new_update(format!(
342            "Update Component rejected with code 0x{:02x}",
343            res.response_code
344        )));
345    }
346
347    let start = chrono::Utc::now();
348
349    loop {
350        // we should be in update mode, handle incoming data requests
351        let (fw_req, mut req_ep) = pldm::pldm_rx_req(listener)?;
352
353        if fw_req.typ != PLDM_TYPE_FW {
354            return Err(PldmUpdateError::new_proto(format!(
355                "unexpected type during update: {fw_req:?}"
356            )));
357        }
358
359        match fw_req.cmd {
360            0x15 => {
361                /* Request Firmware Data */
362                let res: IResult<_, _> = all_consuming(tuple((le_u32, le_u32)))(
363                    fw_req.data.as_ref(),
364                );
365
366                let (_, (offset, len)) = res.map_err(|_e| {
367                    PldmUpdateError::new_proto("RFD parse error".into())
368                })?;
369
370                let mut buf = vec![0u8; len as usize];
371
372                package.read_component(component, offset, &mut buf)?;
373
374                let mut fw_resp = fw_req.response();
375
376                fw_resp.cc = 0;
377                fw_resp.set_data(buf);
378
379                pldm::pldm_tx_resp(&mut req_ep, &fw_resp)?;
380
381                sz_done += len;
382                let elapsed = chrono::Utc::now() - start;
383
384                let bps;
385                let remaining;
386                if elapsed.is_zero() || sz_done == 0 {
387                    bps = 0.;
388                    remaining = chrono::TimeDelta::days(1);
389                } else {
390                    let rate = elapsed / sz_done as i32; // time per byte
391
392                    /* blocks may be repeated */
393                    let sz_left = if sz_done <= sz { sz - sz_done } else { 0 };
394
395                    remaining = rate * sz_left as i32;
396                    // OK unwrap, overflows after 200k years
397                    bps = 1_000_000.0 / rate.num_microseconds().unwrap() as f32;
398                }
399                let percent = ((100 * (sz_done as u64)) / sz as u64) as u8;
400
401                let u = UpdateTransferProgress {
402                    cur_xfer: Some((offset, len)),
403                    percent,
404                    bps,
405                    remaining,
406                    duration: elapsed,
407                    complete: false,
408                };
409
410                progress(&u);
411            }
412            0x16 => {
413                /* Transfer Complete */
414                let res = fw_req.data[0];
415                let elapsed = chrono::Utc::now() - start;
416
417                if res == 0 {
418                    let rate = elapsed
419                        .checked_div(sz_done as i32)
420                        .and_then(|r| r.num_microseconds())
421                        .unwrap_or(0);
422                    let bps = if rate > 0 {
423                        1_000_000.0 / rate as f32
424                    } else {
425                        0.
426                    };
427
428                    let u = UpdateTransferProgress {
429                        cur_xfer: None,
430                        percent: 100,
431                        bps,
432                        remaining: chrono::Duration::zero(),
433                        duration: elapsed,
434                        complete: false,
435                    };
436
437                    progress(&u);
438                } else {
439                    error!("firmware transfer error: 0x{:02x}", res);
440                }
441                let mut fw_resp = fw_req.response();
442                fw_resp.cc = 0;
443                pldm::pldm_tx_resp(&mut req_ep, &fw_resp)?;
444                break;
445            }
446            _ => {
447                return Err(PldmUpdateError::new_proto(format!(
448                    "unexpected command during update: {fw_req:?}"
449                )));
450            }
451        }
452    }
453
454    /* Verify results.. */
455    let (fw_req, mut req_ep) = pldm::pldm_rx_req(listener)?;
456    match fw_req.cmd {
457        0x17 => {
458            let res = fw_req.data[0];
459            if res != 0 {
460                return Err(PldmUpdateError::new_update(
461                    "firmware verify failure".into(),
462                ));
463            }
464        }
465        _ => {
466            return Err(PldmUpdateError::new_update(
467                "unexpected command in verify state".into(),
468            ))
469        }
470    }
471    let mut fw_resp = fw_req.response();
472    fw_resp.cc = 0;
473    pldm::pldm_tx_resp(&mut req_ep, &fw_resp)?;
474    drop(req_ep);
475
476    /* Apply */
477    let (fw_req, mut req_ep) = pldm::pldm_rx_req(listener)?;
478    match fw_req.cmd {
479        0x18 => {
480            let res = fw_req.data[0];
481            if res != 0 {
482                return Err(PldmUpdateError::new_update(
483                    "firmware apply failure".into(),
484                ));
485            }
486        }
487        _ => {
488            return Err(PldmUpdateError::new_update(
489                "unexpected command in apply state".into(),
490            ));
491        }
492    }
493
494    let mut fw_resp = fw_req.response();
495    fw_resp.cc = 0;
496    pldm::pldm_tx_resp(&mut req_ep, &fw_resp)?;
497
498    check_fd_state(comm, PldmFDState::ReadyXfer)?;
499
500    Ok(())
501}
502
503pub fn update_components(
504    comm: &mut impl mctp::ReqChannel,
505    listener: &mut impl mctp::Listener,
506    update: &mut Update,
507) -> Result<()> {
508    update_components_progress(comm, listener, update, |_| ())
509}
510
511pub fn update_components_progress<F>(
512    comm: &mut impl mctp::ReqChannel,
513    listener: &mut impl mctp::Listener,
514    update: &mut Update,
515    mut progress: F,
516) -> Result<()>
517where
518    F: FnMut(&UpdateTransferProgress),
519{
520    let components = update.components.clone();
521
522    for idx in components {
523        let component = update.package.components.get(idx).unwrap();
524        update_component_progress(
525            comm,
526            listener,
527            &update.package,
528            component,
529            update.index,
530            &mut progress,
531        )?;
532    }
533
534    Ok(())
535}
536
537pub fn activate_firmware(
538    comm: &mut impl mctp::ReqChannel,
539    self_activate: bool,
540) -> Result<()> {
541    check_fd_state(comm, PldmFDState::ReadyXfer)?;
542
543    let self_activation_req: u8 = if self_activate { 1 } else { 0 };
544
545    let mut data = vec![];
546    data.extend_from_slice(&self_activation_req.to_le_bytes());
547
548    let req = pldm::PldmRequest::new_data(PLDM_TYPE_FW, 0x1a, data);
549    let rsp = pldm::pldm_xfer(comm, req)?;
550
551    if rsp.cc == 0 || rsp.cc == FwCode::ACTIVATION_NOT_REQUIRED as u8 {
552        Ok(())
553    } else {
554        Err(PldmUpdateError::new_command(0x1a, rsp.cc))
555    }
556}
557
558fn check_fd_state(
559    comm: &mut impl mctp::ReqChannel,
560    expected_state: PldmFDState,
561) -> Result<()> {
562    let req = pldm::PldmRequest::new(PLDM_TYPE_FW, 0x1b);
563    let rsp = pldm::pldm_xfer(comm, req)?;
564
565    if rsp.cc != 0 {
566        return Err(PldmUpdateError::new_command(0x1b, rsp.cc));
567    }
568
569    let (_, res) = all_consuming(GetStatusResponse::parse)(rsp.data.as_ref())
570        .map_err(|_e| {
571        PldmUpdateError::new_proto("can't parse Get Status response".into())
572    })?;
573
574    //todo: flag
575    debug!("state: {:?}", res.current_state);
576
577    if res.current_state != expected_state {
578        return Err(PldmUpdateError::new_proto(format!(
579            "invalid state {:?}",
580            res.current_state
581        )));
582    }
583
584    Ok(())
585}