#include <sys/types.h>
#include <sys/bus.h>
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/mutex.h>
#include <sys/taskqueue.h>
#include <dev/nvmf/host/nvmf_var.h>
struct nvmf_aer {
struct nvmf_softc *sc;
uint8_t log_page_id;
uint8_t info;
uint8_t type;
u_int page_len;
void *page;
int error;
uint16_t status;
int pending;
struct mtx *lock;
struct task complete_task;
struct task finish_page_task;
};
#define MAX_LOG_PAGE_SIZE 4096
static void nvmf_complete_aer(void *arg, const struct nvme_completion *cqe);
static void
nvmf_submit_aer(struct nvmf_softc *sc, struct nvmf_aer *aer)
{
struct nvmf_request *req;
struct nvme_command cmd;
memset(&cmd, 0, sizeof(cmd));
cmd.opc = NVME_OPC_ASYNC_EVENT_REQUEST;
req = nvmf_allocate_request(sc->admin, &cmd, nvmf_complete_aer, aer,
M_WAITOK);
if (req == NULL)
return;
req->aer = true;
nvmf_submit_request(req);
}
static void
nvmf_handle_changed_namespaces(struct nvmf_softc *sc,
struct nvme_ns_list *ns_list)
{
uint32_t nsid;
if (ns_list->ns[0] == 0xffffffff) {
nvmf_rescan_all_ns(sc);
return;
}
for (u_int i = 0; i < nitems(ns_list->ns); i++) {
if (ns_list->ns[i] == 0)
break;
nsid = le32toh(ns_list->ns[i]);
nvmf_rescan_ns(sc, nsid);
}
}
static void
nvmf_finish_aer_page(struct nvmf_softc *sc, struct nvmf_aer *aer)
{
if (aer->error != 0 || aer->status != 0)
return;
taskqueue_enqueue(taskqueue_thread, &aer->finish_page_task);
}
static void
nvmf_finish_aer_page_task(void *arg, int pending)
{
struct nvmf_aer *aer = arg;
struct nvmf_softc *sc = aer->sc;
switch (aer->log_page_id) {
case NVME_LOG_ERROR:
break;
case NVME_LOG_CHANGED_NAMESPACE:
nvmf_handle_changed_namespaces(sc, aer->page);
break;
}
nvmf_submit_aer(sc, aer);
}
static void
nvmf_io_complete_aer_page(void *arg, size_t xfered, int error)
{
struct nvmf_aer *aer = arg;
struct nvmf_softc *sc = aer->sc;
mtx_lock(aer->lock);
aer->error = error;
aer->pending--;
if (aer->pending == 0) {
mtx_unlock(aer->lock);
nvmf_finish_aer_page(sc, aer);
} else
mtx_unlock(aer->lock);
}
static void
nvmf_complete_aer_page(void *arg, const struct nvme_completion *cqe)
{
struct nvmf_aer *aer = arg;
struct nvmf_softc *sc = aer->sc;
mtx_lock(aer->lock);
aer->status = cqe->status;
aer->pending--;
if (aer->pending == 0) {
mtx_unlock(aer->lock);
nvmf_finish_aer_page(sc, aer);
} else
mtx_unlock(aer->lock);
}
static u_int
nvmf_log_page_size(struct nvmf_softc *sc, uint8_t log_page_id)
{
switch (log_page_id) {
case NVME_LOG_ERROR:
return ((sc->cdata->elpe + 1) *
sizeof(struct nvme_error_information_entry));
case NVME_LOG_CHANGED_NAMESPACE:
return (sizeof(struct nvme_ns_list));
default:
return (0);
}
}
static void
nvmf_complete_aer(void *arg, const struct nvme_completion *cqe)
{
struct nvmf_aer *aer = arg;
struct nvmf_softc *sc = aer->sc;
uint32_t cdw0;
if (cqe->status != 0) {
if (!nvmf_cqe_aborted(cqe))
device_printf(sc->dev, "Ignoring error %#x for AER\n",
le16toh(cqe->status));
return;
}
cdw0 = le32toh(cqe->cdw0);
aer->log_page_id = NVMEV(NVME_ASYNC_EVENT_LOG_PAGE_ID, cdw0);
aer->info = NVMEV(NVME_ASYNC_EVENT_INFO, cdw0);
aer->type = NVMEV(NVME_ASYNC_EVENT_TYPE, cdw0);
device_printf(sc->dev, "AER type %u, info %#x, page %#x\n",
aer->type, aer->info, aer->log_page_id);
aer->page_len = nvmf_log_page_size(sc, aer->log_page_id);
taskqueue_enqueue(taskqueue_thread, &aer->complete_task);
}
static void
nvmf_complete_aer_task(void *arg, int pending)
{
struct nvmf_aer *aer = arg;
struct nvmf_softc *sc = aer->sc;
if (aer->page_len != 0) {
aer->page_len = MIN(aer->page_len, MAX_LOG_PAGE_SIZE);
aer->pending = 2;
(void) nvmf_cmd_get_log_page(sc, NVME_GLOBAL_NAMESPACE_TAG,
aer->log_page_id, 0, aer->page, aer->page_len,
nvmf_complete_aer_page, aer, nvmf_io_complete_aer_page,
aer, M_WAITOK);
} else {
nvmf_submit_aer(sc, aer);
}
}
static int
nvmf_set_async_event_config(struct nvmf_softc *sc, uint32_t config)
{
struct nvme_command cmd;
struct nvmf_completion_status status;
struct nvmf_request *req;
memset(&cmd, 0, sizeof(cmd));
cmd.opc = NVME_OPC_SET_FEATURES;
cmd.cdw10 = htole32(NVME_FEAT_ASYNC_EVENT_CONFIGURATION);
cmd.cdw11 = htole32(config);
nvmf_status_init(&status);
req = nvmf_allocate_request(sc->admin, &cmd, nvmf_complete, &status,
M_WAITOK);
if (req == NULL) {
device_printf(sc->dev,
"failed to allocate SET_FEATURES (ASYNC_EVENT_CONFIGURATION) command\n");
return (ECONNABORTED);
}
nvmf_submit_request(req);
nvmf_wait_for_reply(&status);
if (status.cqe.status != 0) {
device_printf(sc->dev,
"SET_FEATURES (ASYNC_EVENT_CONFIGURATION) failed, status %#x\n",
le16toh(status.cqe.status));
return (EIO);
}
return (0);
}
void
nvmf_init_aer(struct nvmf_softc *sc)
{
sc->num_aer = min(8, sc->cdata->aerl + 1);
sc->aer = mallocarray(sc->num_aer, sizeof(*sc->aer), M_NVMF,
M_WAITOK | M_ZERO);
for (u_int i = 0; i < sc->num_aer; i++) {
sc->aer[i].sc = sc;
sc->aer[i].page = malloc(MAX_LOG_PAGE_SIZE, M_NVMF, M_WAITOK);
sc->aer[i].lock = mtx_pool_find(mtxpool_sleep, &sc->aer[i]);
TASK_INIT(&sc->aer[i].complete_task, 0, nvmf_complete_aer_task,
&sc->aer[i]);
TASK_INIT(&sc->aer[i].finish_page_task, 0,
nvmf_finish_aer_page_task, &sc->aer[i]);
}
}
int
nvmf_start_aer(struct nvmf_softc *sc)
{
uint32_t async_event_config;
int error;
async_event_config = NVME_CRIT_WARN_ST_AVAILABLE_SPARE |
NVME_CRIT_WARN_ST_DEVICE_RELIABILITY |
NVME_CRIT_WARN_ST_READ_ONLY |
NVME_CRIT_WARN_ST_VOLATILE_MEMORY_BACKUP;
if (sc->cdata->ver >= NVME_REV(1, 2))
async_event_config |=
sc->cdata->oaes & NVME_ASYNC_EVENT_NS_ATTRIBUTE;
error = nvmf_set_async_event_config(sc, async_event_config);
if (error != 0)
return (error);
for (u_int i = 0; i < sc->num_aer; i++)
nvmf_submit_aer(sc, &sc->aer[i]);
return (0);
}
void
nvmf_destroy_aer(struct nvmf_softc *sc)
{
for (u_int i = 0; i < sc->num_aer; i++) {
taskqueue_drain(taskqueue_thread, &sc->aer[i].complete_task);
taskqueue_drain(taskqueue_thread, &sc->aer[i].finish_page_task);
free(sc->aer[i].page, M_NVMF);
}
free(sc->aer, M_NVMF);
}