Files
zinnia/dev/block/nvme/nvme.h
T
2026-10-29 01:50:52 +01:00

222 lines
5.6 KiB
C++

// SPDX-License-Identifier: ISC
#pragma once
#include <zinnia/atomic.h>
#include <zinnia/blockdev.h>
#include <zinnia/dma.h>
#include <zinnia/errno.h>
#include <zinnia/event.h>
#include <zinnia/mutex.h>
#include <zinnia/pci.h>
#include <zinnia/spin.h>
#include <zinnia/vec.h>
#include <stddef.h>
#include <stdint.h>
#include "spec.h"
constexpr uint32_t NVME_MAJOR = 159;
constexpr uint64_t NVME_COMPLETION_TIMEOUT_NS = 30'000'000'000;
// An entry in the completion queue.
struct nvme_completion {
uint32_t result;
uint16_t sq_head;
uint16_t sq_id;
uint16_t cmd_id;
bool phase_tag;
uint16_t status;
};
static inline bool nvme_status_is_success(uint16_t status) {
return status == 0;
}
struct nvme_rw_cmd {
uint64_t prp1;
uint64_t prp2;
uint16_t cid;
bool do_write;
uint64_t start_lba;
size_t num_lbas;
size_t bytes;
uint16_t control;
uint32_t ds_mgmt;
uint32_t ref_tag;
uint16_t app_tag;
uint16_t app_mask;
uint32_t nsid;
};
struct nvme_flush_cmd {
uint16_t cid;
uint32_t nsid;
};
struct nvme_create_cq_cmd {
dma_addr_t cq_addr;
size_t depth;
size_t qid;
bool irqs_enabled;
uint16_t irq_vector;
};
struct nvme_create_sq_cmd {
dma_addr_t sq_addr;
size_t depth;
size_t qid;
size_t cqid;
};
struct nvme_identify_cmd {
dma_addr_t buffer;
uint16_t controller_id;
uint8_t cns;
uint32_t nsid;
};
void nvme_rw_cmd_write(const struct nvme_rw_cmd* cmd, struct nvme_sqe* sqe);
void nvme_flush_cmd_write(const struct nvme_flush_cmd* cmd, struct nvme_sqe* sqe);
void nvme_create_cq_cmd_write(const struct nvme_create_cq_cmd* cmd, struct nvme_sqe* sqe);
void nvme_create_sq_cmd_write(const struct nvme_create_sq_cmd* cmd, struct nvme_sqe* sqe);
void nvme_identify_cmd_write(const struct nvme_identify_cmd* cmd, struct nvme_sqe* sqe);
struct nvme_queue {
// Amount of queue entries.
size_t depth;
size_t doorbells_offset;
size_t doorbell_stride;
void* regs;
// Physical buffer for the completion queue.
struct dma_buffer cq;
// The index of the current completion queue entry.
size_t cq_head;
// Determines whether a completion queue entry is new.
uint8_t cq_phase;
// Physical buffer for the submission queue.
struct dma_buffer sq;
// The index of the current submission queue entry.
size_t sq_tail;
};
errno_t nvme_queue_new(
struct dma_tag* tag,
void* regs,
size_t doorbell_stride,
size_t queue_id,
size_t depth,
struct nvme_queue** out
);
void nvme_queue_free(struct nvme_queue* queue);
errno_t nvme_queue_submit_cmd(struct nvme_queue* queue, const struct nvme_sqe* sqe);
errno_t nvme_queue_next_completion(struct nvme_queue* queue, struct nvme_completion* out);
struct nvme_inflight {
bool active;
struct bio* bio;
size_t lbas;
struct dma_map* map;
struct dma_buffer prps;
_Atomic(bool)* done;
};
struct nvme_io_queue {
size_t queue_id;
size_t depth;
size_t doorbells_offset;
size_t doorbell_stride;
void* regs;
bool poll;
struct dma_tag* tag;
struct dma_buffer cq;
struct dma_buffer sq;
struct spinlock sq_lock;
size_t sq_tail;
struct spinlock cq_lock;
size_t cq_head;
uint8_t cq_phase;
struct spinlock cmds_lock;
struct nvme_inflight* slots;
uint16_t* free;
size_t num_free;
struct event slots_free;
};
errno_t nvme_io_queue_new(
struct dma_tag* tag,
void* regs,
size_t doorbell_stride,
size_t queue_id,
size_t depth,
bool poll,
struct nvme_io_queue** out
);
void nvme_io_queue_free(struct nvme_io_queue* queue);
uint16_t nvme_io_queue_submit(
struct nvme_io_queue* queue,
struct bio* bio,
struct nvme_rw_cmd cmd,
_Atomic(bool)* done
);
bool nvme_io_queue_abandon(struct nvme_io_queue* queue, uint16_t cid, struct bio* bio);
size_t nvme_io_queue_drain(struct nvme_io_queue* queue);
errno_t nvme_build_prps(
struct dma_tag* tag,
struct bio* bio,
struct nvme_inflight* slot,
uint64_t* prp1,
uint64_t* prp2
);
void nvme_release_prps(struct nvme_inflight* slot);
struct nvme_ns;
struct nvme_ctrl {
void* regs;
struct dma_tag* dma_tag;
size_t doorbell_stride; // The amount of bytes between two doorbells.
size_t mps_min;
uint8_t mps;
char serial[20 + 1]; // Serial number of the controller.
char model[40 + 1]; // Model name of the controller.
char revision[8 + 1]; // Revision of the controller's firmware.
struct nvme_queue* admin_queue; // Queue to submit admin commands to.
mutex_t admin_lock;
struct nvme_io_queue* io_queue; // Queue to submit IO commands to.
struct spinlock io_lock;
_Atomic(uint32_t) ns_count; // Amount of namespaces.
size_t mdts; // Maximum data transfer size.
};
typedef vec(struct nvme_ns*) nvme_ns_vec_t;
errno_t nvme_ctrl_new_pci(void* regs, struct dma_tag* dma_tag, struct nvme_ctrl** out);
size_t nvme_ctrl_max_transfer_bytes(struct nvme_ctrl* ctrl);
errno_t nvme_ctrl_reset(struct nvme_ctrl* ctrl, bool irqs_enabled, uint16_t io_irq_vector);
errno_t nvme_ctrl_identify(struct nvme_ctrl* ctrl);
errno_t nvme_ctrl_scan_namespaces(struct nvme_ctrl* ctrl, nvme_ns_vec_t* out);
struct nvme_io_queue* nvme_ctrl_io_queue(struct nvme_ctrl* ctrl);
struct nvme_ns {
struct blockdev blk;
struct nvme_ctrl* ctrl;
uint32_t nsid;
uint8_t lba_shift;
uint64_t lba_count;
size_t max_transfer_bytes;
};
errno_t nvme_ns_new(
struct nvme_ctrl* ctrl,
uint32_t nsid,
uint8_t lba_shift,
uint64_t lba_count,
size_t max_transfer_bytes,
struct nvme_ns** out
);