#include <err.h>
#include <sys/sysmacros.h>
#include <sys/nvme/ocp.h>
#include "libnvme_test_common.h"
static void
set_feats_supported(nvme_ctrl_t *ctrl)
{
nvme_set_feat_req_t *req;
const char *feats[] = { "ocp/plphealth", "ocp/errinj" };
for (size_t i = 0; i < ARRAY_SIZE(feats); i++) {
if (!nvme_set_feat_req_init_by_name(ctrl, feats[i], 0, NULL,
&req)) {
libnvme_test_ctrl_fatal(ctrl, "missing required "
"feature %s: test cannot proceed", feats[i]);
}
nvme_set_feat_req_fini(req);
}
}
static bool
set_feat_fail(nvme_ctrl_t *ctrl, const char *feat, const char *desc,
uint32_t cdw11, nvme_err_t exp)
{
nvme_err_t err;
bool ret = false;
nvme_set_feat_req_t *set = NULL;
if (!nvme_set_feat_req_init_by_name(ctrl, feat, 0, NULL, &set)) {
libnvme_test_ctrl_warn(ctrl, "set feature %s: failed to "
"create %s request", desc, feat);
goto done;
}
if (!nvme_set_feat_req_set_cdw11(set, cdw11)) {
libnvme_test_ctrl_warn(ctrl, "set feature %s: failed to set "
"%s cdw11 value", desc, feat);
goto done;
}
if (nvme_set_feat_req_exec(set)) {
warnx("TEST FAILED: set feature %s unexpectedly passed: "
"expected failure", desc);
goto done;
}
err = nvme_ctrl_err(ctrl);
if (err != exp) {
warnx("TEST FAILED: expected set feature %s to fail with %s "
"(0x%x), but found %s (0x%x)", desc,
nvme_ctrl_errtostr(ctrl, exp), exp,
nvme_ctrl_errtostr(ctrl, err), err);
goto done;
}
ret = true;
(void) printf("TEST PASSED: set feature %s correctly failed with "
"%s\n", desc, nvme_ctrl_errtostr(ctrl, exp));
done:
nvme_set_feat_req_fini(set);
return (ret);
}
static bool
get_plp(nvme_ctrl_t *ctrl, nvme_get_feat_req_t *get, uint32_t *valp)
{
if (!nvme_get_feat_req_exec(get)) {
libnvme_test_ctrl_warn(ctrl, "failed to execute PLP get "
"feature request");
return (false);
}
if (!nvme_get_feat_req_get_cdw0(get, valp)) {
libnvme_test_ctrl_warn(ctrl, "failed to get PLP cdw0 value");
return (false);
}
*valp &= UINT16_MAX;
return (true);
}
static bool
set_plp(nvme_ctrl_t *ctrl, nvme_set_feat_req_t *set, uint16_t val)
{
if (!nvme_set_feat_req_set_cdw11(set, val << 16)) {
libnvme_test_ctrl_warn(ctrl, "failed to set PLP cdw11 value");
return (false);
}
if (!nvme_set_feat_req_exec(set)) {
libnvme_test_ctrl_warn(ctrl, "failed to execute PLP set "
"feature request");
return (false);
}
return (true);
}
static bool
roundtrip_plp(nvme_ctrl_t *ctrl)
{
nvme_get_feat_req_t *get = NULL;
nvme_set_feat_req_t *set = NULL;
bool ret = true;
uint32_t cdw0, new;
if (!nvme_get_feat_req_init_by_name(ctrl, "ocp/plphealth", 0, NULL,
&get)) {
libnvme_test_ctrl_warn(ctrl, "failed to create PLP get feature "
"request");
ret = false;
goto done;
}
if (!nvme_set_feat_req_init_by_name(ctrl, "ocp/plphealth", 0, NULL,
&set)) {
libnvme_test_ctrl_warn(ctrl, "failed to create PLP set feature "
"request");
ret = false;
goto done;
}
if (!get_plp(ctrl, get, &cdw0)) {
ret = false;
goto done;
}
if (!set_plp(ctrl, set, cdw0 + 1)) {
ret = false;
goto done;
}
if (!get_plp(ctrl, get, &new)) {
ret = false;
goto done;
}
if (cdw0 + 1 != new) {
warnx("setting ocp/plphealth didn't work, originally had 0x%x, "
"expected 0x%x, found 0x%x", cdw0, cdw0 + 1, new);
ret = false;
}
if (!set_plp(ctrl, set, cdw0)) {
ret = false;
goto done;
}
if (!get_plp(ctrl, get, &new)) {
ret = false;
goto done;
}
if (cdw0 != new) {
warnx("TEST FAILED: failed to restore ocp/plphealth back to "
"0x%x: found 0x%x", cdw0, new);
ret = false;
}
if (ret) {
(void) printf("TEST PASSED: ocp/plphealth set feature round "
"trip\n");
}
done:
nvme_get_feat_req_fini(get);
nvme_set_feat_req_fini(set);
return (ret);
}
static bool
errinj_req_fail(nvme_ctrl_t *ctrl, nvme_ocp_errinj_req_t *req, const char *desc,
nvme_err_t exp)
{
const char *exps = nvme_ctrl_errtostr(ctrl, exp);
nvme_err_t err;
if (nvme_ocp_errinj_req_exec(req)) {
warnx("TEST FAILED: %s unexpectedly succeeded, but expected %s",
desc, exps);
return (false);
}
err = nvme_ctrl_err(ctrl);
if (err != exp) {
warnx("TEST FAILED: expected %s to fail with %s (0x%x), but "
"found %s (0x%x)", desc, nvme_ctrl_errtostr(ctrl, exp), exp,
nvme_ctrl_errtostr(ctrl, err), err);
return (false);
}
(void) printf("TEST PASSED: %s correctly failed with %s\n", desc, exps);
return (true);
}
static bool
errinj_set_fail(nvme_ctrl_t *ctrl, const char *desc, nvme_err_t exp, bool res)
{
if (res) {
warnx("TEST FAILED: setting error injection field %s to bad "
"value incorrectly worked: expected %s (0x%x)", desc,
nvme_ctrl_errtostr(ctrl, exp), exp);
return (false);
}
nvme_err_t err = nvme_ctrl_err(ctrl);
if (err != exp) {
warnx("TEST FAILED: expected bad error injection field %s "
"value to fail with %s (0x%x): found %s (0x%x)", desc,
nvme_ctrl_errtostr(ctrl, exp), exp,
nvme_ctrl_errtostr(ctrl, err), err);
return (false);
}
(void) printf("TEST PASSED: bad error injection field %s value "
"correctly failed with %s\n", desc, nvme_ctrl_errtostr(ctrl, exp));
return (true);
}
static bool
errinj_errors(nvme_ctrl_t *ctrl)
{
bool ret = true;
nvme_ocp_errinj_req_t *req = NULL;
if (!nvme_ocp_errinj_req_init(ctrl, &req)) {
libnvme_test_ctrl_warn(ctrl, "failed to create error injection "
"request");
ret = false;
goto done;
}
if (!errinj_req_fail(ctrl, req, "error injection with missing fields",
NVME_ERR_OCP_ERRINJ_REQ_MISSING_FIELDS)) {
ret = false;
}
if (!errinj_set_fail(ctrl, "type", NVME_ERR_OCP_ERRINJ_TYPE_RANGE,
nvme_ocp_errinj_req_set_type(req, 0xb22b7777)))
ret = false;
if (!errinj_set_fail(ctrl, "nrtde", NVME_ERR_OCP_ERRINJ_NRTDE_RANGE,
nvme_ocp_errinj_req_set_nrtde(req, 0x12345)))
ret = false;
if (!nvme_ocp_errinj_req_set_type(req, OCP_ERRINJ_T_CPU_HANG) ||
!nvme_ocp_errinj_req_set_ld(req, 0x23)) {
libnvme_test_ctrl_warn(ctrl, "failed to set fields for error "
"injection duration not settable");
ret = false;
} else if (!errinj_req_fail(ctrl, req, "error injection duration not "
"settable", NVME_ERR_OCP_ERRINJ_LD_UNUSE)) {
ret = false;
}
done:
nvme_ocp_errinj_req_fini(req);
return (ret);
}
static bool
roundtrip_errinj(nvme_ctrl_t *ctrl)
{
bool ret = true;
nvme_ocp_errinj_req_t *req = NULL;
nvme_get_feat_req_t *get = NULL;
nvme_feat_disc_t *disc = NULL;
void *buf = NULL;
uint64_t len;
uint32_t cdw0;
if (!nvme_get_feat_req_init_by_name(ctrl, "ocp/errinj", 0, &disc,
&get)) {
libnvme_test_ctrl_warn(ctrl, "failed to create error injection "
"get feature request");
ret = false;
goto done;
}
len = nvme_feat_disc_data_size(disc);
buf = calloc(len, sizeof (uint8_t));
if (buf == NULL) {
err(EXIT_FAILURE, "INTERNAL TEST FAILED: failed to alloc %"
PRIu64 "bytes for error injection buffer", len);
}
if (!nvme_get_feat_req_set_output(get, buf, len)) {
libnvme_test_ctrl_warn(ctrl, "failed to set get feature output "
"buffer");
ret = false;
goto done;
}
if (!nvme_ocp_errinj_req_init(ctrl, &req)) {
libnvme_test_ctrl_warn(ctrl, "failed to create error injection "
"request");
ret = false;
goto done;
}
if (!nvme_ocp_errinj_req_set_type(req, OCP_ERRINJ_T_CPU_HANG) ||
!nvme_ocp_errinj_req_set_nrtde(req, 0x7777)) {
libnvme_test_ctrl_warn(ctrl, "failed to set basic error "
"injection fields");
ret = false;
goto done;
}
if (!nvme_ocp_errinj_req_exec(req)) {
libnvme_test_ctrl_warn(ctrl, "failed to execute error "
"injection request");
ret = false;
goto done;
}
if (!nvme_get_feat_req_exec(get) || !nvme_get_feat_req_get_cdw0(get,
&cdw0)) {
libnvme_test_ctrl_warn(ctrl, "failed to get error injection "
"request and data");
ret = false;
goto done;
}
if (cdw0 != 1) {
warnx("TEST FAILED: error injection pending count is 0x%x, but "
"expected it to be 1", cdw0);
ret = false;
}
const volatile ocp_vuf_errinj_t *err = buf;
if (err[0].oei_flags != (OCP_ERRINJ_F_ENABLE | OCP_ERRINJ_F_SINGLE)) {
warnx("TEST FAILED: found unexpected error flags 0x%x, "
"expected 0x%x", err[0].oei_flags, OCP_ERRINJ_F_ENABLE |
OCP_ERRINJ_F_SINGLE);
ret = false;
}
if (err[0].oei_type != OCP_ERRINJ_T_CPU_HANG) {
warnx("TEST FAILED: found unexpected error type 0x%x, expected "
"0x%x", err[0].oei_type, OCP_ERRINJ_T_CPU_HANG);
ret = false;
}
if (err[0].oei_nrtde < 0x6666) {
warnx("TEST FAILED: found unexpected nrtde value: 0x%x",
err[0].oei_nrtde);
ret = false;
}
if (!nvme_ocp_errinj_clear(ctrl)) {
libnvme_test_ctrl_warn(ctrl, "failed to clear error injection "
"request");
ret = false;
goto done;
}
if (!nvme_get_feat_req_exec(get) || !nvme_get_feat_req_get_cdw0(get,
&cdw0)) {
libnvme_test_ctrl_warn(ctrl, "failed to get error injection "
"request and data");
ret = false;
goto done;
}
if (cdw0 != 0) {
warnx("TEST FAILED: error injection pending count is 0x%x "
"after clear, but expected 0", cdw0);
ret = false;
}
if (err[0].oei_flags != 0) {
warnx("TEST FAILED: found unexpected error flags 0x%x after "
"clear, but expected 0", err[0].oei_flags);
ret = false;
}
if (err[0].oei_type != 0) {
warnx("TEST FAILED: found unexpected error type 0x%x after "
"clear, but expected 0", err[0].oei_type);
ret = false;
}
if (err[0].oei_nrtde != 0) {
warnx("TEST FAILED: found unexpected nrtde 0x%x after "
"clear, but expected 0", err[0].oei_nrtde);
ret = false;
}
if (ret) {
(void) printf("TEST PASSED: error injection roundtrip\n");
}
done:
nvme_ocp_errinj_req_fini(req);
free(buf);
nvme_feat_disc_free(disc);
nvme_get_feat_req_fini(get);
return (ret);
}
int
main(void)
{
int ret = EXIT_SUCCESS;
nvme_t *nvme;
nvme_ctrl_t *ctrl;
libnvme_test_init(&nvme, &ctrl);
set_feats_supported(ctrl);
if (!set_feat_fail(ctrl, "ocp/plphealth", "without control lock", 0x23,
NVME_ERR_NEED_CTRL_WRLOCK)) {
ret = EXIT_FAILURE;
}
if (!set_feat_fail(ctrl, "queues", "standard feature", 0x7777,
NVME_ERR_NEED_CTRL_WRLOCK)) {
ret = EXIT_FAILURE;
}
if (!nvme_ctrl_lock(ctrl, NVME_LOCK_L_WRITE, 0)) {
libnvme_test_ctrl_fatal(ctrl, "failed to take a write lock: "
"cannot continue");
}
if (!roundtrip_plp(ctrl))
ret = EXIT_FAILURE;
if (!errinj_errors(ctrl))
ret = EXIT_FAILURE;
if (!roundtrip_errinj(ctrl))
ret = EXIT_FAILURE;
nvme_ctrl_unlock(ctrl);
nvme_ctrl_fini(ctrl);
nvme_fini(nvme);
if (ret == EXIT_SUCCESS) {
(void) printf("All tests exited successfully\n");
}
return (ret);
}