Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add more USDT probes to NVMe emulation #496

Closed
wants to merge 1 commit into from
Closed
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
29 changes: 29 additions & 0 deletions lib/propolis/src/hw/nvme/cmds.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ use crate::vmm::MemCtx;

use thiserror::Error;

#[usdt::provider(provider = "propolis")]
mod probes {
fn nvme_prp_entry(iter: u64, prp: u64) {}
fn nvme_prp_list(iter: u64, prp: u64, idx: u16) {}
fn nvme_prp_error(err: &'static str) {}
}

/// Errors that may be encounted during command parsing.
#[derive(Debug, Error)]
pub enum ParseErr {
Expand Down Expand Up @@ -608,6 +615,10 @@ impl PrpIter<'_> {
PrpNext::Prp1 => {
// The first PRP entry contained within the command may have a
// non-zero offset within the memory page.
probes::nvme_prp_entry!(|| (
self as *const Self as u64,
self.prp1
));
let offset = self.prp1 & PAGE_OFFSET as u64;
let size = u64::min(PAGE_SIZE as u64 - offset, self.remain);
let after = self.remain - size;
Expand Down Expand Up @@ -648,6 +659,11 @@ impl PrpIter<'_> {
// the end of the list).
let base = self.prp2 & (PAGE_MASK as u64);
let idx = (self.prp2 & PAGE_OFFSET as u64) / 8;
probes::nvme_prp_list!(|| (
self as *const Self as u64,
base,
idx as u16,
));
PrpNext::List(base, idx as u16)
};
(self.prp1, size, next)
Expand All @@ -658,6 +674,10 @@ impl PrpIter<'_> {
if self.prp2 & PAGE_OFFSET as u64 != 0 {
return Err("Inappropriate PRP2 offset");
}
probes::nvme_prp_entry!(|| (
self as *const Self as u64,
self.prp2
));
let size = self.remain;
assert!(size <= PAGE_SIZE as u64);
(self.prp2, size, PrpNext::Done)
Expand All @@ -669,6 +689,9 @@ impl PrpIter<'_> {
.mem
.read(GuestAddr(entry_addr))
.ok_or_else(|| "Unable to read PRP list entry")?;
probes::nvme_prp_entry!(
|| (self as *const Self as u64, entry,)
);

if entry & PAGE_OFFSET as u64 != 0 {
return Err("Inappropriate PRP list entry offset");
Expand All @@ -682,6 +705,11 @@ impl PrpIter<'_> {
// The last PRP in this list chains to another
// (page-aligned) list with the next PRP.
self.next = PrpNext::List(entry, 0);
probes::nvme_prp_list!(|| (
self as *const Self as u64,
entry,
0,
));
return self.get_next();
}
}
Expand Down Expand Up @@ -713,6 +741,7 @@ impl Iterator for PrpIter<'_> {
match self.get_next() {
Ok(res) => Some(res),
Err(e) => {
probes::nvme_prp_error!(|| (e));
self.error = Some(e);
None
}
Expand Down
14 changes: 13 additions & 1 deletion lib/propolis/src/hw/nvme/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@ mod requests;
use bits::*;
use queue::{CompQueue, QueueId, SubQueue};

#[usdt::provider(provider = "propolis")]
mod probes {
fn nvme_doorbell(off: u64, qid: u16, is_cq: u8, val: u16) {}
fn nvme_doorbell_admin_cq(val: u16) {}
fn nvme_doorbell_admin_sq(val: u16) {}
fn nvme_admin_cmd(opcode: u8, prp1: u64, prp2: u64) {}
}

/// The max number of MSI-X interrupts we support
const NVME_MSIX_COUNT: u16 = 1024;

Expand Down Expand Up @@ -757,6 +765,7 @@ impl PciNvme {
CtrlrReg::DoorBellAdminSQ => {
// 32-bit register but ignore reserved top 16-bits
let val = wo.read_u32() as u16;
probes::nvme_doorbell_admin_sq!(|| (val));
let state = self.state.lock().unwrap();

if !state.ctrl.cc.enabled() {
Expand All @@ -778,6 +787,7 @@ impl PciNvme {
CtrlrReg::DoorBellAdminCQ => {
// 32-bit register but ignore reserved top 16-bits
let val = wo.read_u32() as u16;
probes::nvme_doorbell_admin_cq!(|| (val));
let state = self.state.lock().unwrap();

if !state.ctrl.cc.enabled() {
Expand Down Expand Up @@ -833,6 +843,7 @@ impl PciNvme {

// 32-bit register but ignore reserved top 16-bits
let val = wo.read_u32() as u16;
probes::nvme_doorbell!(|| (off as u64, qid, is_cq as u8, val));
if is_cq {
// Completion Queue y Head Doorbell
let cq = state.get_cq(qid)?;
Expand Down Expand Up @@ -881,14 +892,15 @@ impl PciNvme {
}
let mem = mem.unwrap();

while let Some((sub, cqe_permit)) = sq.pop(&mem) {
while let Some((sub, cqe_permit, _idx)) = sq.pop(&mem) {
use cmds::AdminCmd;

let parsed = AdminCmd::parse(sub);
if parsed.is_err() {
// XXX: set controller error state?
continue;
}
probes::nvme_admin_cmd!(|| (sub.opcode(), sub.prp1, sub.prp2));
let cmd = parsed.unwrap();
let comp = match cmd {
AdminCmd::CreateIOCompQ(cmd) => {
Expand Down
17 changes: 12 additions & 5 deletions lib/propolis/src/hw/nvme/queue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ use crate::vmm::MemCtx;

use thiserror::Error;

#[usdt::provider(provider = "propolis")]
mod probes {
fn nvme_cqe(qid: u16, idx: u16, phase: u8) {}
}

/// Each queue is identified by a 16-bit ID.
///
/// See NVMe 1.0e Section 4.1.4 Queue Identifier
Expand Down Expand Up @@ -379,13 +384,13 @@ impl SubQueue {
pub fn pop(
self: &Arc<SubQueue>,
mem: &MemCtx,
) -> Option<(bits::RawSubmission, CompQueueEntryPermit)> {
) -> Option<(bits::RawSubmission, CompQueueEntryPermit, u16)> {
// Attempt to reserve an entry on the Completion Queue
let cqe_permit = self.cq.reserve_entry(self.clone())?;
if let Some(idx) = self.state.pop_head() {
let ent: Option<RawSubmission> = mem.read(self.entry_addr(idx));
// XXX: handle a guest addr that becomes unmapped later
ent.map(|ent| (ent, cqe_permit))
ent.map(|ent| (ent, cqe_permit, idx))
} else {
// No Submission Queue entry, so return the CQE permit
cqe_permit.remit();
Expand All @@ -400,7 +405,7 @@ impl SubQueue {
}

/// Returns the ID of this Submission Queue.
fn id(&self) -> QueueId {
pub(super) fn id(&self) -> QueueId {
self.id
}

Expand Down Expand Up @@ -576,6 +581,7 @@ impl CompQueue {
// Since we have a permit, there should always be at least
// one space in the queue and this unwrap shouldn't fail.
let idx = self.state.push_tail().unwrap();
probes::nvme_cqe!(|| (self.id, idx, (entry.status_phase & 1) as u8));
let addr = self.entry_addr(idx);
mem.write(addr, &entry);
// XXX: handle a guest addr that becomes unmapped later
Expand Down Expand Up @@ -946,7 +952,7 @@ mod tests {
));

// Now pop those SQ items and complete them in the CQ
while let Some((_, permit)) = sq.pop(&mem) {
while let Some((_, permit, _)) = sq.pop(&mem) {
permit.push_completion_test(&mem);
}

Expand Down Expand Up @@ -1116,7 +1122,8 @@ mod tests {

let mut rng = rand::thread_rng();
while let Ok(()) = worker_rx.recv() {
while let Some((_, cqe_permit)) = worker_sq.pop(&mem) {
while let Some((_, cqe_permit, _)) = worker_sq.pop(&mem)
{
submissions += 1;

// Sleep for a bit to mimic actually doing
Expand Down
54 changes: 44 additions & 10 deletions lib/propolis/src/hw/nvme/requests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,23 @@ use super::{cmds::NvmCmd, queue::CompQueueEntryPermit, PciNvme};

#[usdt::provider(provider = "propolis")]
mod probes {
fn nvme_read_enqueue(cid: u16, off: u64, sz: u64) {}
fn nvme_read_complete(cid: u16) {}
fn nvme_read_enqueue(qid: u16, idx: u16, cid: u16, off: u64, sz: u64) {}
fn nvme_read_complete(cid: u16, res: u8) {}

fn nvme_write_enqueue(cid: u16, off: u64, sz: u64) {}
fn nvme_write_complete(cid: u16) {}
fn nvme_write_enqueue(qid: u16, idx: u16, cid: u16, off: u64, sz: u64) {}
fn nvme_write_complete(cid: u16, res: u8) {}

fn nvme_flush_enqueue(qid: u16, idx: u16, cid: u16) {}
fn nvme_flush_complete(cid: u16, res: u8) {}

fn nvme_raw_cmd(
qid: u16,
cdw0nsid: u64,
prp1: u64,
prp2: u64,
cdw10cdw11: u64,
) {
}
}

impl block::Device for PciNvme {
Expand Down Expand Up @@ -58,7 +70,17 @@ impl PciNvme {
// Go through all the queues (skip admin as we just want I/O queues)
// looking for a request to service
for sq in state.sqs.iter().skip(1).flatten() {
while let Some((sub, cqe_permit)) = sq.pop(&mem) {
while let Some((sub, cqe_permit, idx)) = sq.pop(&mem) {
let qid = sq.id();
probes::nvme_raw_cmd!(|| {
(
qid,
sub.cdw0 as u64 | ((sub.nsid as u64) << 32),
sub.prp1,
sub.prp2,
(sub.cdw10 as u64 | ((sub.cdw11 as u64) << 32)),
)
});
let cmd = NvmCmd::parse(sub);
let cid = sub.cid();
match cmd {
Expand All @@ -72,7 +94,9 @@ impl PciNvme {
Ok(NvmCmd::Write(cmd)) => {
let off = state.nlb_to_size(cmd.slba as usize) as u64;
let size = state.nlb_to_size(cmd.nlb as usize) as u64;
probes::nvme_write_enqueue!(|| (cid, off, size));
probes::nvme_write_enqueue!(|| (
qid, idx, cid, off, size
));

let bufs = cmd.data(size, &mem).collect();
let req = Request::new_write(
Expand All @@ -86,7 +110,9 @@ impl PciNvme {
let off = state.nlb_to_size(cmd.slba as usize) as u64;
let size = state.nlb_to_size(cmd.nlb as usize) as u64;

probes::nvme_read_enqueue!(|| (cid, off, size));
probes::nvme_read_enqueue!(|| (
qid, idx, cid, off, size
));

let bufs = cmd.data(size, &mem).collect();
let req = Request::new_read(
Expand All @@ -97,6 +123,7 @@ impl PciNvme {
return Some(req);
}
Ok(NvmCmd::Flush) => {
probes::nvme_flush_enqueue!(|| (qid, idx, cid));
let req = Request::new_flush(
0,
0, // TODO: is 0 enough or do we pass total size?
Expand Down Expand Up @@ -130,14 +157,21 @@ impl PciNvme {
payload.cqe_permit.take().expect("permit must be present");
let cid = payload.cid;

let resnum: u8 = match &res {
BlockResult::Success => 0,
BlockResult::Failure => 1,
BlockResult::Unsupported => 2,
};
match op {
Operation::Read(..) => {
probes::nvme_read_complete!(|| (cid));
probes::nvme_read_complete!(|| (cid, resnum));
}
Operation::Write(..) => {
probes::nvme_write_complete!(|| (cid));
probes::nvme_write_complete!(|| (cid, resnum));
}
Operation::Flush(..) => {
probes::nvme_flush_complete!(|| (cid, resnum));
}
_ => {}
}

let mem = self.mem_access();
Expand Down
Loading