Skip to content

Commit 96775ad

Browse files
authored
feat: support disabling interface between host and BMC for SMC MGX C2 servers (#72)
Supermicro MGX C2 system use SSIF for in-band communication is using SSIF instead of X86 KCS. Starting with BMC firmware 01.05.01, MGX C2 systems support enabling/disabling communication b/w the host and its BMC by issuing a PATCH Redfish request to modify the IPMIHostInterface attribute at `https://BMC_IP/redfish/v1/Systems/1`. Similarly, you can read that attribute to determine whether communication between the host and BMC is enabled.
1 parent 29de93f commit 96775ad

3 files changed

Lines changed: 119 additions & 39 deletions

File tree

src/model/system.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,16 @@ pub struct ComputerSystem {
201201
pub serial_console: Option<SerialConsole>, // Newer Redfish impls, inc Supermicro
202202
pub links: Option<ComputerSystemLinks>,
203203
pub boot_progress: Option<BootProgress>,
204+
#[serde(rename = "IPMIHostInterface")]
205+
pub ipmi_host_interface: Option<IpmiHostInterface>, // MGX C2 Supermicro (SSIF, not KCS); BMC firmware 01.05.01+
206+
}
207+
208+
/// SSIF in-band host-to-BMC interface on MGX C2 Supermicro systems.
209+
/// Present at `Systems/{id}` when the BMC firmware is 01.05.01+.
210+
#[derive(Debug, Serialize, Deserialize, Clone)]
211+
#[serde(rename_all = "PascalCase")]
212+
pub struct IpmiHostInterface {
213+
pub service_enabled: bool,
204214
}
205215

206216
#[derive(Debug, Serialize, Deserialize, Default, Clone)]

src/network.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,11 @@ impl RedfishHttpClient {
339339
}
340340
}
341341

