| File: | .build-ci/../libnvme/src/nvme/nvme-cmds.c |
| Warning: | line 610, column 10 Potential leak of memory pointed to by 'cmd.addr' |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
| 1 | // SPDX-License-Identifier: LGPL-2.1-or-later | |||
| 2 | /* | |||
| 3 | * This file is part of libnvme. | |||
| 4 | * Copyright (c) 2020 Western Digital Corporation or its affiliates. | |||
| 5 | * | |||
| 6 | * Authors: Keith Busch <keith.busch@wdc.com> | |||
| 7 | * Chaitanya Kulkarni <chaitanya.kulkarni@wdc.com> | |||
| 8 | */ | |||
| 9 | ||||
| 10 | #include <ccan/endian/endian.h> | |||
| 11 | #include <ccan/minmax/minmax.h> | |||
| 12 | ||||
| 13 | #include <shared/compiler-attributes-util.h> | |||
| 14 | ||||
| 15 | #include <libnvme.h> | |||
| 16 | ||||
| 17 | #include "cleanup.h" | |||
| 18 | #include "private.h" | |||
| 19 | ||||
| 20 | static int submit_get_log_cmd(struct libnvme_transport_handle *hdl, | |||
| 21 | struct libnvme_passthru_cmd *cmd) | |||
| 22 | { | |||
| 23 | int err; | |||
| 24 | ||||
| 25 | if (hdl->type != LIBNVME_TRANSPORT_HANDLE_TYPE_DIRECT) | |||
| 26 | goto no_uring; | |||
| 27 | ||||
| 28 | if (hdl->uring_state == LIBNVME_IO_URING_STATE_NOT_AVAILABLE) | |||
| 29 | goto no_uring; | |||
| 30 | ||||
| 31 | err = libnvme_submit_admin_passthru(hdl, cmd, NULL((void*)0)); | |||
| 32 | if (err && err == -ENOTSUP95) | |||
| 33 | goto no_uring; | |||
| 34 | ||||
| 35 | return err; | |||
| 36 | ||||
| 37 | no_uring: | |||
| 38 | return libnvme_exec_admin_passthru(hdl, cmd); | |||
| 39 | } | |||
| 40 | ||||
| 41 | static int wait_get_log_cmd(struct libnvme_transport_handle *hdl) | |||
| 42 | { | |||
| 43 | if (hdl->type != LIBNVME_TRANSPORT_HANDLE_TYPE_DIRECT) | |||
| 44 | return 0; | |||
| 45 | ||||
| 46 | if (hdl->uring_state == LIBNVME_IO_URING_STATE_NOT_AVAILABLE) | |||
| 47 | return 0; | |||
| 48 | ||||
| 49 | return libnvme_wait_passthru(hdl); | |||
| 50 | } | |||
| 51 | ||||
| 52 | __shr_public__attribute__((visibility("default"))) int libnvme_get_log(struct libnvme_transport_handle *hdl, | |||
| 53 | struct libnvme_passthru_cmd *cmd, bool_Bool rae, | |||
| 54 | __u32 xfer_len) | |||
| 55 | { | |||
| 56 | __u64 offset = 0, xfer, data_len, start, lpo; | |||
| 57 | __u32 numd, cdw10, cdw11; | |||
| 58 | __u16 numdu, numdl; | |||
| 59 | bool_Bool _rae; | |||
| 60 | void *ptr; | |||
| 61 | int ret; | |||
| 62 | ||||
| 63 | if (!hdl) | |||
| 64 | return -ENODEV19; | |||
| 65 | if (!cmd || xfer_len < 4) | |||
| 66 | return -EINVAL22; | |||
| 67 | ||||
| 68 | data_len = cmd->data_len; | |||
| 69 | start = (__u64)cmd->cdw13 << 32 | cmd->cdw12; | |||
| 70 | ptr = (void *)(uintptr_t)cmd->addr; | |||
| 71 | cdw10 = cmd->cdw10 & (NVME_VAL(LOG_CDW10_LID)((__u32)NVME_LOG_CDW10_LID_MASK << NVME_LOG_CDW10_LID_SHIFT ) | | |||
| 72 | NVME_VAL(LOG_CDW10_LSP)((__u32)NVME_LOG_CDW10_LSP_MASK << NVME_LOG_CDW10_LSP_SHIFT )); | |||
| 73 | cdw11 = cmd->cdw11 & NVME_VAL(LOG_CDW11_LSI)((__u32)NVME_LOG_CDW11_LSI_MASK << NVME_LOG_CDW11_LSI_SHIFT ); | |||
| 74 | ||||
| 75 | if (hdl->ctx->force_4k) | |||
| 76 | xfer_len = NVME_LOG_PAGE_PDU_SIZE4096; | |||
| 77 | ||||
| 78 | /* | |||
| 79 | * 4k is the smallest possible transfer unit, so restricting to 4k | |||
| 80 | * avoids having to check the MDTS value of the controller. | |||
| 81 | */ | |||
| 82 | do { | |||
| 83 | if (!hdl->ctx->force_4k) { | |||
| 84 | xfer = data_len - offset; | |||
| 85 | if (xfer > xfer_len) | |||
| 86 | xfer = xfer_len; | |||
| 87 | } else { | |||
| 88 | xfer = NVME_LOG_PAGE_PDU_SIZE4096; | |||
| 89 | } | |||
| 90 | ||||
| 91 | /* | |||
| 92 | * Always retain regardless of the RAE parameter until the very | |||
| 93 | * last portion of this log page so the data remains latched | |||
| 94 | * during the fetch sequence. | |||
| 95 | */ | |||
| 96 | lpo = start + offset; | |||
| 97 | numd = (xfer >> 2) - 1; | |||
| 98 | numdu = numd >> 16; | |||
| 99 | numdl = numd & 0xffff; | |||
| 100 | _rae = offset + xfer < data_len || rae; | |||
| 101 | ||||
| 102 | cmd->cdw10 = cdw10 | | |||
| 103 | NVME_SET(!!_rae, LOG_CDW10_RAE)(((__u32)(!!_rae) & NVME_LOG_CDW10_RAE_MASK) << NVME_LOG_CDW10_RAE_SHIFT ) | | |||
| 104 | NVME_SET(numdl, LOG_CDW10_NUMDL)(((__u32)(numdl) & NVME_LOG_CDW10_NUMDL_MASK) << NVME_LOG_CDW10_NUMDL_SHIFT ); | |||
| 105 | cmd->cdw11 = cdw11 | | |||
| 106 | NVME_SET(numdu, LOG_CDW11_NUMDU)(((__u32)(numdu) & NVME_LOG_CDW11_NUMDU_MASK) << NVME_LOG_CDW11_NUMDU_SHIFT ); | |||
| 107 | cmd->cdw12 = lpo & 0xffffffff; | |||
| 108 | cmd->cdw13 = lpo >> 32; | |||
| 109 | cmd->data_len = xfer; | |||
| 110 | cmd->addr = (__u64)(uintptr_t)ptr; | |||
| 111 | ||||
| 112 | ret = submit_get_log_cmd(hdl, cmd); | |||
| 113 | if (ret) | |||
| 114 | return ret; | |||
| 115 | ||||
| 116 | offset += xfer; | |||
| 117 | ptr += xfer; | |||
| 118 | } while (offset < data_len); | |||
| 119 | ||||
| 120 | return wait_get_log_cmd(hdl); | |||
| 121 | } | |||
| 122 | ||||
| 123 | __shr_public__attribute__((visibility("default"))) int libnvme_get_log_dynamic_chunk( | |||
| 124 | struct libnvme_transport_handle *hdl, | |||
| 125 | struct libnvme_passthru_cmd *cmd, bool_Bool rae, | |||
| 126 | __u32 xfer_len) | |||
| 127 | { | |||
| 128 | __u64 offset = 0, xfer, data_len, start, lpo; | |||
| 129 | __u32 numd, cdw10, cdw11; | |||
| 130 | __u16 numdu, numdl; | |||
| 131 | bool_Bool _rae; | |||
| 132 | void *ptr; | |||
| 133 | int ret; | |||
| 134 | ||||
| 135 | if (!hdl) | |||
| 136 | return -ENODEV19; | |||
| 137 | if (!cmd || xfer_len < 4) | |||
| 138 | return -EINVAL22; | |||
| 139 | ||||
| 140 | data_len = cmd->data_len; | |||
| 141 | start = (__u64)cmd->cdw13 << 32 | cmd->cdw12; | |||
| 142 | ptr = (void *)(uintptr_t)cmd->addr; | |||
| 143 | cdw10 = cmd->cdw10 & (NVME_VAL(LOG_CDW10_LID)((__u32)NVME_LOG_CDW10_LID_MASK << NVME_LOG_CDW10_LID_SHIFT ) | | |||
| 144 | NVME_VAL(LOG_CDW10_LSP)((__u32)NVME_LOG_CDW10_LSP_MASK << NVME_LOG_CDW10_LSP_SHIFT )); | |||
| 145 | cdw11 = cmd->cdw11 & NVME_VAL(LOG_CDW11_LSI)((__u32)NVME_LOG_CDW11_LSI_MASK << NVME_LOG_CDW11_LSI_SHIFT ); | |||
| 146 | ||||
| 147 | if (hdl->ctx->force_4k) | |||
| 148 | xfer_len = NVME_LOG_PAGE_PDU_SIZE4096; | |||
| 149 | ||||
| 150 | do { | |||
| 151 | xfer = data_len - offset; | |||
| 152 | if (xfer > xfer_len) | |||
| 153 | xfer = xfer_len; | |||
| 154 | ||||
| 155 | /* | |||
| 156 | * Always retain regardless of the RAE parameter until the very | |||
| 157 | * last portion of this log page so the data remains latched | |||
| 158 | * during the fetch sequence. | |||
| 159 | */ | |||
| 160 | lpo = start + offset; | |||
| 161 | numd = (xfer >> 2) - 1; | |||
| 162 | numdu = numd >> 16; | |||
| 163 | numdl = numd & 0xffff; | |||
| 164 | _rae = offset + xfer < data_len || rae; | |||
| 165 | ||||
| 166 | cmd->cdw10 = cdw10 | | |||
| 167 | NVME_SET(!!_rae, LOG_CDW10_RAE)(((__u32)(!!_rae) & NVME_LOG_CDW10_RAE_MASK) << NVME_LOG_CDW10_RAE_SHIFT ) | | |||
| 168 | NVME_SET(numdl, LOG_CDW10_NUMDL)(((__u32)(numdl) & NVME_LOG_CDW10_NUMDL_MASK) << NVME_LOG_CDW10_NUMDL_SHIFT ); | |||
| 169 | cmd->cdw11 = cdw11 | | |||
| 170 | NVME_SET(numdu, LOG_CDW11_NUMDU)(((__u32)(numdu) & NVME_LOG_CDW11_NUMDU_MASK) << NVME_LOG_CDW11_NUMDU_SHIFT ); | |||
| 171 | cmd->cdw12 = lpo & 0xffffffff; | |||
| 172 | cmd->cdw13 = lpo >> 32; | |||
| 173 | cmd->data_len = xfer; | |||
| 174 | cmd->addr = (__u64)(uintptr_t)ptr; | |||
| 175 | ||||
| 176 | ret = submit_get_log_cmd(hdl, cmd); | |||
| 177 | if (!ret) | |||
| 178 | ret = wait_get_log_cmd(hdl); | |||
| 179 | /* | |||
| 180 | * Retry with a smaller chunk on OS errors (negative errno, | |||
| 181 | * e.g. the kernel rejecting an oversized transfer) and on | |||
| 182 | * NVMe command errors (positive status) with Generic (SCT=0) | |||
| 183 | * or Command Specific (SCT=1) status types. Path errors | |||
| 184 | * (SCT=3) and media errors (SCT=2) are not recoverable by | |||
| 185 | * reducing the transfer size. | |||
| 186 | */ | |||
| 187 | if (ret < 0 || | |||
| 188 | (ret > 0 && | |||
| 189 | (nvme_status_code_type(ret) <= NVME_SCT_CMD_SPECIFIC))) { | |||
| 190 | xfer_len = (xfer_len / 2) & | |||
| 191 | ~(__u32)(NVME_LOG_PAGE_PDU_SIZE4096 - 1); | |||
| 192 | if (xfer_len < NVME_LOG_PAGE_PDU_SIZE4096) | |||
| 193 | return ret; | |||
| 194 | continue; | |||
| 195 | } | |||
| 196 | if (ret) | |||
| 197 | return ret; | |||
| 198 | ||||
| 199 | offset += xfer; | |||
| 200 | ptr += xfer; | |||
| 201 | } while (offset < data_len); | |||
| 202 | ||||
| 203 | return 0; | |||
| 204 | } | |||
| 205 | ||||
| 206 | static int read_ana_chunk(struct libnvme_transport_handle *hdl, | |||
| 207 | enum nvme_log_ana_lsp lsp, bool_Bool rae, | |||
| 208 | __u8 *log, __u8 **read, __u8 *to_read, __u8 *log_end) | |||
| 209 | { | |||
| 210 | struct libnvme_passthru_cmd cmd; | |||
| 211 | ||||
| 212 | if (to_read > log_end) | |||
| 213 | return -ENOSPC28; | |||
| 214 | ||||
| 215 | while (*read < to_read) { | |||
| 216 | __u32 len = min_t(__u32, log_end - *read,({ __u32 _ta = (log_end - *read); __u32 _tb = (4096); ({ typeof (_ta) _a = (_ta); typeof(_tb) _b = (_tb); do { } while (0); _a < _b ? _a : _b; }); }) | |||
| 217 | NVME_LOG_PAGE_PDU_SIZE)({ __u32 _ta = (log_end - *read); __u32 _tb = (4096); ({ typeof (_ta) _a = (_ta); typeof(_tb) _b = (_tb); do { } while (0); _a < _b ? _a : _b; }); }); | |||
| 218 | int ret; | |||
| 219 | ||||
| 220 | nvme_init_get_log_ana(&cmd, lsp, *read - log, *read, len); | |||
| 221 | ret = libnvme_get_log(hdl, &cmd, rae, NVME_LOG_PAGE_PDU_SIZE4096); | |||
| 222 | if (ret) | |||
| 223 | return ret; | |||
| 224 | ||||
| 225 | *read += len; | |||
| 226 | } | |||
| 227 | return 0; | |||
| 228 | } | |||
| 229 | ||||
| 230 | static int try_read_ana(struct libnvme_transport_handle *hdl, | |||
| 231 | enum nvme_log_ana_lsp lsp, bool_Bool rae, | |||
| 232 | struct nvme_ana_log *log, __u8 *log_end, | |||
| 233 | __u8 *read, __u8 **to_read, bool_Bool *may_retry) | |||
| 234 | { | |||
| 235 | __u16 ngrps = le16_to_cpu(log->ngrps); | |||
| 236 | ||||
| 237 | while (ngrps--) { | |||
| 238 | __u8 *group = *to_read; | |||
| 239 | int ret; | |||
| 240 | __le32 nnsids; | |||
| 241 | ||||
| 242 | *to_read += sizeof(*log->descs); | |||
| 243 | ret = read_ana_chunk(hdl, lsp, rae, | |||
| 244 | (__u8 *)log, &read, *to_read, log_end); | |||
| 245 | if (ret) { | |||
| 246 | /* | |||
| 247 | * If the provided buffer isn't long enough, | |||
| 248 | * the log page may have changed while reading it | |||
| 249 | * and the computed length was inaccurate. | |||
| 250 | * Have the caller check chgcnt and retry. | |||
| 251 | */ | |||
| 252 | *may_retry = ret == -ENOSPC28; | |||
| 253 | return ret; | |||
| 254 | } | |||
| 255 | ||||
| 256 | /* | |||
| 257 | * struct nvme_ana_group_desc has 8-byte alignment | |||
| 258 | * but the group pointer is only 4-byte aligned. | |||
| 259 | * Don't dereference the misaligned pointer. | |||
| 260 | */ | |||
| 261 | memcpy(&nnsids, | |||
| 262 | group + offsetof(struct nvme_ana_group_desc, nnsids)__builtin_offsetof(struct nvme_ana_group_desc, nnsids), | |||
| 263 | sizeof(nnsids)); | |||
| 264 | /* | |||
| 265 | * The NSIDs must fit the buffer. Check it before moving | |||
| 266 | * to_read: with a 32 bit size_t, NNSIDS * 4 wraps around. | |||
| 267 | */ | |||
| 268 | if (le32_to_cpu(nnsids) > | |||
| 269 | (size_t)(log_end - *to_read) / sizeof(__le32)) { | |||
| 270 | *may_retry = true1; | |||
| 271 | return -ENOSPC28; | |||
| 272 | } | |||
| 273 | *to_read += le32_to_cpu(nnsids) * sizeof(__le32); | |||
| 274 | ret = read_ana_chunk(hdl, lsp, rae, | |||
| 275 | (__u8 *)log, &read, *to_read, log_end); | |||
| 276 | if (ret) { | |||
| 277 | *may_retry = ret == -ENOSPC28; | |||
| 278 | return ret; | |||
| 279 | } | |||
| 280 | } | |||
| 281 | ||||
| 282 | *may_retry = true1; | |||
| 283 | return 0; | |||
| 284 | } | |||
| 285 | ||||
| 286 | __shr_public__attribute__((visibility("default"))) int libnvme_get_ana_log_atomic( | |||
| 287 | struct libnvme_transport_handle *hdl, bool_Bool rae, bool_Bool rgo, | |||
| 288 | struct nvme_ana_log *log, __u32 *len, unsigned int retries) | |||
| 289 | { | |||
| 290 | const enum nvme_log_ana_lsp lsp = | |||
| 291 | rgo ? NVME_LOG_ANA_LSP_RGO_GROUPS_ONLY : 0; | |||
| 292 | /* Get Log Page can only fetch multiples of dwords */ | |||
| 293 | __u8 * const log_end = (__u8 *)log + (*len & -4); | |||
| 294 | __u8 *read = (__u8 *)log; | |||
| 295 | __u8 *to_read; | |||
| 296 | int ret; | |||
| 297 | ||||
| 298 | if (!retries) | |||
| 299 | return -EINVAL22; | |||
| 300 | ||||
| 301 | to_read = (__u8 *)log->descs; | |||
| 302 | ret = read_ana_chunk(hdl, lsp, rae, | |||
| 303 | (__u8 *)log, &read, to_read, log_end); | |||
| 304 | if (ret) | |||
| 305 | return ret; | |||
| 306 | ||||
| 307 | do { | |||
| 308 | bool_Bool may_retry = false0; | |||
| 309 | int saved_ret; | |||
| 310 | int saved_errno; | |||
| 311 | __le64 chgcnt; | |||
| 312 | ||||
| 313 | saved_ret = try_read_ana(hdl, lsp, rae, log, log_end, | |||
| 314 | read, &to_read, &may_retry); | |||
| 315 | /* | |||
| 316 | * If the log page was read with multiple Get Log Page commands, | |||
| 317 | * chgcnt must be checked afterwards to ensure atomicity | |||
| 318 | */ | |||
| 319 | *len = to_read - (__u8 *)log; | |||
| 320 | if (*len <= NVME_LOG_PAGE_PDU_SIZE4096 || !may_retry) | |||
| 321 | return saved_ret; | |||
| 322 | ||||
| 323 | saved_errno = errno(*__errno_location ()); | |||
| 324 | chgcnt = log->chgcnt; | |||
| 325 | read = (__u8 *)log; | |||
| 326 | to_read = (__u8 *)log->descs; | |||
| 327 | ret = read_ana_chunk(hdl, lsp, rae, | |||
| 328 | (__u8 *)log, &read, to_read, log_end); | |||
| 329 | if (ret) | |||
| 330 | return ret; | |||
| 331 | ||||
| 332 | if (log->chgcnt == chgcnt) { | |||
| 333 | /* Log hasn't changed; return try_read_ana() result */ | |||
| 334 | errno(*__errno_location ()) = saved_errno; | |||
| 335 | return saved_ret; | |||
| 336 | } | |||
| 337 | } while (--retries); | |||
| 338 | ||||
| 339 | return -EAGAIN11; | |||
| 340 | } | |||
| 341 | ||||
| 342 | __shr_public__attribute__((visibility("default"))) int libnvme_set_etdas( | |||
| 343 | struct libnvme_transport_handle *hdl, bool_Bool *changed) | |||
| 344 | { | |||
| 345 | struct nvme_feat_host_behavior da4; | |||
| 346 | struct libnvme_passthru_cmd cmd; | |||
| 347 | int err; | |||
| 348 | ||||
| 349 | nvme_init_get_features_host_behavior(&cmd, 0, &da4); | |||
| 350 | err = libnvme_exec_admin_passthru(hdl, &cmd); | |||
| 351 | if (err) | |||
| 352 | return err; | |||
| 353 | ||||
| 354 | if (da4.etdas) { | |||
| 355 | *changed = false0; | |||
| 356 | return 0; | |||
| 357 | } | |||
| 358 | ||||
| 359 | da4.etdas = 1; | |||
| 360 | ||||
| 361 | nvme_init_set_features_host_behavior(&cmd, false0, &da4); | |||
| 362 | err = libnvme_exec_admin_passthru(hdl, &cmd); | |||
| 363 | if (err) | |||
| 364 | return err; | |||
| 365 | ||||
| 366 | *changed = true1; | |||
| 367 | return 0; | |||
| 368 | } | |||
| 369 | ||||
| 370 | __shr_public__attribute__((visibility("default"))) int libnvme_clear_etdas( | |||
| 371 | struct libnvme_transport_handle *hdl, bool_Bool *changed) | |||
| 372 | { | |||
| 373 | struct nvme_feat_host_behavior da4; | |||
| 374 | struct libnvme_passthru_cmd cmd; | |||
| 375 | int err; | |||
| 376 | ||||
| 377 | nvme_init_get_features_host_behavior(&cmd, 0, &da4); | |||
| 378 | err = libnvme_exec_admin_passthru(hdl, &cmd); | |||
| 379 | if (err) | |||
| 380 | return err; | |||
| 381 | ||||
| 382 | if (!da4.etdas) { | |||
| 383 | *changed = false0; | |||
| 384 | return 0; | |||
| 385 | } | |||
| 386 | ||||
| 387 | da4.etdas = 0; | |||
| 388 | nvme_init_set_features_host_behavior(&cmd, false0, &da4); | |||
| 389 | err = libnvme_exec_admin_passthru(hdl, &cmd); | |||
| 390 | if (err) | |||
| 391 | return err; | |||
| 392 | ||||
| 393 | *changed = true1; | |||
| 394 | return 0; | |||
| 395 | } | |||
| 396 | ||||
| 397 | __shr_public__attribute__((visibility("default"))) int libnvme_get_uuid_list(struct libnvme_transport_handle *hdl, | |||
| 398 | struct nvme_id_uuid_list *uuid_list) | |||
| 399 | { | |||
| 400 | struct libnvme_passthru_cmd cmd; | |||
| 401 | struct nvme_id_ctrl ctrl; | |||
| 402 | int err; | |||
| 403 | ||||
| 404 | if (!hdl) | |||
| 405 | return -ENODEV19; | |||
| 406 | if (!uuid_list) | |||
| 407 | return -EINVAL22; | |||
| 408 | ||||
| 409 | memset(&ctrl, 0, sizeof(struct nvme_id_ctrl)); | |||
| 410 | nvme_init_identify_ctrl(&cmd, &ctrl); | |||
| 411 | err = libnvme_exec_admin_passthru(hdl, &cmd); | |||
| 412 | if (err) { | |||
| 413 | libnvme_msg(hdl->ctx, LIBNVME_LOG_ERR,__libnvme_msg(hdl->ctx, LIBNVME_LOG_ERR, ((void*)0), "ERROR: nvme_identify_ctrl() failed 0x%x\n" , err) | |||
| 414 | "ERROR: nvme_identify_ctrl() failed 0x%x\n", err)__libnvme_msg(hdl->ctx, LIBNVME_LOG_ERR, ((void*)0), "ERROR: nvme_identify_ctrl() failed 0x%x\n" , err); | |||
| 415 | return err; | |||
| 416 | } | |||
| 417 | ||||
| 418 | if (le32_to_cpu(ctrl.ctratt) & NVME_CTRL_CTRATT_UUID_LIST) { | |||
| 419 | nvme_init_identify_uuid_list(&cmd, uuid_list); | |||
| 420 | err = libnvme_exec_admin_passthru(hdl, &cmd); | |||
| 421 | } | |||
| 422 | ||||
| 423 | return err; | |||
| 424 | } | |||
| 425 | ||||
| 426 | __shr_public__attribute__((visibility("default"))) int libnvme_get_telemetry_max( | |||
| 427 | struct libnvme_transport_handle *hdl, | |||
| 428 | enum nvme_telemetry_da *da, size_t *data_tx) | |||
| 429 | { | |||
| 430 | __cleanup_libnvme_free__attribute__((cleanup(libnvme_freep))) struct nvme_id_ctrl *id_ctrl = NULL((void*)0); | |||
| 431 | struct libnvme_passthru_cmd cmd; | |||
| 432 | int err; | |||
| 433 | ||||
| 434 | id_ctrl = libnvme_alloc(sizeof(*id_ctrl)); | |||
| 435 | if (!id_ctrl) | |||
| 436 | return -ENOMEM12; | |||
| 437 | ||||
| 438 | nvme_init_identify_ctrl(&cmd, id_ctrl); | |||
| 439 | err = libnvme_exec_admin_passthru(hdl, &cmd); | |||
| 440 | if (err) | |||
| 441 | return err; | |||
| 442 | ||||
| 443 | if (data_tx) { | |||
| 444 | *data_tx = id_ctrl->mdts; | |||
| 445 | if (id_ctrl->mdts) { | |||
| 446 | /* | |||
| 447 | * assuming CAP.MPSMIN is zero minimum Memory Page Size | |||
| 448 | * is at least 4096 bytes | |||
| 449 | */ | |||
| 450 | *data_tx = (1 << id_ctrl->mdts) * 4096; | |||
| 451 | } | |||
| 452 | } | |||
| 453 | if (da) { | |||
| 454 | if (id_ctrl->lpa & 0x8) | |||
| 455 | *da = NVME_TELEMETRY_DA_3; | |||
| 456 | if (id_ctrl->lpa & 0x40) | |||
| 457 | *da = NVME_TELEMETRY_DA_4; | |||
| 458 | ||||
| 459 | } | |||
| 460 | return err; | |||
| 461 | } | |||
| 462 | ||||
| 463 | __shr_public__attribute__((visibility("default"))) int libnvme_get_telemetry_log( | |||
| 464 | struct libnvme_transport_handle *hdl, bool_Bool create, bool_Bool ctrl, | |||
| 465 | bool_Bool rae, size_t max_data_tx, enum nvme_telemetry_da da, | |||
| 466 | struct nvme_telemetry_log **buf, size_t *size) | |||
| 467 | { | |||
| 468 | static const __u32 xfer = NVME_LOG_TELEM_BLOCK_SIZE; | |||
| 469 | struct nvme_telemetry_log *telem; | |||
| 470 | struct libnvme_passthru_cmd cmd; | |||
| 471 | __cleanup_libnvme_free__attribute__((cleanup(libnvme_freep))) void *log = NULL((void*)0); | |||
| 472 | void *tmp; | |||
| 473 | int err; | |||
| 474 | size_t dalb; | |||
| 475 | ||||
| 476 | *size = 0; | |||
| 477 | ||||
| 478 | log = libnvme_alloc(xfer); | |||
| 479 | if (!log) | |||
| 480 | return -ENOMEM12; | |||
| 481 | ||||
| 482 | if (ctrl) { | |||
| 483 | nvme_init_get_log_telemetry_ctrl(&cmd, 0, log, xfer); | |||
| 484 | err = libnvme_get_log(hdl, &cmd, true1, xfer); | |||
| 485 | } else { | |||
| 486 | if (create) { | |||
| 487 | nvme_init_get_log_create_telemetry_host_mcda(&cmd, | |||
| 488 | da, log); | |||
| 489 | err = libnvme_get_log(hdl, &cmd, false0, xfer); | |||
| 490 | } else { | |||
| 491 | nvme_init_get_log_telemetry_host(&cmd, 0, log, xfer); | |||
| 492 | err = libnvme_get_log(hdl, &cmd, false0, xfer); | |||
| 493 | } | |||
| 494 | } | |||
| 495 | ||||
| 496 | if (err) | |||
| 497 | return err; | |||
| 498 | ||||
| 499 | telem = log; | |||
| 500 | if (ctrl && !telem->ctrlavail) { | |||
| 501 | *buf = log; | |||
| 502 | log = NULL((void*)0); | |||
| 503 | *size = xfer; | |||
| 504 | return 0; | |||
| 505 | } | |||
| 506 | ||||
| 507 | switch (da) { | |||
| 508 | case NVME_TELEMETRY_DA_1: | |||
| 509 | dalb = le16_to_cpu(telem->dalb1); | |||
| 510 | break; | |||
| 511 | case NVME_TELEMETRY_DA_2: | |||
| 512 | dalb = le16_to_cpu(telem->dalb2); | |||
| 513 | break; | |||
| 514 | case NVME_TELEMETRY_DA_3: | |||
| 515 | /* dalb3 >= dalb2 >= dalb1 */ | |||
| 516 | dalb = le16_to_cpu(telem->dalb3); | |||
| 517 | break; | |||
| 518 | case NVME_TELEMETRY_DA_4: | |||
| 519 | dalb = le32_to_cpu(telem->dalb4); | |||
| 520 | break; | |||
| 521 | default: | |||
| 522 | return -EINVAL22; | |||
| 523 | } | |||
| 524 | ||||
| 525 | if (dalb == 0) | |||
| 526 | return -ENOENT2; | |||
| 527 | ||||
| 528 | *size = (dalb + 1) * xfer; | |||
| 529 | tmp = libnvme_realloc(log, *size); | |||
| 530 | if (!tmp) | |||
| 531 | return -ENOMEM12; | |||
| 532 | log = tmp; | |||
| 533 | ||||
| 534 | if (ctrl) | |||
| 535 | nvme_init_get_log_telemetry_ctrl(&cmd, 0, log, *size); | |||
| 536 | else | |||
| 537 | nvme_init_get_log_telemetry_host(&cmd, 0, log, *size); | |||
| 538 | err = libnvme_get_log(hdl, &cmd, rae, max_data_tx); | |||
| 539 | if (err) | |||
| 540 | return err; | |||
| 541 | ||||
| 542 | *buf = log; | |||
| 543 | log = NULL((void*)0); | |||
| 544 | return 0; | |||
| 545 | } | |||
| 546 | ||||
| 547 | static int nvme_check_get_telemetry_log(struct libnvme_transport_handle *hdl, | |||
| 548 | bool_Bool create, bool_Bool ctrl, bool_Bool rae, | |||
| 549 | struct nvme_telemetry_log **log, enum nvme_telemetry_da da, | |||
| 550 | size_t *size) | |||
| 551 | { | |||
| 552 | enum nvme_telemetry_da max_da = 0; | |||
| 553 | int err; | |||
| 554 | ||||
| 555 | err = libnvme_get_telemetry_max(hdl, &max_da, NULL((void*)0)); | |||
| 556 | if (err) | |||
| 557 | return err; | |||
| 558 | ||||
| 559 | if (da > max_da) | |||
| 560 | return -ENOENT2; | |||
| 561 | ||||
| 562 | return libnvme_get_telemetry_log(hdl, create, ctrl, rae, 4096, da, | |||
| 563 | log, size); | |||
| 564 | } | |||
| 565 | ||||
| 566 | ||||
| 567 | __shr_public__attribute__((visibility("default"))) int libnvme_get_ctrl_telemetry( | |||
| 568 | struct libnvme_transport_handle *hdl, bool_Bool rae, | |||
| 569 | struct nvme_telemetry_log **log, enum nvme_telemetry_da da, | |||
| 570 | size_t *size) | |||
| 571 | { | |||
| 572 | return nvme_check_get_telemetry_log(hdl, false0, true1, rae, log, | |||
| 573 | da, size); | |||
| 574 | } | |||
| 575 | ||||
| 576 | __shr_public__attribute__((visibility("default"))) int libnvme_get_host_telemetry( | |||
| 577 | struct libnvme_transport_handle *hdl, | |||
| 578 | struct nvme_telemetry_log **log, enum nvme_telemetry_da da, | |||
| 579 | size_t *size) | |||
| 580 | { | |||
| 581 | return nvme_check_get_telemetry_log(hdl, false0, false0, false0, log, | |||
| 582 | da, size); | |||
| 583 | } | |||
| 584 | ||||
| 585 | __shr_public__attribute__((visibility("default"))) int libnvme_get_new_host_telemetry( | |||
| 586 | struct libnvme_transport_handle *hdl, | |||
| 587 | struct nvme_telemetry_log **log, enum nvme_telemetry_da da, | |||
| 588 | size_t *size) | |||
| 589 | { | |||
| 590 | return nvme_check_get_telemetry_log(hdl, true1, false0, false0, log, | |||
| 591 | da, size); | |||
| 592 | } | |||
| 593 | ||||
| 594 | int libnvme_get_lba_status_log(struct libnvme_transport_handle *hdl, bool_Bool rae, | |||
| 595 | struct nvme_lba_status_log **log) | |||
| 596 | { | |||
| 597 | __cleanup_free__attribute__((cleanup(shr_freep))) struct nvme_lba_status_log *buf = NULL((void*)0); | |||
| 598 | struct libnvme_passthru_cmd cmd; | |||
| 599 | __u32 size; | |||
| 600 | void *tmp; | |||
| 601 | int err; | |||
| 602 | ||||
| 603 | buf = malloc(sizeof(*buf)); | |||
| ||||
| 604 | if (!buf) | |||
| 605 | return -ENOMEM12; | |||
| 606 | ||||
| 607 | nvme_init_get_log_lba_status(&cmd, 0, buf, sizeof(*buf)); | |||
| 608 | err = libnvme_get_log(hdl, &cmd, true1, sizeof(*buf)); | |||
| 609 | if (err
| |||
| 610 | *log = NULL((void*)0); | |||
| ||||
| 611 | return err; | |||
| 612 | } | |||
| 613 | ||||
| 614 | size = le32_to_cpu(buf->lslplen); | |||
| 615 | if (!size) { | |||
| 616 | *log = buf; | |||
| 617 | buf = NULL((void*)0); | |||
| 618 | return 0; | |||
| 619 | } | |||
| 620 | ||||
| 621 | tmp = realloc(buf, size); | |||
| 622 | if (!tmp) { | |||
| 623 | *log = NULL((void*)0); | |||
| 624 | return -ENOMEM12; | |||
| 625 | } | |||
| 626 | buf = tmp; | |||
| 627 | ||||
| 628 | nvme_init_get_log_lba_status(&cmd, 0, buf, size); | |||
| 629 | err = libnvme_get_log(hdl, &cmd, rae, NVME_LOG_PAGE_PDU_SIZE4096); | |||
| 630 | if (err) { | |||
| 631 | *log = NULL((void*)0); | |||
| 632 | return err; | |||
| 633 | } | |||
| 634 | ||||
| 635 | *log = buf; | |||
| 636 | buf = NULL((void*)0); | |||
| 637 | return 0; | |||
| 638 | } | |||
| 639 | ||||
| 640 | __shr_public__attribute__((visibility("default"))) size_t libnvme_get_ana_log_len_from_id_ctrl( | |||
| 641 | const struct nvme_id_ctrl *id_ctrl, bool_Bool rgo) | |||
| 642 | { | |||
| 643 | __u32 nanagrpid = le32_to_cpu(id_ctrl->nanagrpid); | |||
| 644 | size_t size = sizeof(struct nvme_ana_log) + | |||
| 645 | nanagrpid * sizeof(struct nvme_ana_group_desc); | |||
| 646 | ||||
| 647 | return rgo ? size : size + le32_to_cpu(id_ctrl->mnan) * sizeof(__le32); | |||
| 648 | } | |||
| 649 | ||||
| 650 | __shr_public__attribute__((visibility("default"))) int libnvme_get_ana_log_len( | |||
| 651 | struct libnvme_transport_handle *hdl, size_t *analen) | |||
| 652 | { | |||
| 653 | __cleanup_libnvme_free__attribute__((cleanup(libnvme_freep))) struct nvme_id_ctrl *ctrl = NULL((void*)0); | |||
| 654 | struct libnvme_passthru_cmd cmd; | |||
| 655 | int ret; | |||
| 656 | ||||
| 657 | ctrl = libnvme_alloc(sizeof(*ctrl)); | |||
| 658 | if (!ctrl) | |||
| 659 | return -ENOMEM12; | |||
| 660 | ||||
| 661 | nvme_init_identify_ctrl(&cmd, ctrl); | |||
| 662 | ret = libnvme_exec_admin_passthru(hdl, &cmd); | |||
| 663 | if (ret) | |||
| 664 | return ret; | |||
| 665 | ||||
| 666 | *analen = libnvme_get_ana_log_len_from_id_ctrl(ctrl, false0); | |||
| 667 | return 0; | |||
| 668 | } | |||
| 669 | ||||
| 670 | __shr_public__attribute__((visibility("default"))) int libnvme_get_logical_block_size( | |||
| 671 | struct libnvme_transport_handle *hdl, __u32 nsid, int *blksize) | |||
| 672 | { | |||
| 673 | __cleanup_libnvme_free__attribute__((cleanup(libnvme_freep))) struct nvme_id_ns *ns = NULL((void*)0); | |||
| 674 | struct libnvme_passthru_cmd cmd; | |||
| 675 | __u8 flbas; | |||
| 676 | int ret; | |||
| 677 | ||||
| 678 | ns = libnvme_alloc(sizeof(*ns)); | |||
| 679 | if (!ns) | |||
| 680 | return -ENOMEM12; | |||
| 681 | ||||
| 682 | nvme_init_identify_ns(&cmd, nsid, ns); | |||
| 683 | ret = libnvme_exec_admin_passthru(hdl, &cmd); | |||
| 684 | if (ret) | |||
| 685 | return ret; | |||
| 686 | ||||
| 687 | nvme_id_ns_flbas_to_lbaf_inuse(ns->flbas, &flbas); | |||
| 688 | /* an LBA data size the device reports, which has to fit an int */ | |||
| 689 | if (ns->lbaf[flbas].ds >= 31) | |||
| 690 | return -EINVAL22; | |||
| 691 | *blksize = 1 << ns->lbaf[flbas].ds; | |||
| 692 | ||||
| 693 | return 0; | |||
| 694 | } | |||
| 695 | ||||
| 696 | __shr_public__attribute__((visibility("default"))) int libnvme_get_feature_length( | |||
| 697 | int fid, __u32 cdw11, enum nvme_data_tfr dir, __u32 *len) | |||
| 698 | { | |||
| 699 | switch (fid) { | |||
| 700 | case NVME_FEAT_FID_LBA_RANGE: | |||
| 701 | *len = sizeof(struct nvme_lba_range_type); | |||
| 702 | break; | |||
| 703 | case NVME_FEAT_FID_AUTO_PST: | |||
| 704 | *len = sizeof(struct nvme_feat_auto_pst); | |||
| 705 | break; | |||
| 706 | case NVME_FEAT_FID_PLM_CONFIG: | |||
| 707 | *len = sizeof(struct nvme_plm_config); | |||
| 708 | break; | |||
| 709 | case NVME_FEAT_FID_TIMESTAMP: | |||
| 710 | *len = sizeof(struct nvme_timestamp); | |||
| 711 | break; | |||
| 712 | case NVME_FEAT_FID_HOST_BEHAVIOR: | |||
| 713 | *len = sizeof(struct nvme_feat_host_behavior); | |||
| 714 | break; | |||
| 715 | case NVME_FEAT_FID_HOST_ID: | |||
| 716 | *len = (cdw11 & 0x1) ? 16 : 8; | |||
| 717 | break; | |||
| 718 | case NVME_FEAT_FID_HOST_MEM_BUF: | |||
| 719 | if (dir == NVME_DATA_TFR_HOST_TO_CTRL) { | |||
| 720 | *len = 0; | |||
| 721 | break; | |||
| 722 | } | |||
| 723 | *len = sizeof(struct nvme_host_mem_buf_attrs); | |||
| 724 | break; | |||
| 725 | case NVME_FEAT_FID_ARBITRATION: | |||
| 726 | case NVME_FEAT_FID_POWER_MGMT: | |||
| 727 | case NVME_FEAT_FID_TEMP_THRESH: | |||
| 728 | case NVME_FEAT_FID_ERR_RECOVERY: | |||
| 729 | case NVME_FEAT_FID_VOLATILE_WC: | |||
| 730 | case NVME_FEAT_FID_NUM_QUEUES: | |||
| 731 | case NVME_FEAT_FID_IRQ_COALESCE: | |||
| 732 | case NVME_FEAT_FID_IRQ_CONFIG: | |||
| 733 | case NVME_FEAT_FID_WRITE_ATOMIC: | |||
| 734 | case NVME_FEAT_FID_ASYNC_EVENT: | |||
| 735 | case NVME_FEAT_FID_KATO: | |||
| 736 | case NVME_FEAT_FID_HCTM: | |||
| 737 | case NVME_FEAT_FID_NOPSC: | |||
| 738 | case NVME_FEAT_FID_RRL: | |||
| 739 | case NVME_FEAT_FID_PLM_WINDOW: | |||
| 740 | case NVME_FEAT_FID_LBA_STS_INTERVAL: | |||
| 741 | case NVME_FEAT_FID_SANITIZE: | |||
| 742 | case NVME_FEAT_FID_ENDURANCE_EVT_CFG: | |||
| 743 | case NVME_FEAT_FID_SW_PROGRESS: | |||
| 744 | case NVME_FEAT_FID_RESV_NF_MASK: | |||
| 745 | case NVME_FEAT_FID_RESV_PERSIST: | |||
| 746 | case NVME_FEAT_FID_WRITE_PROTECT: | |||
| 747 | case NVME_FEAT_FID_POWER_LIMIT: | |||
| 748 | case NVME_FEAT_FID_POWER_MEASUREMENT: | |||
| 749 | *len = 0; | |||
| 750 | break; | |||
| 751 | case NVME_FEAT_FID_ENH_CTRL_METADATA: | |||
| 752 | case NVME_FEAT_FID_CTRL_METADATA: | |||
| 753 | case NVME_FEAT_FID_NS_METADATA: | |||
| 754 | *len = sizeof(struct nvme_host_metadata); | |||
| 755 | break; | |||
| 756 | case NVME_FEAT_FID_PERF_CHARACTERISTICS: | |||
| 757 | *len = sizeof(struct nvme_perf_characteristics); | |||
| 758 | break; | |||
| 759 | case NVME_FEAT_FID_FDP_EVENTS: | |||
| 760 | *len = NVME_FEAT_FDPE_NOET_MASK * | |||
| 761 | sizeof(struct nvme_fdp_supported_event_desc); | |||
| 762 | break; | |||
| 763 | default: | |||
| 764 | return -EINVAL22; | |||
| 765 | } | |||
| 766 | return 0; | |||
| 767 | } | |||
| 768 | ||||
| 769 | __shr_public__attribute__((visibility("default"))) int libnvme_get_directive_receive_length( | |||
| 770 | enum nvme_directive_dtype dtype, | |||
| 771 | enum nvme_directive_receive_doper doper, __u32 *len) | |||
| 772 | { | |||
| 773 | switch (dtype) { | |||
| 774 | case NVME_DIRECTIVE_DTYPE_IDENTIFY: | |||
| 775 | switch (doper) { | |||
| 776 | case NVME_DIRECTIVE_RECEIVE_IDENTIFY_DOPER_PARAM: | |||
| 777 | *len = sizeof(struct nvme_id_directives); | |||
| 778 | return 0; | |||
| 779 | default: | |||
| 780 | return -EINVAL22; | |||
| 781 | } | |||
| 782 | case NVME_DIRECTIVE_DTYPE_STREAMS: | |||
| 783 | switch (doper) { | |||
| 784 | case NVME_DIRECTIVE_RECEIVE_STREAMS_DOPER_PARAM: | |||
| 785 | *len = sizeof(struct nvme_streams_directive_params); | |||
| 786 | return 0; | |||
| 787 | case NVME_DIRECTIVE_RECEIVE_STREAMS_DOPER_STATUS: | |||
| 788 | *len = (128 * 1024) * sizeof(__le16); | |||
| 789 | return 0; | |||
| 790 | case NVME_DIRECTIVE_RECEIVE_STREAMS_DOPER_RESOURCE: | |||
| 791 | *len = 0; | |||
| 792 | return 0; | |||
| 793 | default: | |||
| 794 | return -EINVAL22; | |||
| 795 | } | |||
| 796 | default: | |||
| 797 | return -EINVAL22; | |||
| 798 | } | |||
| 799 | } |