Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 36 additions & 3 deletions faux-mgs/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -236,20 +236,33 @@ enum Command {
long,
value_name = "SLOT",
requires = "switch_duration",
group = "action",
conflicts_with_all = ["cancel_pending"],
help = "set the active slot"
)]
set: Option<u16>,
/// Cancel a pending `set` operation.
/// Used to avoid a reset when recoverying from a failed or abandoned update.
#[clap(
short,
long,
requires = "set",
value_name = "SLOT",
requires = "switch_duration",
group = "action",
conflicts_with_all = ["set"]
)]
cancel_pending: Option<u16>,
#[clap(
short,
long,
requires = "action",
group = "switch_duration",
help = "persist the active slot to non-volatile memory"
)]
persist: bool,
/// Only valid with component "rot":
/// Prefer the specified slot on the next soft reset.
#[clap(short, long, requires = "set", group = "switch_duration")]
#[clap(short, long, requires = "action", group = "switch_duration")]
transient: bool,
},

Expand Down Expand Up @@ -1264,7 +1277,13 @@ async fn run_command(
]))
}
}
Command::ComponentActiveSlot { component, set, persist, transient } => {
Command::ComponentActiveSlot {
component,
set,
persist,
transient,
cancel_pending,
} => {
if transient && component != SpComponent::ROT {
bail!("The --transient (-t) flag is only allowed for the 'rot' component, not for {component}");
} else if let Some(slot) = set {
Expand All @@ -1276,6 +1295,20 @@ async fn run_command(
"set active slot for {component:?} to {slot}"
)]))
}
} else if let Some(slot) = cancel_pending {
sp.cancel_pending_component_active_slot(
component, slot, persist,
)
.await?;
if json {
Ok(Output::Json(
json!({ "ack": "cancel_pending", "slot": slot }),
))
} else {
Ok(Output::Lines(vec![format!(
"cancel pending active slot for {component:?} as {slot}"
)]))
}
} else {
let slot = sp.component_active_slot(component).await?;
info!(log, "active slot for {component:?}: {slot}");
Expand Down
10 changes: 10 additions & 0 deletions gateway-messages/src/mgs_to_sp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,16 @@ pub enum MgsRequest {
GetHostFlashHash {
slot: u16,
},

/// Cancel a pending slot activation for components with multiple slots that
/// have separate `pending` and `active` states.
/// This is useful when recovering from a failed or abandoned update without incurring an
/// additional reset or other activation step.
ComponentCancelPendingActiveSlot {
component: SpComponent,
slot: u16,
persist: bool,
},
}

#[derive(
Expand Down
23 changes: 23 additions & 0 deletions gateway-messages/src/sp_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,13 @@ pub trait SpHandler {
fn start_host_flash_hash(&mut self, slot: u16) -> Result<(), SpError>;

fn get_host_flash_hash(&mut self, slot: u16) -> Result<[u8; 32], SpError>;

fn component_cancel_pending_active_slot(
&mut self,
component: SpComponent,
slot: u16,
persist: bool,
) -> Result<(), SpError>;
}

/// Handle a single incoming message.
Expand Down Expand Up @@ -1032,6 +1039,13 @@ fn handle_mgs_request<H: SpHandler>(
MgsRequest::GetHostFlashHash { slot } => {
handler.get_host_flash_hash(slot).map(SpResponse::HostFlashHash)
}
MgsRequest::ComponentCancelPendingActiveSlot {
component,
slot,
persist,
} => handler
.component_cancel_pending_active_slot(component, slot, persist)
.map(|()| SpResponse::ComponentCancelPendingActiveSlotAck),
};

let response = match result {
Expand Down Expand Up @@ -1457,6 +1471,15 @@ mod tests {
) -> Result<[u8; 32], SpError> {
unimplemented!()
}

fn component_cancel_pending_active_slot(
&mut self,
_component: SpComponent,
_slot: u16,
_persist: bool,
) -> Result<(), SpError> {
unimplemented!()
}
}

#[cfg(feature = "std")]
Expand Down
16 changes: 12 additions & 4 deletions gateway-messages/src/sp_to_mgs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,9 @@ pub enum SpResponse {

/// sha2-256 hash of a flash bank
HostFlashHash([u8; 32]),

/// Cancel a pending slot activation
ComponentCancelPendingActiveSlotAck,
}

/// Identifier for one of of an SP's KSZ8463 management-network-facing ports.
Expand Down Expand Up @@ -1295,6 +1298,8 @@ pub enum UpdateError {
SignatureNotValidated,
VersionNotSupported,
InvalidPreferredSlotId,
AlreadyPending,
NonePending,
}

impl fmt::Display for UpdateError {
Expand Down Expand Up @@ -1355,10 +1360,13 @@ impl fmt::Display for UpdateError {
write!(f, "invalid component for operation")
}
Self::InvalidPreferredSlotId => {
write!(
f,
"updating a bootloader preferred slot is not permitted"
)
write!(f, "specified slot ID is not valid for this operation")
}
Self::AlreadyPending => {
write!(f, "pending preference must be canceled first")
}
Self::NonePending => {
write!(f, "no pending preference to cancel")
}
}
}
Expand Down
1 change: 1 addition & 0 deletions gateway-messages/tests/versioning/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ mod v17;
mod v18;
mod v19;
mod v20;
mod v21;