342+
/// Returns the hostname or IP address of the BMC this client connects to.
343+
pub fn host(&self) -> &str {
344+
&self.endpoint.host
345+
}
346+
342347
/// Returns `true` if this client has no credentials (i.e. anonymous/unauthenticated).
343348
pub fn is_anonymous(&self) -> bool {
344349
self.endpoint.user.is_none()

src/supermicro.rs

Lines changed: 104 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,20 @@ use crate::{
6161

6262
const MELLANOX_UEFI_HTTP_IPV4: &str = "UEFI HTTP IPv4 Mellanox Network Adapter";
6363
const NVIDIA_UEFI_HTTP_IPV4: &str = "UEFI HTTP IPv4 Nvidia Network Adapter";
64+
65+
/// MGX C2 systems use SSIF instead of x86 KCS for in-band BMC communication,
66+
/// so the KCSInterface endpoint doesn't exist. These models require the
67+
/// IPMIHostInterface fallback on Systems/{id}.
68+
const MGX_C2_MODELS: [&str; 4] = [
69+
"ARS-121L-DNR",
70+
"ARS-221GL-NR",
71+
"SYS-221H-TNR",
72+
"SYS-221H-TN24R",
73+
];
74+
75+
/// Minimum BMC firmware version that exposes `IPMIHostInterface` on
76+
/// `Systems/{id}` for MGX C2 systems.
77+
const MIN_BMC_FW_IPMI_HOST_IFACE: &str = "01.05.01";
6478
const HARD_DISK: &str = "UEFI Hard Disk";
6579
const NETWORK: &str = "UEFI Network";
6680

@@ -336,19 +350,7 @@ impl Redfish for Bmc {
336350
fn lockdown_status<'a>(&'a self) -> crate::RedfishFuture<'a, Result<Status, RedfishError>> {
337351
Box::pin(async move {
338352
let is_hi_on = self.is_host_interface_enabled().await?;
339-
let kcs_privilege = match self.get_kcs_privilege().await {
340-
Ok(priviledge) => Ok(Some(priviledge)),
341-
Err(e) => {
342-
// The Grace-Grace Supermicros in our GB200 lab do not seem to support
343-
// querying KCS access from the host to its BMC. Use this workaround to
344-
// temporarily enable ingesting these servers.
345-
if e.not_found() {
346-
Ok(None)
347-
} else {
348-
Err(e)
349-
}
350-
}
351-
}?;
353+
let kcs_privilege = self.get_kcs_privilege().await?;
352354

353355
let is_syslockdown = self.get_syslockdown().await?;
354356
let message = format!("SysLockdownEnabled={is_syslockdown}, kcs_privilege={kcs_privilege:#?}, host_interface_enabled={is_hi_on}");
@@ -357,14 +359,10 @@ impl Redfish for Bmc {
357359
let is_grace_grace = self.is_grace_grace_smc().await?;
358360

359361
let is_locked = is_syslockdown
360-
&& kcs_privilege
361-
.clone()
362-
.unwrap_or(supermicro::Privilege::Callback)
363-
== supermicro::Privilege::Callback
362+
&& kcs_privilege == supermicro::Privilege::Callback
364363
&& (is_grace_grace || !is_hi_on);
365364
let is_unlocked = !is_syslockdown
366-
&& kcs_privilege.unwrap_or(supermicro::Privilege::Administrator)
367-
== supermicro::Privilege::Administrator
365+
&& kcs_privilege == supermicro::Privilege::Administrator
368366
&& is_hi_on;
369367
Ok(Status {
370368
message,
@@ -1362,11 +1360,24 @@ impl Bmc {
13621360
}
13631361

13641362
async fn get_kcs_privilege(&self) -> Result<supermicro::Privilege, RedfishError> {
1363+
if self.is_mgx_c2().await? {
1364+
let enabled = self.get_ipmi_host_interface_enabled().await?;
1365+
return if enabled {
1366+
Ok(supermicro::Privilege::Administrator)
1367+
} else {
1368+
Ok(supermicro::Privilege::Callback)
1369+
};
1370+
}
1371+
13651372
let url = format!(
13661373
"Managers/{}/Oem/Supermicro/KCSInterface",
13671374
self.s.manager_id()
13681375
);
1369-
let (_, body): (_, HashMap<String, serde_json::Value>) = self.s.client.get(&url).await?;
1376+
let (_, body) = self
1377+
.s
1378+
.client
1379+
.get::<HashMap<String, serde_json::Value>>(&url)
1380+
.await?;
13701381
let key = "Privilege";
13711382
let p_str = body
13721383
.get(key)
@@ -1391,29 +1402,77 @@ impl Bmc {
13911402
&self,
13921403
privilege: supermicro::Privilege,
13931404
) -> Result<(), RedfishError> {
1405+
if self.is_mgx_c2().await? {
1406+
let enabled = privilege == supermicro::Privilege::Administrator;
1407+
return self.set_ipmi_host_interface(enabled).await;
1408+
}
1409+
13941410
let url = format!(
13951411
"Managers/{}/Oem/Supermicro/KCSInterface",
13961412
self.s.manager_id()
13971413
);
13981414
let body = HashMap::from([("Privilege", privilege.to_string())]);
1399-
self.s
1400-
.client
1401-
.patch(&url, body)
1402-
.await
1403-
.or_else(|err| {
1404-
// The Grace-Grace Supermicros in our GB200 lab do not seem to support
1405-
// disabling KCS access from the host to its BMC. Use this workaround to
1406-
// temporarily enable ingesting these servers.
1407-
if err.not_found() {
1408-
tracing::warn!(
1409-
"Supermicro was uanble to find {url}: {err}; not returning error to caller"
1410-
);
1411-
Ok((StatusCode::OK, None))
1412-
} else {
1413-
Err(err)
1414-
}
1415-
})
1416-
.map(|_status_code| ())
1415+
self.s.client.patch(&url, body).await?;
1416+
Ok(())
1417+
}
1418+
1419+
/// Returns `true` when the BMC firmware version is at least
1420+
/// [`MIN_BMC_FW_IPMI_HOST_IFACE`] (`01.05.01`), which is the first
1421+
/// version to expose `IPMIHostInterface` on `Systems/{id}`.
1422+
async fn bmc_supports_ipmi_host_iface(&self) -> Result<bool, RedfishError> {
1423+
let manager = self.s.get_manager().await?;
1424+
let fw = manager.firmware_version.unwrap_or_default();
1425+
Ok(version_compare::compare(&fw, MIN_BMC_FW_IPMI_HOST_IFACE)
1426+
.is_ok_and(|c| c != version_compare::Cmp::Lt))
1427+
}
1428+
1429+
/// Disable/enable SSIF in-band access via `IPMIHostInterface` on `Systems/{id}`.
1430+
/// Used for MGX C2 systems that lack the KCSInterface endpoint.
1431+
/// No-op when the BMC firmware is older than 01.05.01.
1432+
async fn set_ipmi_host_interface(&self, enabled: bool) -> Result<(), RedfishError> {
1433+
if !self.bmc_supports_ipmi_host_iface().await? {
1434+
let smc_bmc_ip = self.s.client.host();
1435+
tracing::warn!(
1436+
smc_bmc_ip,
1437+
"MGX C2 BMC firmware is older than {MIN_BMC_FW_IPMI_HOST_IFACE}; \
1438+
skipping IPMIHostInterface write"
1439+
);
1440+
return Ok(());
1441+
}
1442+
1443+
use crate::model::system::IpmiHostInterface;
1444+
let url = format!("Systems/{}", self.s.system_id());
1445+
let body = HashMap::from([(
1446+
"IPMIHostInterface",
1447+
IpmiHostInterface {
1448+
service_enabled: enabled,
1449+
},
1450+
)]);
1451+
self.s.client.patch(&url, body).await.map(|_status_code| ())
1452+
}
1453+
1454+
/// Get whether SSIF in-band access is enabled via `IPMIHostInterface` on `Systems/{id}`.
1455+
/// Used for MGX C2 systems that lack the KCSInterface endpoint.
1456+
/// Returns `false` when the BMC firmware is older than 01.05.01.
1457+
async fn get_ipmi_host_interface_enabled(&self) -> Result<bool, RedfishError> {
1458+
if !self.bmc_supports_ipmi_host_iface().await? {
1459+
let smc_bmc_ip = self.s.client.host();
1460+
tracing::warn!(
1461+
smc_bmc_ip,
1462+
"MGX C2 BMC firmware is older than {MIN_BMC_FW_IPMI_HOST_IFACE}; \
1463+
IPMIHostInterface unavailable, reporting disabled"
1464+
);
1465+
return Ok(false);
1466+
}
1467+
1468+
let system = self.s.get_system().await?;
1469+
let iface = system
1470+
.ipmi_host_interface
1471+
.ok_or_else(|| RedfishError::MissingKey {
1472+
key: "IPMIHostInterface".to_string(),
1473+
url: format!("Systems/{}", self.s.system_id()),
1474+
})?;
1475+
Ok(iface.service_enabled)
14171476
}
14181477

14191478
async fn is_host_interface_enabled(&self) -> Result<bool, RedfishError> {
@@ -1633,7 +1692,13 @@ impl Bmc {
16331692
Ok(by_name)
16341693
}
16351694

1636-
// Check if this is a Grace-Grace SMC (ARS-121L-DNR) that needs host_interface enabled
1695+
/// MGX C2 systems use SSIF instead of x86 KCS, so the KCSInterface
1696+
/// endpoint doesn't exist. Detect them by matching the system model.
1697+
async fn is_mgx_c2(&self) -> Result<bool, RedfishError> {
1698+
let model = self.s.get_system().await?.model.unwrap_or_default();
1699+
Ok(MGX_C2_MODELS.iter().any(|m| model.contains(m)))
1700+
}
1701+
16371702
async fn is_grace_grace_smc(&self) -> Result<bool, RedfishError> {
16381703
Ok(self
16391704
.s

0 commit comments

Comments
 (0)