Skip to content

Commit 8982492

Browse files
authored
NVMe: don't panic if guests tries a doorbell write for admin queues while controller is disabled (#309)
* nvme: use bare cast instead of try_into to better match spec The spec defines the doorbell registers as 32-bits wide but leaves the upper 16 bits as reserved. Now, a guest shouldn't be setting those upper bits but we can't control that. To better model that, just use a bare `as` cast to keep the bottom 16 bits rather than potentially failing via unwrapping a valid try_into. * nvme: don't process doorbell writes if controller disabled While the guest shouldn't be trying to write to the Admin or IO queue's doorbell registers when the controller isn't enabled, we also shouldn't just panic. Just log a warning and bail out. * nvme: remove unwraps for grabbing admin queues. Even though we should only be trying to unwrap at points we know the admin queues should exist, don't assume they are. We should treat them same as the IO queues where we'll bail with an error that gets logged instead of panic'ing.
1 parent e6a479a commit 8982492

File tree

1 file changed

+55
-24
lines changed
  • lib/propolis/src/hw/nvme

1 file changed

+55
-24
lines changed

lib/propolis/src/hw/nvme/mod.rs

Lines changed: 55 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -298,21 +298,13 @@ impl NvmeCtrl {
298298
}
299299

300300
/// Returns a reference to the Admin [`CompQueue`].
301-
///
302-
/// # Panics
303-
///
304-
/// Panics if the Admin Completion Queue hasn't been created yet.
305-
fn get_admin_cq(&self) -> Arc<CompQueue> {
306-
self.get_cq(queue::ADMIN_QUEUE_ID).unwrap()
301+
fn get_admin_cq(&self) -> Result<Arc<CompQueue>, NvmeError> {
302+
self.get_cq(queue::ADMIN_QUEUE_ID)
307303
}
308304

309305
/// Returns a reference to the Admin [`SubQueue`].
310-
///
311-
/// # Panics
312-
///
313-
/// Panics if the Admin Submission Queue hasn't been created yet.
314-
fn get_admin_sq(&self) -> Arc<SubQueue> {
315-
self.get_sq(queue::ADMIN_QUEUE_ID).unwrap()
306+
fn get_admin_sq(&self) -> Result<Arc<SubQueue>, NvmeError> {
307+
self.get_sq(queue::ADMIN_QUEUE_ID)
316308
}
317309

318310
/// Configure Controller
@@ -760,25 +752,49 @@ impl PciNvme {
760752
}
761753

762754
CtrlrReg::DoorBellAdminSQ => {
763-
let val = wo.read_u32().try_into().unwrap();
755+
// 32-bit register but ignore reserved top 16-bits
756+
let val = wo.read_u32() as u16;
764757
let state = self.state.lock().unwrap();
765-
let admin_sq = state.get_admin_sq();
758+
759+
if !state.ctrl.cc.enabled() {
760+
slog::warn!(
761+
self.log,
762+
"Doorbell write while controller is disabled"
763+
);
764+
return Err(NvmeError::InvalidSubQueue(
765+
queue::ADMIN_QUEUE_ID,
766+
));
767+
}
768+
769+
let admin_sq = state.get_admin_sq()?;
766770
admin_sq.notify_tail(val)?;
767771

768772
// Process any new SQ entries
769773
self.process_admin_queue(state, admin_sq)?;
770774
}
771775
CtrlrReg::DoorBellAdminCQ => {
772-
let val = wo.read_u32().try_into().unwrap();
776+
// 32-bit register but ignore reserved top 16-bits
777+
let val = wo.read_u32() as u16;
773778
let state = self.state.lock().unwrap();
774-
let admin_cq = state.get_admin_cq();
779+
780+
if !state.ctrl.cc.enabled() {
781+
slog::warn!(
782+
self.log,
783+
"Doorbell write while controller is disabled"
784+
);
785+
return Err(NvmeError::InvalidCompQueue(
786+
queue::ADMIN_QUEUE_ID,
787+
));
788+
}
789+
790+
let admin_cq = state.get_admin_cq()?;
775791
admin_cq.notify_head(val)?;
776792

777793
// We may have skipped pulling entries off the admin sq
778794
// due to no available completion entry permit, so just
779795
// kick it here again in case.
780796
if admin_cq.kick() {
781-
let admin_sq = state.get_admin_sq();
797+
let admin_sq = state.get_admin_sq()?;
782798
self.process_admin_queue(state, admin_sq)?;
783799
}
784800
}
@@ -793,14 +809,30 @@ impl PciNvme {
793809
//
794810
// But note that we only support CAP.DSTRD = 0
795811
let off = wo.offset() - 0x1000;
812+
let is_cq = (off >> 2) & 0b1 == 0b1;
813+
let qid = if is_cq { (off - 4) >> 3 } else { off >> 3 };
814+
815+
// Queue IDs should be 16-bit and we know `off <= CONTROLLER_REG_SZ (0x4000)`
816+
let qid = qid.try_into().unwrap();
796817

797-
let val: u16 = wo.read_u32().try_into().unwrap();
798818
let state = self.state.lock().unwrap();
819+
if !state.ctrl.cc.enabled() {
820+
slog::warn!(
821+
self.log,
822+
"Doorbell write while controller is disabled"
823+
);
824+
return Err(if is_cq {
825+
NvmeError::InvalidCompQueue(qid)
826+
} else {
827+
NvmeError::InvalidSubQueue(qid)
828+
});
829+
}
799830

800-
if (off >> 2) & 0b1 == 0b1 {
831+
// 32-bit register but ignore reserved top 16-bits
832+
let val = wo.read_u32() as u16;
833+
if is_cq {
801834
// Completion Queue y Head Doorbell
802-
let y = (off - 4) >> 3;
803-
let cq = state.get_cq(y as u16)?;
835+
let cq = state.get_cq(qid)?;
804836
cq.notify_head(val)?;
805837

806838
// We may have skipped pulling entries off some SQ due to this
@@ -813,8 +845,7 @@ impl PciNvme {
813845
}
814846
} else {
815847
// Submission Queue y Tail Doorbell
816-
let y = off >> 3;
817-
let sq = state.get_sq(y as u16)?;
848+
let sq = state.get_sq(qid)?;
818849
sq.notify_tail(val)?;
819850

820851
// Poke block device to service new requests
@@ -839,7 +870,7 @@ impl PciNvme {
839870
}
840871

841872
// Grab the Admin CQ too
842-
let cq = state.get_admin_cq();
873+
let cq = state.get_admin_cq()?;
843874

844875
let mem = self.mem_access();
845876
if mem.is_none() {

0 commit comments

Comments
 (0)