Skip to content
Merged
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
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use nvme::{
use uuid::Uuid;

pub mod nvme;
mod pcie;
mod wire;

extern crate deku;
Expand Down
38 changes: 38 additions & 0 deletions src/nvme/mi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ pub enum ResponseStatus {
InvalidParameter = 0x04,
InvalidCommandSize = 0x05,
InvalidCommandInputDataSize = 0x06,
AccessDenied = 0x07,
}
unsafe impl Discriminant<u8> for ResponseStatus {}

Expand Down Expand Up @@ -908,3 +909,40 @@ struct AdminCommandResponseHeader {
cqedw3: u32,
}
impl Encode<16> for AdminCommandResponseHeader {}

// MI v2.0, 7, Figure 146
#[derive(Debug, DekuRead, DekuWrite)]
#[deku(endian = "little")]
struct PcieCommandRequestHeader {
_opcode: u8,
#[deku(seek_from_current = "1")]
ctlid: u16,
#[deku(ctx = "*_opcode")]
op: PcieCommandRequestType,
}

// MI v2.0, 7, Figure 148
#[derive(Debug, DekuRead, DekuWrite, Eq, PartialEq)]
#[deku(ctx = "endian: Endian, opcode: u8", id = "opcode", endian = "endian")]
#[repr(u8)]
enum PcieCommandRequestType {
#[deku(id = 0x00)]
ConfigurationRead(PcieConfigurationAccessRequest),
#[deku(id = 0x01)]
ConfigurationWrite(PcieConfigurationAccessRequest),
MemoryRead = 0x02,
MemoryWrite = 0x03,
IoRead = 0x04,
IoWrite = 0x05,
}
unsafe impl Discriminant<u8> for PcieCommandRequestType {}

// MI v2.0, 7, Figure 151-152
#[derive(Debug, DekuRead, DekuWrite, Eq, PartialEq)]
#[deku(ctx = "endian: Endian", endian = "endian")]
struct PcieConfigurationAccessRequest {
length: u16,
#[deku(seek_from_current = "2")]
#[deku(pad_bytes_after = "6")]
offset: u16,
}
93 changes: 91 additions & 2 deletions src/nvme/mi/dev.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,11 @@ use crate::{
ControllerPropertyFlags, MessageType, NvmSubsystemHealthDataStructureResponse,
NvmSubsystemInformationResponse, NvmeManagementResponse, NvmeMiCommandRequestHeader,
NvmeMiCommandRequestType, NvmeMiDataStructureManagementResponse,
NvmeMiDataStructureRequestType, PciePortDataResponse, PortInformationResponse,
TwoWirePortDataResponse,
NvmeMiDataStructureRequestType, PcieCommandRequestHeader, PciePortDataResponse,
PortInformationResponse, TwoWirePortDataResponse,
},
},
pcie::PciDeviceFunctionConfigurationSpace,
wire::{WireString, WireVec},
};

Expand Down Expand Up @@ -121,6 +122,18 @@ impl RequestHandler for MessageHeader {
}
}
}
MessageType::PcieCommand => {
match &PcieCommandRequestHeader::from_bytes((rest, 0)) {
Ok(((rest, _), ch)) => ch.handle(ch, mep, subsys, rest, resp, app).await,
Err(err) => {
debug!(
"Unable to parse PcieCommandRequestHeader from message buffer: {err:?}"
);
// TODO: This is a bad assumption: Can see DekuError::InvalidParam too
Err(ResponseStatus::InvalidCommandSize)
}
}
}
_ => {
debug!("Unimplemented NMINT: {:?}", ctx.nmimt());
Err(ResponseStatus::InternalError)
Expand Down Expand Up @@ -2003,6 +2016,82 @@ impl RequestHandler for AdminFormatNvmRequest {
}
}

