1 /* 2 * This file and its contents are supplied under the terms of the 3 * Common Development and Distribution License ("CDDL"), version 1.0. 4 * You may only use this file in accordance with the terms of version 5 * 1.0 of the CDDL. 6 * 7 * A full copy of the text of the CDDL should have accompanied this 8 * source. A copy of the CDDL is also available via the Internet at 9 * http://www.illumos.org/license/CDDL. 10 */ 11 12 /* 13 * Copyright 2025 Oxide Computer Company 14 */ 15 16 /* 17 * This file contains shared pieces of the NVMe field validation logic and has 18 * shared pieces that are used between different parts. 19 */ 20 21 #include "nvme_common.h" 22 23 #include <sys/sysmacros.h> 24 #ifdef _KERNEL 25 #include <sys/sunddi.h> 26 #include <sys/stdint.h> 27 #else 28 #include <stdio.h> 29 #include <inttypes.h> 30 #endif 31 32 bool 33 nvme_field_atleast(const nvme_valid_ctrl_data_t *data, 34 const nvme_version_t *targ) 35 { 36 return (nvme_vers_atleast(data->vcd_vers, targ)); 37 } 38 39 /* 40 * Note, we rely on external logic to determine if the broadcast nsid is valid. 41 * We always accept it. 42 */ 43 bool 44 nvme_field_valid_nsid(const nvme_field_info_t *field, 45 const nvme_valid_ctrl_data_t *data, uint64_t nsid, char *msg, size_t msglen) 46 { 47 if ((nsid != 0 && nsid <= data->vcd_id->id_nn) || 48 nsid == NVME_NSID_BCAST) { 49 return (true); 50 } 51 52 (void) snprintf(msg, msglen, "namespace id %" PRIu64 "is outside the " 53 "valid range [0x%x, 0x%x], the broadcast nsid (0x%x) may be valid", 54 nsid, NVME_NSID_MIN, NVME_NSID_BCAST, data->vcd_id->id_nn); 55 return (false); 56 } 57 58 bool 59 nvme_field_range_check(const nvme_field_info_t *field, uint64_t min, 60 uint64_t max, char *msg, size_t msglen, uint64_t value) 61 { 62 if (value >= min && value <= max) { 63 return (true); 64 } 65 66 (void) snprintf(msg, msglen, "field %s (%s) value 0x%" 67 PRIx64 " is outside the valid range: [0x%" PRIx64 ", 0x%" PRIx64 68 "]", field->nlfi_human, field->nlfi_spec, value, min, max); 69 return (false); 70 } 71 72 bool 73 nvme_field_mask_check(const nvme_field_info_t *field, uint64_t valid_mask, 74 char *msg, size_t msglen, uint64_t value) 75 { 76 uint64_t inval = ~valid_mask & value; 77 if (inval == 0) { 78 return (true); 79 } 80 81 (void) snprintf(msg, msglen, "field %s (%s) value 0x%" PRIx64 82 " uses bits outside of the mask 0x%" PRIx64 ": 0x%" PRIx64, 83 field->nlfi_human, field->nlfi_spec, value, valid_mask, inval); 84 return (false); 85 } 86 87 /* 88 * This is a general validation function for fields that are part of a command. 89 * It will check if the field is supported by the controller and if so, that its 90 * value is within the expected range. On error, an optional message will be 91 * written that explains the error. This is intended to be shared between 92 * userland and the kernel. The kernel should pass NULL/0 for msg/msglen because 93 * there is no message translation capability in the kernel. 94 */ 95 nvme_field_error_t 96 nvme_field_validate(const nvme_field_info_t *field, 97 const nvme_valid_ctrl_data_t *data, uint64_t value, char *msg, 98 size_t msglen) 99 { 100 ASSERT3P(field->nlfi_vers, !=, NULL); 101 102 if (msglen > 0) 103 *msg = '\0'; 104 105 if (!nvme_field_atleast(data, field->nlfi_vers)) { 106 (void) snprintf(msg, msglen, "field %s (%s) requires " 107 "version %u.%u, but device is at %u.%u", field->nlfi_human, 108 field->nlfi_spec, data->vcd_vers->v_major, 109 data->vcd_vers->v_minor, field->nlfi_vers->v_major, 110 field->nlfi_vers->v_minor); 111 return (NVME_FIELD_ERR_UNSUP_VERSION); 112 } 113 114 if (field->nlfi_sup != NULL && !field->nlfi_sup(field, data, msg, 115 msglen)) { 116 (void) snprintf(msg, msglen, "field %s (%s) is not " 117 "supported by the controller", field->nlfi_human, 118 field->nlfi_spec); 119 return (NVME_FIELD_ERR_UNSUP_FIELD); 120 } 121 122 if (field->nlfi_valid != NULL) { 123 if (!field->nlfi_valid(field, data, value, msg, msglen)) { 124 if (msglen > 0 && *msg == '\0') { 125 (void) snprintf(msg, msglen, 126 "field %s (%s) value 0x%" PRIx64 127 " is invalid", 128 field->nlfi_human, 129 field->nlfi_spec, value); 130 } 131 return (NVME_FIELD_ERR_BAD_VALUE); 132 } 133 } else if (!nvme_field_range_check(field, 0, field->nlfi_max_size, msg, 134 msglen, value)) { 135 return (NVME_FIELD_ERR_BAD_VALUE); 136 } 137 138 return (NVME_FIELD_ERR_OK); 139 } 140