pub fn assert_serialized<T: Serialize + SerializedSize + std::fmt::Debug>(
expected: &[u8],
Expand Down
53 changes: 53 additions & 0 deletions gateway-messages/tests/versioning/v21.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

// Copyright 2023 Oxide Computer Company

//! The tests in this module check that the serialized form of messages from MGS
//! protocol version 2 have not changed.
//!
//! If a test in this module fails, _do not change the test_! This means you
//! have changed, deleted, or reordered an existing message type or enum
//! variant, and you should revert that change. This will remain true until we
//! bump the `version::MIN` to a value higher than 2, at which point these tests
//! can be removed as we will stop supporting v2.

use gateway_messages::MgsRequest;
use gateway_messages::SpComponent;
use gateway_messages::SpResponse;
use gateway_messages::UpdateError;

use super::assert_serialized;

#[test]
fn mgs_request() {
let request = MgsRequest::ComponentCancelPendingActiveSlot {
component: SpComponent::ROT,
slot: 0x0102,
persist: true,
};
#[rustfmt::skip]
let expected = &[
50, // ComponentCancelPendingActiveSlot
b'r', b'o', b't', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // ROT
2, 1, // slot
1, // persist = true
];
assert_serialized(expected, &request);
}

#[test]
fn sp_response() {
let response = SpResponse::ComponentCancelPendingActiveSlotAck;
let expected = &[52];
assert_serialized(expected, &response);
}

#[test]
fn error_enums() {
let response: [UpdateError; 2] =
[UpdateError::AlreadyPending, UpdateError::NonePending];
let expected = vec![35, 36];
assert_serialized(&expected, &response);
}
6 changes: 6 additions & 0 deletions gateway-sp-comms/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,12 @@ pub enum UpdateError {
InvalidComponent,
#[error("an image was not found")]
ImageNotFound,
#[error("cannot update preferred boot image")]
InvalidPreferredSlotId,
#[error("pending preference must be canceled first")]
AlreadyPending,
#[error("no pending preference to cancel")]
NonePending,
}

#[derive(Debug, thiserror::Error, SlogInlineError)]
Expand Down
16 changes: 16 additions & 0 deletions gateway-sp-comms/src/single_sp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,22 @@ impl SingleSp {
})
}

/// Cancel a pending slot activation for a particular component..
pub async fn cancel_pending_component_active_slot(
&self,
component: SpComponent,
slot: u16,
persist: bool,
) -> Result<()> {
self.rpc(MgsRequest::ComponentCancelPendingActiveSlot {
component,
slot,
persist,
})
.await
.and_then(expect_component_cancel_pending_active_slot_ack)
}

/// Request that the status of a component be cleared (e.g., resetting
/// counters).
pub async fn component_clear_status(
Expand Down
1 change: 1 addition & 0 deletions gateway-sp-comms/src/sp_response_expect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ expect_data_fn!(BulkIgnitionState(page) -> TlvPage);
expect_data_fn!(ComponentDetails(page) -> TlvPage);
expect_data_fn!(Inventory(page) -> TlvPage);
expect_data_fn!(BulkIgnitionLinkEvents(page) -> TlvPage);
expect_fn!(ComponentCancelPendingActiveSlotAck);

pub(crate) fn expect_caboose_value(
r: (SocketAddrV6, SpResponse, Vec<u8>),
Expand Down
4 changes: 4 additions & 0 deletions wireshark/protofields.lua
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ M.mgs_request.names = {
[47] = "ReadHostFlash",
[48] = "StartHostFlashHash",
[49] = "GetHostFlashHash",
[50] = "ComponentCancelPendingActiveSlot",
}
M.mgs_request.handlers = {
[0] = "dissect_discover",
Expand Down Expand Up @@ -146,6 +147,7 @@ M.mgs_request.handlers = {
[47] = "dissect_read_host_flash",
[48] = "dissect_start_host_flash_hash",
[49] = "dissect_get_host_flash_hash",
[50] = "dissect_component_cancel_pending_active_slot",
}
M.mgs_request.field = ProtoField.uint8(
"mgs.mgs_request",
Expand Down Expand Up @@ -330,6 +332,7 @@ M.sp_response.names = {
[49] = "ReadHostFlash",
[50] = "StartHostFlashHashAck",
[51] = "HostFlashHash",
[52] = "ComponentCancelPendingActiveSlotAck",
}
M.sp_response.handlers = {
[0] = "dissect_discover",
Expand Down Expand Up @@ -384,6 +387,7 @@ M.sp_response.handlers = {
[49] = "dissect_read_host_flash",
[50] = "dissect_start_host_flash_hash_ack",
[51] = "dissect_host_flash_hash",
[52] = "dissect_component_cancel_pending_active_slot_ack",
}
M.sp_response.field = ProtoField.uint8(
"mgs.sp_response",
Expand Down
Loading