impl RequestHandler for PcieCommandRequestHeader {
type Ctx = PcieCommandRequestHeader;

async fn handle<A, C>(
&self,
ctx: &Self::Ctx,
_mep: &mut crate::ManagementEndpoint,
subsys: &mut crate::Subsystem,
rest: &[u8],
resp: &mut C,
_app: A,
) -> Result<(), ResponseStatus>
where
A: AsyncFnMut(crate::CommandEffect) -> Result<(), CommandEffectError>,
C: mctp::AsyncRespChannel,
{
match &ctx.op {
super::PcieCommandRequestType::ConfigurationRead(req) => {
if !rest.is_empty() {
debug!("Invalid request size for PcieCommand");
return Err(ResponseStatus::InvalidCommandSize);
}

if req.length != 4096 {
debug!("Implement length support");
return Err(ResponseStatus::InternalError);
}

if req.offset != 0 {
debug!("Implement offset support");
return Err(ResponseStatus::InternalError);
}

let mh = MessageHeader::respond(MessageType::PcieCommand).encode()?;

let status = [0u8; 4]; /* Success */

let cr = PciDeviceFunctionConfigurationSpace::builder()
.vid(subsys.info.pci_vid)
.did(subsys.info.pci_did)
.svid(subsys.info.pci_svid)
.sdid(subsys.info.pci_sdid)
.build()
.encode()?;

send_response(resp, &[&mh.0, &status, &cr.0]).await;
Ok(())
}
super::PcieCommandRequestType::ConfigurationWrite(req) => {
let response = if rest.len() == req.length as usize {
debug!("Unsupported write at {} for {}", req.offset, req.length);
ResponseStatus::AccessDenied
} else {
debug!(
"Request data size {} does not match requested write size {}",
rest.len(),
req.length
);
ResponseStatus::InvalidCommandInputDataSize
};

let mh = MessageHeader::respond(MessageType::PcieCommand).encode()?;

let status = [response.id(), 0, 0, 0];

send_response(resp, &[&mh.0, &status]).await;
Ok(())
}
_ => {
debug!("Unimplemented OPCODE: {:?}", ctx._opcode);
Err(ResponseStatus::InternalError)
}
}
}
}

