Skip to content

Commit fdfd8a9

Browse files
committed
nvme: mi: dev: Implement NVMe MI / Controller Health Status Poll
Signed-off-by: Andrew Jeffery <andrew@codeconstruct.com.au>
1 parent b8ebad6 commit fdfd8a9

4 files changed

Lines changed: 442 additions & 22 deletions

File tree

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ pub struct PortId(u8);
198198
struct ManagementEndpointControllerState {
199199
cc: nvme::ControllerConfiguration,
200200
csts: FlagSet<nvme::ControllerStatusFlags>,
201+
chscf: FlagSet<nvme::mi::ControllerHealthStatusChangedFlags>,
201202
}
202203

203204
#[derive(Debug)]

src/nvme/mi.rs

Lines changed: 142 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ use deku::{DekuError, DekuRead, DekuWrite};
33
use flagset::{FlagSet, flags};
44
use log::debug;
55

6-
use crate::{CommandEffectError, Discriminant, Encode};
6+
use crate::wire::{WireFlagSet, WireVec};
7+
use crate::{CommandEffectError, Discriminant, Encode, MAX_CONTROLLERS};
78

89
use super::{AdminGetLogPageLidRequestType, AdminIdentifyCnsRequestType};
910

@@ -124,7 +125,8 @@ enum NvmeMiCommandRequestType {
124125
ReadNvmeMiDataStructure(NvmeMiDataStructureRequest),
125126
#[deku(id = "0x01")]
126127
NvmSubsystemHealthStatusPoll(NvmSubsystemHealthStatusPollRequest),
127-
ControllerHealthStatusPoll = 0x02,
128+
#[deku(id = "0x02")]
129+
ControllerHealthStatusPoll(ControllerHealthStatusPollRequest),
128130
#[deku(id = "0x03")]
129131
ConfigurationSet(NvmeMiConfigurationSetRequest),
130132
#[deku(id = "0x04")]
@@ -259,6 +261,137 @@ struct MctpTransmissionUnitSizeRequest {
259261
dw1_mtus: u16,
260262
}
261263

264+
// MI v2.0, 5.3, Figure 94
265+
flags! {
266+
pub enum ControllerFunctionAndReportingFlags: u8 {
267+
Incf = 1 << 0,
268+
Incpf = 1 << 1,
269+
Incvf = 1 << 2,
270+
All = 1 << 7,
271+
}
272+
}
273+
274+
// MI v2.0, 5.3, Figure 95
275+
flags! {
276+
pub enum ControllerPropertyFlags: u32 {
277+
Csts = 1 << 0,
278+
Ctemp = 1 << 1,
279+
Pldu = 1 << 2,
280+
Spare = 1 << 3,
281+
Cwarn = 1 << 4,
282+
Ccf = 1 << 31,
283+
}
284+
}
285+
286+
// MI v2.0, 5.3, Figures 94, 95
287+
#[derive(Debug, DekuRead, DekuWrite, Eq, PartialEq)]
288+
#[deku(ctx = "endian: Endian", endian = "endian")]
289+
struct ControllerHealthStatusPollRequest {
290+
sctlid: u16,
291+
maxrent: u8,
292+
functions: WireFlagSet<ControllerFunctionAndReportingFlags>,
293+
properties: WireFlagSet<ControllerPropertyFlags>,
294+
}
295+
296+
// MI v2.0, 5.3, Figure 96
297+
#[derive(Debug, DekuRead, DekuWrite)]
298+
#[deku(endian = "little")]
299+
struct ControllerHealthStatusPollResponse {
300+
status: ResponseStatus,
301+
#[deku(pad_bytes_before = "2", update = "self.body.len() as u8")]
302+
rent: u8,
303+
body: WireVec<ControllerHealthDataStructure, MAX_CONTROLLERS>,
304+
}
305+
impl Encode<{ 4 + 16 * MAX_CONTROLLERS }> for ControllerHealthStatusPollResponse {}
306+
307+
// MI v2.0, 5.3, Figure 97, CSTS
308+
flags! {
309+
pub enum ControllerStatusFlags: u16 {
310+
Rdy = 1 << 0,
311+
Cfs = 1 << 1,
312+
ShstInProgress = 1 << 2,
313+
ShstComplete = 1 << 3,
314+
ShstReserved = (ControllerStatusFlags::ShstInProgress | ControllerStatusFlags::ShstComplete).bits(),
315+
Nssro = 1 << 4,
316+
Ceco = 1 << 5,
317+
Nac = 1 << 6,
318+
Fa = 1 << 7,
319+
Tcida = 1 << 8,
320+
}
321+
}
322+
323+
// XXX: Consider improving the data model to handle the incongruence of the two flag
324+
// sets
325+
impl From<FlagSet<super::ControllerStatusFlags>> for WireFlagSet<ControllerStatusFlags> {
326+
fn from(value: FlagSet<super::ControllerStatusFlags>) -> Self {
327+
use super::ControllerStatusFlags as F;
328+
use ControllerStatusFlags as T;
329+
330+
let mut fs = FlagSet::empty();
331+
332+
for f in value {
333+
fs |= match f {
334+
F::Rdy => T::Rdy,
335+
F::Cfs => T::Cfs,
336+
F::ShstInProgress => T::ShstInProgress,
337+
F::ShstComplete => T::ShstComplete,
338+
F::ShstReserved => T::ShstReserved,
339+
F::Nssro => T::Nssro,
340+
F::Pp => todo!(),
341+
F::St => todo!(),
342+
};
343+
}
344+
345+
Self(fs)
346+
}
347+
}
348+
349+
// MI v2.0, 5.3, Figure 97, CWARN
350+
flags! {
351+
pub enum CriticalWarningFlags: u8 {
352+
St,
353+
Taut,
354+
Rd,
355+
Ro,
356+
Vmbf,
357+
Pmre
358+
}
359+
}
360+
361+
// MI v2.0, 5.3, Figure 97
362+
#[derive(Debug, DekuRead, DekuWrite)]
363+
#[deku(ctx = "endian: Endian", endian = "endian")]
364+
struct ControllerHealthDataStructure {
365+
ctlid: u16,
366+
csts: WireFlagSet<ControllerStatusFlags>,
367+
ctemp: u16,
368+
pdlu: u8,
369+
spare: u8,
370+
cwarn: WireFlagSet<CriticalWarningFlags>,
371+
#[deku(pad_bytes_after = "5")]
372+
chsc: WireFlagSet<ControllerHealthStatusChangedFlags>,
373+
}
374+
375+
// MI v2.0, 5.3, Figure 98
376+
flags! {
377+
// NOTE: These are the same as CompositeControllerStatusFlags
378+
pub enum ControllerHealthStatusChangedFlags: u16 {
379+
Rdy = 1 << 0,
380+
Cfs = 1 << 1,
381+
Shst = 1 << 2,
382+
Nssro = 1 << 4,
383+
Ceco = 1 << 5,
384+
Nac = 1 << 6,
385+
Fa = 1 << 7,
386+
Csts = 1 << 8,
387+
Ctemp = 1 << 9,
388+
Pdlu = 1 << 10,
389+
Spare = 1 << 11,
390+
Cwarn = 1 << 12,
391+
Tcida = 1 << 13,
392+
}
393+
}
394+
262395
// MI v2.0, 5.6, Figure 106
263396
#[derive(Debug, DekuRead, DekuWrite, Eq, PartialEq)]
264397
#[deku(ctx = "endian: Endian", endian = "endian")]
@@ -323,6 +456,13 @@ impl From<FlagSet<HealthStatusChangeFlags>> for CompositeControllerStatusFlagSet
323456
}
324457
}
325458

