|
15 | 15 | #include "cleanup.h" |
16 | 16 | #include "private.h" |
17 | 17 |
|
| 18 | +static bool force_4k; |
| 19 | + |
| 20 | +__attribute__((constructor)) |
| 21 | +static void nvme_init_env(void) |
| 22 | +{ |
| 23 | + char *val; |
| 24 | + |
| 25 | + val = getenv("LIBNVME_FORCE_4K"); |
| 26 | + if (!val) |
| 27 | + return; |
| 28 | + if (!strcmp(val, "1") || |
| 29 | + !strcasecmp(val, "true") || |
| 30 | + !strncasecmp(val, "enable", 6)) |
| 31 | + force_4k = true; |
| 32 | +} |
| 33 | + |
| 34 | +int nvme_get_log(struct nvme_transport_handle *hdl, |
| 35 | + struct nvme_passthru_cmd *cmd, bool rae, |
| 36 | + __u32 xfer_len) |
| 37 | +{ |
| 38 | + __u64 offset = 0, xfer, data_len = cmd->data_len; |
| 39 | + __u64 start = (__u64)cmd->cdw13 << 32 | cmd->cdw12; |
| 40 | + __u64 lpo; |
| 41 | + void *ptr = (void *)(uintptr_t)cmd->addr; |
| 42 | + int ret; |
| 43 | + bool _rae; |
| 44 | + __u32 numd; |
| 45 | + __u16 numdu, numdl; |
| 46 | + __u32 cdw10 = cmd->cdw10 & (NVME_VAL(LOG_CDW10_LID) | |
| 47 | + NVME_VAL(LOG_CDW10_LSP)); |
| 48 | + __u32 cdw11 = cmd->cdw11 & NVME_VAL(LOG_CDW11_LSI); |
| 49 | + |
| 50 | + if (force_4k) |
| 51 | + xfer_len = NVME_LOG_PAGE_PDU_SIZE; |
| 52 | + |
| 53 | + /* |
| 54 | + * 4k is the smallest possible transfer unit, so restricting to 4k |
| 55 | + * avoids having to check the MDTS value of the controller. |
| 56 | + */ |
| 57 | + do { |
| 58 | + if (!force_4k) { |
| 59 | + xfer = data_len - offset; |
| 60 | + if (xfer > xfer_len) |
| 61 | + xfer = xfer_len; |
| 62 | + } else { |
| 63 | + xfer = NVME_LOG_PAGE_PDU_SIZE; |
| 64 | + } |
| 65 | + |
| 66 | + /* |
| 67 | + * Always retain regardless of the RAE parameter until the very |
| 68 | + * last portion of this log page so the data remains latched |
| 69 | + * during the fetch sequence. |
| 70 | + */ |
| 71 | + lpo = start + offset; |
| 72 | + numd = (xfer >> 2) - 1; |
| 73 | + numdu = numd >> 16; |
| 74 | + numdl = numd & 0xffff; |
| 75 | + _rae = offset + xfer < data_len || rae; |
| 76 | + |
| 77 | + cmd->cdw10 = cdw10 | |
| 78 | + NVME_SET(!!_rae, LOG_CDW10_RAE) | |
| 79 | + NVME_SET(numdl, LOG_CDW10_NUMDL); |
| 80 | + cmd->cdw11 = cdw11 | |
| 81 | + NVME_SET(numdu, LOG_CDW11_NUMDU); |
| 82 | + cmd->cdw12 = lpo & 0xffffffff; |
| 83 | + cmd->cdw13 = lpo >> 32; |
| 84 | + cmd->data_len = xfer; |
| 85 | + cmd->addr = (__u64)(uintptr_t)ptr; |
| 86 | + |
| 87 | + if (hdl->uring_enabled) |
| 88 | + ret = nvme_submit_admin_passthru_async(hdl, cmd); |
| 89 | + else |
| 90 | + ret = nvme_submit_admin_passthru(hdl, cmd); |
| 91 | + if (ret) |
| 92 | + return ret; |
| 93 | + |
| 94 | + offset += xfer; |
| 95 | + ptr += xfer; |
| 96 | + } while (offset < data_len); |
| 97 | + |
| 98 | + if (hdl->uring_enabled) { |
| 99 | + ret = nvme_wait_complete_passthru(hdl); |
| 100 | + if (ret) |
| 101 | + return ret; |
| 102 | + } |
| 103 | + |
| 104 | + return 0; |
| 105 | +} |
| 106 | + |
| 107 | +static int read_ana_chunk(struct nvme_transport_handle *hdl, |
| 108 | + enum nvme_log_ana_lsp lsp, bool rae, |
| 109 | + __u8 *log, __u8 **read, __u8 *to_read, __u8 *log_end) |
| 110 | +{ |
| 111 | + struct nvme_passthru_cmd cmd; |
| 112 | + |
| 113 | + if (to_read > log_end) |
| 114 | + return -ENOSPC; |
| 115 | + |
| 116 | + while (*read < to_read) { |
| 117 | + __u32 len = min_t(__u32, log_end - *read, |
| 118 | + NVME_LOG_PAGE_PDU_SIZE); |
| 119 | + int ret; |
| 120 | + |
| 121 | + nvme_init_get_log_ana(&cmd, lsp, *read - log, *read, len); |
| 122 | + ret = nvme_get_log(hdl, &cmd, rae, NVME_LOG_PAGE_PDU_SIZE); |
| 123 | + if (ret) |
| 124 | + return ret; |
| 125 | + |
| 126 | + *read += len; |
| 127 | + } |
| 128 | + return 0; |
| 129 | +} |
| 130 | + |
| 131 | +static int try_read_ana(struct nvme_transport_handle *hdl, |
| 132 | + enum nvme_log_ana_lsp lsp, bool rae, |
| 133 | + struct nvme_ana_log *log, __u8 *log_end, |
| 134 | + __u8 *read, __u8 **to_read, bool *may_retry) |
| 135 | +{ |
| 136 | + __u16 ngrps = le16_to_cpu(log->ngrps); |
| 137 | + |
| 138 | + while (ngrps--) { |
| 139 | + __u8 *group = *to_read; |
| 140 | + int ret; |
| 141 | + __le32 nnsids; |
| 142 | + |
| 143 | + *to_read += sizeof(*log->descs); |
| 144 | + ret = read_ana_chunk(hdl, lsp, rae, |
| 145 | + (__u8 *)log, &read, *to_read, log_end); |
| 146 | + if (ret) { |
| 147 | + /* |
| 148 | + * If the provided buffer isn't long enough, |
| 149 | + * the log page may have changed while reading it |
| 150 | + * and the computed length was inaccurate. |
| 151 | + * Have the caller check chgcnt and retry. |
| 152 | + */ |
| 153 | + *may_retry = ret == -ENOSPC; |
| 154 | + return ret; |
| 155 | + } |
| 156 | + |
| 157 | + /* |
| 158 | + * struct nvme_ana_group_desc has 8-byte alignment |
| 159 | + * but the group pointer is only 4-byte aligned. |
| 160 | + * Don't dereference the misaligned pointer. |
| 161 | + */ |
| 162 | + memcpy(&nnsids, |
| 163 | + group + offsetof(struct nvme_ana_group_desc, nnsids), |
| 164 | + sizeof(nnsids)); |
| 165 | + *to_read += le32_to_cpu(nnsids) * sizeof(__le32); |
| 166 | + ret = read_ana_chunk(hdl, lsp, rae, |
| 167 | + (__u8 *)log, &read, *to_read, log_end); |
| 168 | + if (ret) { |
| 169 | + *may_retry = ret == -ENOSPC; |
| 170 | + return ret; |
| 171 | + } |
| 172 | + } |
| 173 | + |
| 174 | + *may_retry = true; |
| 175 | + return 0; |
| 176 | +} |
| 177 | + |
| 178 | +int nvme_get_ana_log_atomic(struct nvme_transport_handle *hdl, |
| 179 | + bool rae, bool rgo, struct nvme_ana_log *log, __u32 *len, |
| 180 | + unsigned int retries) |
| 181 | +{ |
| 182 | + const enum nvme_log_ana_lsp lsp = |
| 183 | + rgo ? NVME_LOG_ANA_LSP_RGO_GROUPS_ONLY : 0; |
| 184 | + /* Get Log Page can only fetch multiples of dwords */ |
| 185 | + __u8 * const log_end = (__u8 *)log + (*len & -4); |
| 186 | + __u8 *read = (__u8 *)log; |
| 187 | + __u8 *to_read; |
| 188 | + int ret; |
| 189 | + |
| 190 | + if (!retries) |
| 191 | + return -EINVAL; |
| 192 | + |
| 193 | + to_read = (__u8 *)log->descs; |
| 194 | + ret = read_ana_chunk(hdl, lsp, rae, |
| 195 | + (__u8 *)log, &read, to_read, log_end); |
| 196 | + if (ret) |
| 197 | + return ret; |
| 198 | + |
| 199 | + do { |
| 200 | + bool may_retry = false; |
| 201 | + int saved_ret; |
| 202 | + int saved_errno; |
| 203 | + __le64 chgcnt; |
| 204 | + |
| 205 | + saved_ret = try_read_ana(hdl, lsp, rae, log, log_end, |
| 206 | + read, &to_read, &may_retry); |
| 207 | + /* |
| 208 | + * If the log page was read with multiple Get Log Page commands, |
| 209 | + * chgcnt must be checked afterwards to ensure atomicity |
| 210 | + */ |
| 211 | + *len = to_read - (__u8 *)log; |
| 212 | + if (*len <= NVME_LOG_PAGE_PDU_SIZE || !may_retry) |
| 213 | + return saved_ret; |
| 214 | + |
| 215 | + saved_errno = errno; |
| 216 | + chgcnt = log->chgcnt; |
| 217 | + read = (__u8 *)log; |
| 218 | + to_read = (__u8 *)log->descs; |
| 219 | + ret = read_ana_chunk(hdl, lsp, rae, |
| 220 | + (__u8 *)log, &read, to_read, log_end); |
| 221 | + if (ret) |
| 222 | + return ret; |
| 223 | + |
| 224 | + if (log->chgcnt == chgcnt) { |
| 225 | + /* Log hasn't changed; return try_read_ana() result */ |
| 226 | + errno = saved_errno; |
| 227 | + return saved_ret; |
| 228 | + } |
| 229 | + } while (--retries); |
| 230 | + |
| 231 | + return -EAGAIN; |
| 232 | +} |
| 233 | + |
18 | 234 | int nvme_set_etdas(struct nvme_transport_handle *hdl, bool *changed) |
19 | 235 | { |
20 | 236 | struct nvme_feat_host_behavior da4; |
|
0 commit comments