impl crate::ManagementEndpoint {
fn update(&mut self, subsys: &crate::Subsystem) {
assert!(subsys.ctlrs.len() <= self.mecss.len());
Expand Down
216 changes: 216 additions & 0 deletions src/pcie.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Copyright (c) 2025 Code Construct
*/
use deku::ctx::Endian;
use deku::{DekuRead, DekuWrite};

// PCIe Base 4.0r1.0, 7.5.1.2, Figure 7-10
#[derive(Debug, DekuRead, DekuWrite)]
#[deku(endian = "little")]
pub struct PciDeviceFunctionConfigurationSpace {
vid: u16,
did: u16,
cmd: u16,
sts: u16,
rid: u8,
#[deku(bytes = "3")]
cc: u32,
cls: u8,
lt: u8,
ht: u8,
bist: u8,
bars: [u32; 6],
cis: u32,
svid: u16,
sdid: u16,
rom: u32,
cap: u8,
#[deku(seek_from_current = "7")]
il: u8,
ip: u8,
min_gnt: u8,
max_lat: u8,
caps: [PciCapabilityType; 2],
}
impl crate::Encode<4096> for PciDeviceFunctionConfigurationSpace {}

impl PciDeviceFunctionConfigurationSpace {
pub fn new() -> Self {
Self {
vid: 0xffff,
did: 0xffff,
cmd: 0,
sts: 0x0010,
rid: 0,
cc: 0x010803,
cls: 0,
lt: 0,
ht: 0,
bist: 0,
bars: [0; 6],
cis: 0,
svid: 0xffff,
sdid: 0xffff,
rom: 0,
cap: 0x40,
il: 0,
ip: 0,
min_gnt: 0,
max_lat: 0,
caps: [
PciCapabilityType::PciPowerManagement(PciPowerManagementCapability {
next: 0x48,
pmc: {
PowerManagementCapabilities {
version: 3,
pme_clock: false,
ready_d0: true,
dsi: false,
aux_current: 0,
d1: false,
d2: false,
pme: 0,
}
}
.into(),
pmcsr: 0,
data: 0,
}),
PciCapabilityType::Pcie(PcieCapability::default()),
],
}
}

pub fn builder() -> PciDeviceFunctionConfigurationSpaceBuilder {
Default::default()
}
}

impl Default for PciDeviceFunctionConfigurationSpace {
fn default() -> Self {
PciDeviceFunctionConfigurationSpace::new()
}
}

pub struct PciDeviceFunctionConfigurationSpaceBuilder {
vid: u16,
did: u16,
svid: u16,
sdid: u16,
}

impl Default for PciDeviceFunctionConfigurationSpaceBuilder {
fn default() -> Self {
Self {
vid: 0xffff,
did: 0xffff,
svid: 0xffff,
sdid: 0xffff,
}
}
}

impl PciDeviceFunctionConfigurationSpaceBuilder {
pub fn vid(&mut self, vid: u16) -> &mut Self {
self.vid = vid;
self
}

pub fn did(&mut self, did: u16) -> &mut Self {
self.did = did;
self
}

pub fn svid(&mut self, svid: u16) -> &mut Self {
self.svid = svid;
self
}

pub fn sdid(&mut self, sdid: u16) -> &mut Self {
self.sdid = sdid;
self
}

pub fn build(&self) -> PciDeviceFunctionConfigurationSpace {
PciDeviceFunctionConfigurationSpace {
vid: self.vid,
did: self.did,
svid: self.svid,
sdid: self.sdid,
..Default::default()
}
}
}

#[derive(Debug)]
pub struct PowerManagementCapabilities {
version: u8,
pme_clock: bool,
ready_d0: bool,
dsi: bool,
aux_current: u8,
d1: bool,
d2: bool,
pme: u8,
}

impl From<PowerManagementCapabilities> for u16 {
fn from(value: PowerManagementCapabilities) -> Self {
((value.pme as u16 & 0xf) << 11)
| ((value.d2 as u16) << 10)
| ((value.d1 as u16) << 9)
| ((value.aux_current as u16 & 0x7) << 6)
| ((value.dsi as u16) << 5)
| ((value.ready_d0 as u16) << 4)
| ((value.pme_clock as u16) << 3)
| (value.version as u16 & 0x7)
}
}

#[derive(Debug, DekuRead, DekuWrite)]
#[deku(ctx = "endian: Endian", endian = "endian")]
pub struct PciPowerManagementCapability {
next: u8,
pmc: u16,
pmcsr: u16,
#[deku(seek_from_current = "1")]
data: u8,
}

#[derive(Debug, Default, DekuRead, DekuWrite)]
#[deku(ctx = "endian: Endian", endian = "endian")]
pub struct PcieCapability {
next: u8,
pciec: u16,
devcap: u32,
devctl: u16,
devsts: u16,
linkcap: u32,
linkctl: u16,
linksts: u16,
slotctl: u16,
slotsts: u16,
rootctl: u16,
rootsts: u16,
devcap2: u32,
devctl2: u16,
devsts2: u16,
linkcap2: u32,
linkctl2: u16,
linksts2: u16,
slotcap2: u32,
slotctl2: u16,
slotsts2: u16,
}

#[derive(Debug, DekuRead, DekuWrite)]
#[deku(ctx = "endian: Endian", endian = "endian", id_type = "u8")]
#[repr(u8)]
pub enum PciCapabilityType {
#[deku(id = "0x01")]
PciPowerManagement(PciPowerManagementCapability),
#[deku(id = "0x10")]
Pcie(PcieCapability),
}
unsafe impl crate::Discriminant<u8> for PciCapabilityType {}
Loading