459+
impl From<FlagSet<ControllerHealthStatusChangedFlags>> for CompositeControllerStatusFlagSet {
460+
fn from(value: FlagSet<ControllerHealthStatusChangedFlags>) -> Self {
461+
// SAFETY: Separate declarations have the equal definitions
462+
Self(FlagSet::new(value.bits()).expect("Divergent flag definitions"))
463+
}
464+
}
465+
326466
// MI v2.0, 5.6, Figure 107, 108
327467
#[derive(Debug, DekuRead, DekuWrite)]
328468
#[deku(endian = "little")]

src/nvme/mi/dev.rs

Lines changed: 137 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,26 @@ use log::debug;
99
use mctp::{AsyncRespChannel, MsgIC};
1010

1111
use crate::{
12-
CommandEffect, CommandEffectError, Discriminant, MAX_NAMESPACES,
12+
CommandEffect, CommandEffectError, Discriminant, MAX_CONTROLLERS, MAX_NAMESPACES,
1313
nvme::{
1414
AdminGetLogPageLidRequestType, AdminGetLogPageSupportedLogPagesResponse,
1515
AdminIdentifyActiveNamespaceIdListResponse, AdminIdentifyAllocatedNamespaceIdListResponse,
1616
AdminIdentifyCnsRequestType, AdminIdentifyControllerResponse,
1717
AdminIdentifyNamespaceIdentificationDescriptorListResponse,
1818
AdminIdentifyNvmIdentifyNamespaceResponse, AdminIoCqeGenericCommandStatus,
19-
AdminIoCqeStatus, AdminIoCqeStatusType, ControllerListResponse, CriticalWarningFlags,
19+
AdminIoCqeStatus, AdminIoCqeStatusType, ControllerListResponse,
2020
LidSupportedAndEffectsDataStructure, LidSupportedAndEffectsFlags, LogPageAttributes,
2121
NamespaceIdentifierType, SmartHealthInformationLogPageResponse,
2222
mi::{
2323
AdminCommandRequestHeader, AdminCommandResponseHeader,
24-
CompositeControllerStatusDataStructureResponse, ControllerInformationResponse,
25-
MessageType, NvmSubsystemHealthDataStructureResponse, NvmSubsystemInformationResponse,
26-
NvmeManagementResponse, NvmeMiCommandRequestHeader, NvmeMiCommandRequestType,
27-
NvmeMiDataStructureManagementResponse, NvmeMiDataStructureRequestType,
28-
PciePortDataResponse, PortInformationResponse, TwoWirePortDataResponse,
24+
CompositeControllerStatusDataStructureResponse, CompositeControllerStatusFlagSet,
25+
ControllerFunctionAndReportingFlags, ControllerHealthDataStructure,
26+
ControllerHealthStatusPollResponse, ControllerInformationResponse,
27+
ControllerPropertyFlags, MessageType, NvmSubsystemHealthDataStructureResponse,
28+
NvmSubsystemInformationResponse, NvmeManagementResponse, NvmeMiCommandRequestHeader,
29+
NvmeMiCommandRequestType, NvmeMiDataStructureManagementResponse,
30+
NvmeMiDataStructureRequestType, PciePortDataResponse, PortInformationResponse,
31+
TwoWirePortDataResponse,
2932
},
3033
},
3134
wire::{WireString, WireVec},
@@ -232,6 +235,109 @@ impl RequestHandler for NvmeMiCommandRequestHeader {
232235
send_response(resp, &[&mh.0, &mr.0, &nvmshds.0, &ccs.0]).await;
233236
Ok(())
234237
}
238+
NvmeMiCommandRequestType::ControllerHealthStatusPoll(req) => {
239+
// MI v2.0, 5.3
240+
if !rest.is_empty() {
241+
debug!("Lost coherence decoding {:?}", ctx.opcode);
242+
return Err(ResponseStatus::InvalidCommandSize);
243+
}
244+
245+
if !req
246+
.functions
247+
.0
248+
.contains(ControllerFunctionAndReportingFlags::All)
249+
{
250+
debug!("TODO: Implement support for property-based selectors");
251+
return Err(ResponseStatus::InternalError);
252+
}
253+
254+
if req.functions.0.contains(
255+
ControllerFunctionAndReportingFlags::Incf
256+
| ControllerFunctionAndReportingFlags::Incpf
257+
| ControllerFunctionAndReportingFlags::Incvf,
258+
) {
259+
debug!("TODO: Implement support for function-base selectors");
260+
return Err(ResponseStatus::InternalError);
261+
}
262+
263+
assert!(MAX_CONTROLLERS <= u8::MAX as usize);
264+
if req.maxrent < MAX_CONTROLLERS as u8 {
265+
debug!("TODO: Implement response entry constraint");
266+
return Err(ResponseStatus::InternalError);
267+
}
268+
269+
if req.sctlid > 0 {
270+
debug!("TODO: Implement starting controller ID constraint");
271+
return Err(ResponseStatus::InternalError);
272+
}
273+
274+
let mh = MessageHeader::respond(MessageType::NvmeMiCommand).encode()?;
275+
276+
let mut chspr = ControllerHealthStatusPollResponse {
277+
status: ResponseStatus::Success,
278+
rent: 0,
279+
body: WireVec::new(),
280+
};
281+
282+
for ctlr in &subsys.ctlrs {
283+
chspr
284+
.body
285+
.push(ControllerHealthDataStructure {
286+
ctlid: ctlr.id.0,
287+
csts: ctlr.csts.into(),
288+
ctemp: ctlr.temp,
289+
pdlu: core::cmp::min(255, 100 * ctlr.write_age / ctlr.write_lifespan)
290+
as u8,
291+
spare: <u8>::try_from(100 * ctlr.spare / ctlr.capacity)
292+
.map_err(|_| ResponseStatus::InternalError)?
293+
.clamp(0, 100),
294+
cwarn: {
295+
let mut fs = FlagSet::empty();
296+
297+
if ctlr.spare < ctlr.spare_range.lower {
298+
fs |= crate::nvme::mi::CriticalWarningFlags::St;
299+
}
300+
301+
if ctlr.temp < ctlr.temp_range.lower
302+
|| ctlr.temp > ctlr.temp_range.upper
303+
{
304+
fs |= crate::nvme::mi::CriticalWarningFlags::Taut;
305+
}
306+
307+
// TODO: RD
308+
309+
if ctlr.ro {
310+
fs |= crate::nvme::mi::CriticalWarningFlags::Ro;
311+
}
312+
313+
// TODO: VMBF
314+
// TODO: PMRRO
315+
316+
fs.into()
317+
},
318+
chsc: {
319+
let mecs = &mut mep.mecss[ctlr.id.0 as usize];
320+
let fs = mecs.chscf;
321+
322+
if req.properties.0.contains(ControllerPropertyFlags::Ccf) {
323+
mecs.chscf.clear();
324+
// TODO: Clear NAC, FA, TCIDA in controller health
325+
}
326+
327+
fs.into()
328+
},
329+
})
330+
.map_err(|_| {
331+
debug!("Failed to push ControllerHealthDataStructure");
332+
ResponseStatus::InternalError
333+
})?;
334+
}
335+
chspr.update()?;
336+
let chspr = chspr.encode()?;
337+
338+
send_response(resp, &[&mh.0, &chspr.0[..chspr.1]]).await;
339+
Ok(())
340+
}
235341
NvmeMiCommandRequestType::ConfigurationSet(cid) => {
236342
cid.handle(ctx, mep, subsys, rest, resp, app).await
237343
}
@@ -970,17 +1076,17 @@ impl RequestHandler for AdminGetLogPageRequest {
9701076
let mut fs = FlagSet::empty();
9711077

9721078
if ctlr.spare < ctlr.spare_range.lower {
973-
fs |= CriticalWarningFlags::Ascbt;
1079+
fs |= crate::nvme::CriticalWarningFlags::Ascbt;
9741080
}
9751081

9761082
if ctlr.temp < ctlr.temp_range.lower || ctlr.temp > ctlr.temp_range.upper {
977-
fs |= CriticalWarningFlags::Ttc;
1083+
fs |= crate::nvme::CriticalWarningFlags::Ttc;
9781084
}
9791085

9801086
// TODO: NDR
9811087

9821088
if ctlr.ro {
983-
fs |= CriticalWarningFlags::Amro;
1089+
fs |= crate::nvme::CriticalWarningFlags::Amro;
9841090
}
9851091

9861092
// TODO: VMBF
@@ -1350,16 +1456,35 @@ impl crate::ManagementEndpoint {
13501456
for c in &subsys.ctlrs {
13511457
let mecs = &mut self.mecss[c.id.0 as usize];
13521458

1459+
// It might seem tempting to compose self.ccsf with an
1460+
// assignment-union over each controller's mecs.chscf. However, this
1461+
// doesn't work in practice due to the requirements of NVMe MI /
1462+
// Configuration Set / Health Status Change on the behaviour of
1463+
// clearing the composite controller flags, against the requirements
1464+
// of NVMe MI / Controller Health Status Poll on the behaviour of
1465+
// clearing the controller health status flags.
1466+
//
1467+
// Instead, update each independently by first gathering the change
1468+
// flags for the current update cycle, then using union-assignment
1469+
// into both mecs.chscf and self.ccsf (in the case of the latter,
1470+
// via the conversion to the composite controller flag set).
1471+
let mut update = FlagSet::empty();
1472+
13531473
if mecs.cc.en != c.cc.en {
1354-
self.ccsf.0 |= crate::nvme::mi::CompositeControllerStatusFlags::Ceco;
1474+
update |= crate::nvme::mi::ControllerHealthStatusChangedFlags::Ceco;
13551475
}
13561476

13571477
if mecs.csts.contains(crate::nvme::ControllerStatusFlags::Rdy)
13581478
!= c.csts.contains(crate::nvme::ControllerStatusFlags::Rdy)
13591479
{
1360-
self.ccsf.0 |= crate::nvme::mi::CompositeControllerStatusFlags::Rdy;
1480+
update |= crate::nvme::mi::ControllerHealthStatusChangedFlags::Rdy;
13611481
}
13621482

1483+
mecs.chscf |= update;
1484+
1485+
let update: CompositeControllerStatusFlagSet = update.into();
1486+
self.ccsf.0 |= update.0;
1487+
13631488
mecs.cc = c.cc;
13641489
mecs.csts = c.csts;
13651490
}

0 commit comments

Comments
 (0)