use core::hint::spin_loop;
use log::debug;
use tock_registers::{
interfaces::{Readable, Writeable},
register_bitfields, register_structs,
registers::{ReadOnly, ReadWrite, WriteOnly},
};
register_structs! {
pub(crate) NvmeReg {
(0x000 => controller_capabilities: ReadOnly<u64, CAP::Register>),
(0x008 => version: ReadOnly<u32, VS::Register>),
(0x00c => interrupt_mask_set: WriteOnly<u32>),
(0x010 => interrupt_mask_clear: WriteOnly<u32>),
(0x014 => controller_configuration: ReadWrite<u32, CC::Register>),
(0x018 => reserved_1),
(0x01c => controller_status: ReadWrite<u32, CSTS::Register>),
(0x020 => nvm_subsystem_reset: WriteOnly<u32>),
(0x024 => admin_queue_attributes: ReadWrite<u32, AQA::Register>),
(0x028 => admin_submission_queue_base_address: ReadWrite<u64>),
(0x030 => admin_completion_queue_base_address: ReadWrite<u64>),
(0x038 => controller_memory_buffer_location: ReadWrite<u32>),
(0x03c => controller_memory_buffer_size: ReadWrite<u32>),
(0x040 => boot_partition_information: ReadWrite<u32>),
(0x044 => boot_partition_read_select: ReadWrite<u32>),
(0x048 => boot_partition_memory_buffer_location: ReadWrite<u64>),
(0x050 => controller_memory_buffer_memory_space_control: ReadWrite<u64>),
(0x058 => controller_memory_buffer_status: ReadOnly<u32>),
(0x05c => reserved_2),
(0xe00 => persistent_memory_capabilities: ReadOnly<u32>),
(0xe04 => persistent_memory_region_control: WriteOnly<u32>),
(0xe08 => persistent_memory_region_status: ReadOnly<u32>),
(0xe0c => persistent_memory_region_elasticity_buffer_size: ReadWrite<u32>),
(0xe10 => persistent_memory_region_sustained_write_throughput: ReadOnly<u32>),
(0xe14 => persistent_memory_region_controller_memory_space_control_lower: ReadWrite<u32>),
(0xe18 => persistent_memory_region_controller_memory_space_control_upper: ReadWrite<u32>),
(0xe1c => reserved_3),
(0x1000 => submission_queue_0_tail_doorbell: WriteOnly<u32>),
(0x1004 => @END),
}
}
register_bitfields! [
u32,
VS [
Major OFFSET(16) NUMBITS(8) [],
Minor OFFSET(8) NUMBITS(8) [],
Tertiary OFFSET(0) NUMBITS(8) []
],
CC [
Enable OFFSET(0) NUMBITS(1) [],
IOCommandSetSelected OFFSET(4) NUMBITS(3) [
NVMCommandSet = 0,
AdminCommandSetOnly = 0b111,
],
MemoryPageSize OFFSET(7) NUMBITS(4) [],
ArbitrationMechanismSelected OFFSET(11) NUMBITS(3) [
RoundRobin = 0,
WeightedRoundRobinWithUrgentPriorityClass=0b001,
VendorSpecific = 0b111
],
ShutdownNotification OFFSET(14) NUMBITS(2) [
None = 0,
Normal = 1,
Abrupt = 0b10,
Reserved = 0b11
],
IOSubmissionQueueEntrySize OFFSET(16) NUMBITS(4) [],
IOCompletionQueueEntrySize OFFSET(20) NUMBITS(4) []
],
AQA [
AdminSubmissionQueueSize OFFSET(0) NUMBITS(12) [],
AdminCompletionQueueSize OFFSET(16) NUMBITS(12) []
],
CSTS [
PP OFFSET(5) NUMBITS(1) [],
NSSRO OFFSET(4) NUMBITS(1) [],
SHST OFFSET(2) NUMBITS(2) [],
CFS OFFSET(1) NUMBITS(1) [],
RDY OFFSET(0) NUMBITS(1) []
]
];
register_bitfields! [
u64,
CAP [
MQES OFFSET(0) NUMBITS(16) [],
CQR OFFSET(16) NUMBITS(1) [],
AMS OFFSET(17) NUMBITS(2) [],
TO OFFSET(24) NUMBITS(8) [],
DSTRD OFFSET(32) NUMBITS(4) [],
NSSRS OFFSET(36) NUMBITS(1) [],
CSS OFFSET(37) NUMBITS(8) [],
CMBS OFFSET(57) NUMBITS(1) [],
],
];
impl NvmeReg {
const QUEUE_BASE_MASK: u64 = !0xfff;
pub fn version(&self) -> (usize, usize, usize) {
let major = self.version.read(VS::Major);
let minor = self.version.read(VS::Minor);
let tertiary = self.version.read(VS::Tertiary);
(major as _, minor as _, tertiary as _)
}
pub fn set_admin_submission_queue_base_address(&self, addr: u64) {
let addr = addr & Self::QUEUE_BASE_MASK;
self.admin_submission_queue_base_address.set(addr);
}
pub fn set_admin_completion_queue_base_address(&self, addr: u64) {
let addr = addr & Self::QUEUE_BASE_MASK;
self.admin_completion_queue_base_address.set(addr);
}
pub fn set_admin_submission_and_completion_queue_size(
&self,
submission_size: usize,
completion_size: usize,
) {
self.admin_queue_attributes.write(
AQA::AdminSubmissionQueueSize.val(submission_size as u32 - 1)
+ AQA::AdminCompletionQueueSize.val(completion_size as u32 - 1),
);
}
pub fn reset(&self) {
self.controller_configuration.write(CC::Enable::CLEAR);
debug!("Waiting for reset...");
spin_for_true(|| !self.controller_status.is_set(CSTS::RDY));
debug!("Reset complete!")
}
pub fn setup_cc(&self, sqes: u32, cqes: u32) {
self.controller_configuration.write(
CC::Enable::SET
+ CC::IOCommandSetSelected::NVMCommandSet
+ CC::ArbitrationMechanismSelected::RoundRobin
+ CC::ShutdownNotification::None
+ CC::IOSubmissionQueueEntrySize.val(sqes)
+ CC::IOCompletionQueueEntrySize.val(cqes),
);
debug!("Waiting for ready...");
spin_for_true(|| self.controller_status.is_set(CSTS::RDY));
debug!("Ready!");
}
pub fn ready_for_read_controller_info(&self) {
self.controller_configuration.write(
CC::Enable::SET
+ CC::IOCommandSetSelected::NVMCommandSet
+ CC::ArbitrationMechanismSelected::RoundRobin
+ CC::ShutdownNotification::None,
);
debug!("Waiting for ready...");
spin_for_true(|| self.controller_status.is_set(CSTS::RDY));
debug!("Ready!");
}
pub fn write_sq_y_tail_doolbell(&self, y: usize, tail: u32) {
let dstrd = self.controller_capabilities.read(CAP::DSTRD) as usize;
unsafe {
let ptr = (self as *const NvmeReg as *const u8).add(0x1000 + (2 * y * (4 << dstrd)))
as usize as *mut u32;
ptr.write_volatile(tail);
}
}
pub fn write_cq_y_head_doolbell(&self, y: usize, head: u32) {
let dstrd = self.controller_capabilities.read(CAP::DSTRD) as usize;
unsafe {
let ptr = (self as *const NvmeReg as *const u8)
.add(0x1000 + ((2 * y + 1) * (4 << dstrd))) as usize
as *mut u32;
ptr.write_volatile(head);
}
}
}
fn spin_for_true<F>(f: F)
where
F: Fn() -> bool,
{
while !f() {
spin_loop();
}
}