diff --git a/src/lib.rs b/src/lib.rs index c1f364c..df45577 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,6 +16,7 @@ use nvme::{ use uuid::Uuid; pub mod nvme; +mod pcie; mod wire; extern crate deku; diff --git a/src/nvme/mi.rs b/src/nvme/mi.rs index fd74164..6ff4f68 100644 --- a/src/nvme/mi.rs +++ b/src/nvme/mi.rs @@ -84,6 +84,7 @@ pub enum ResponseStatus { InvalidParameter = 0x04, InvalidCommandSize = 0x05, InvalidCommandInputDataSize = 0x06, + AccessDenied = 0x07, } unsafe impl Discriminant for ResponseStatus {} @@ -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 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, +} diff --git a/src/nvme/mi/dev.rs b/src/nvme/mi/dev.rs index 02726b1..28c184b 100644 --- a/src/nvme/mi/dev.rs +++ b/src/nvme/mi/dev.rs @@ -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}, }; @@ -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) @@ -2003,6 +2016,82 @@ impl RequestHandler for AdminFormatNvmRequest { } } +impl RequestHandler for PcieCommandRequestHeader { + type Ctx = PcieCommandRequestHeader; + + async fn handle( + &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()); diff --git a/src/pcie.rs b/src/pcie.rs new file mode 100644 index 0000000..746d197 --- /dev/null +++ b/src/pcie.rs @@ -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 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 for PciCapabilityType {} diff --git a/tests/pcie.rs b/tests/pcie.rs new file mode 100644 index 0000000..e034fb9 --- /dev/null +++ b/tests/pcie.rs @@ -0,0 +1,248 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Copyright (c) 2025 Code Construct + */ +mod common; + +use common::setup; +use mctp::MsgIC; + +use crate::common::{DeviceType, ExpectedRespChannel, new_device}; + +#[rustfmt::skip] +pub const RESP_INVALID_COMMAND_SIZE: [u8; 11] = [ + 0xa0, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, + 0x81, 0xb0, 0x66, 0xf7 +]; + +#[rustfmt::skip] +pub const RESP_INVALID_COMMAND_INPUT_DATA_SIZE: [u8; 11] = [ + 0xa0, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, + 0xb8, 0x39, 0x44, 0x95 +]; + +#[rustfmt::skip] +pub const RESP_ACCESS_DENIED: [u8; 11] = [ + 0xa0, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, + 0x00, 0x93, 0x01, 0x48 +]; + +#[test] +fn configuration_read_short() { + setup(); + + let (mut mep, mut subsys) = new_device(DeviceType::P1p1tC1iN0a0a); + + #[rustfmt::skip] + const REQ: [u8; 19] = [ + 0x20, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + + // PCIe Request DWORD 0 + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + // Missing PCIe Request DWORD 2 + + // MIC + 0x95, 0xbf, 0x48, 0xfc + ]; + + let resp = ExpectedRespChannel::new(&RESP_INVALID_COMMAND_SIZE); + smol::block_on(async { + mep.handle_async(&mut subsys, &REQ, MsgIC(true), resp, async |_| Ok(())) + .await + }) +} + +#[test] +fn configuration_read_long() { + setup(); + + let (mut mep, mut subsys) = new_device(DeviceType::P1p1tC1iN0a0a); + + #[rustfmt::skip] + const REQ: [u8; 27] = [ + 0x20, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + + // PCIe Request DWORD 0 + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + + // Unexpected request data + 0x00, 0x00, 0x00, 0x00, + + // MIC + 0x86, 0x75, 0x81, 0xd5 + ]; + + let resp = ExpectedRespChannel::new(&RESP_INVALID_COMMAND_SIZE); + smol::block_on(async { + mep.handle_async(&mut subsys, &REQ, MsgIC(true), resp, async |_| Ok(())) + .await + }) +} + +#[test] +fn configuration_read() { + setup(); + + let (mut mep, mut subsys) = new_device(DeviceType::P1p1tC1iN0a0a); + + #[rustfmt::skip] + const REQ: [u8; 23] = [ + 0x20, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + + // PCIe Request DWORD 0 + 0x00, 0x10, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + + // MIC + 0x8f, 0x1b, 0x1a, 0x82 + ]; + + #[rustfmt::skip] + const PCI_CONFIG: [u8; 132] = [ + 0xff, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x10, 0x00, + 0x00, 0x03, 0x08, 0x01, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, + 0x40, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x01, 0x48, 0x13, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + ]; + + const RESP_LEN: usize = 4107; + let mut resp = [0; RESP_LEN]; + resp[0] = 0xa0; + resp[7..139].copy_from_slice(&PCI_CONFIG); + resp[{ RESP_LEN - 4 }..].copy_from_slice(&[0xab, 0x72, 0xdb, 0xa3]); + + let resp = ExpectedRespChannel::new(&resp); + smol::block_on(async { + mep.handle_async(&mut subsys, &REQ, MsgIC(true), resp, async |_| Ok(())) + .await + }) +} + +#[test] +fn configuration_write_invalid_short() { + setup(); + + let (mut mep, mut subsys) = new_device(DeviceType::P1p1tC1iN0a0a); + + #[rustfmt::skip] + const REQ: [u8; 23] = [ + 0x20, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + + // PCIe Request DWORD 0 + 0x04, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + + // No request Data + + // MIC + 0xb5, 0x79, 0x5c, 0xc9 + ]; + + let resp = ExpectedRespChannel::new(&RESP_INVALID_COMMAND_INPUT_DATA_SIZE); + smol::block_on(async { + mep.handle_async(&mut subsys, &REQ, MsgIC(true), resp, async |_| Ok(())) + .await + }) +} + +#[test] +fn configuration_write_invalid_long() { + setup(); + + let (mut mep, mut subsys) = new_device(DeviceType::P1p1tC1iN0a0a); + + #[rustfmt::skip] + const REQ: [u8; 31] = [ + 0x20, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + + // PCIe Request DWORD 0 + 0x04, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + + // Request Data + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + + // MIC + 0x01, 0x87, 0x99, 0x1c + ]; + + let resp = ExpectedRespChannel::new(&RESP_INVALID_COMMAND_INPUT_DATA_SIZE); + smol::block_on(async { + mep.handle_async(&mut subsys, &REQ, MsgIC(true), resp, async |_| Ok(())) + .await + }) +} + +#[test] +fn configuration_write() { + setup(); + + let (mut mep, mut subsys) = new_device(DeviceType::P1p1tC1iN0a0a); + + #[rustfmt::skip] + const REQ: [u8; 27] = [ + 0x20, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + + // PCIe Request DWORD 0 + 0x04, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + + // Request Data + 0x00, 0x00, 0x00, 0x00, + + // MIC + 0x10, 0x7e, 0x01, 0xe1 + ]; + + let resp = ExpectedRespChannel::new(&RESP_ACCESS_DENIED); + smol::block_on(async { + mep.handle_async(&mut subsys, &REQ, MsgIC(true), resp, async |_| Ok(())) + .await + }) +}