| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * NVMe over Fabrics Persist Reservation. | |
| * Copyright (c) 2024 Guixin Liu, Alibaba Group. | |
| * All rights reserved. | |
| */ | |
| #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | |
| #include <linux/unaligned.h> | |
| #include "nvmet.h" | |
| #define NVMET_PR_NOTIFY_MASK_ALL \ | |
| (1 << NVME_PR_NOTIFY_BIT_REG_PREEMPTED | \ | |
| 1 << NVME_PR_NOTIFY_BIT_RESV_RELEASED | \ | |
| 1 << NVME_PR_NOTIFY_BIT_RESV_PREEMPTED) | |
| static inline bool nvmet_pr_parse_ignore_key(u32 cdw10) | |
| { | |
| /* Ignore existing key, bit 03. */ | |
| return (cdw10 >> 3) & 1; | |
| } | |
| static inline struct nvmet_ns *nvmet_pr_to_ns(struct nvmet_pr *pr) | |
| { | |
| return container_of(pr, struct nvmet_ns, pr); | |
| } | |
| static struct nvmet_pr_registrant * | |
| nvmet_pr_find_registrant(struct nvmet_pr *pr, uuid_t *hostid) | |
| { | |
| struct nvmet_pr_registrant *reg; | |
| list_for_each_entry_rcu(reg, &pr->registrant_list, entry) { | |
| if (uuid_equal(®->hostid, hostid)) | |
| return reg; | |
| } | |
| return NULL; | |
| } | |
| u16 nvmet_set_feat_resv_notif_mask(struct nvmet_req *req, u32 mask) | |
| { | |
| u32 nsid = le32_to_cpu(req->cmd->common.nsid); | |
| struct nvmet_ctrl *ctrl = req->sq->ctrl; | |
| struct nvmet_ns *ns; | |
| unsigned long idx; | |
| u16 status; | |
| if (mask & ~(NVMET_PR_NOTIFY_MASK_ALL)) { | |
| req->error_loc = offsetof(struct nvme_common_command, cdw11); | |
| return NVME_SC_INVALID_FIELD | NVME_STATUS_DNR; | |
| } | |
| if (nsid != U32_MAX) { | |
| status = nvmet_req_find_ns(req); | |
| if (status == (NVME_SC_INVALID_NS | NVME_STATUS_DNR)) | |
| return NVME_SC_INVALID_FIELD | NVME_STATUS_DNR; | |
| if (status) | |
| return status; | |
| if (!req->ns->pr.enable) | |
| return NVME_SC_INVALID_FIELD | NVME_STATUS_DNR; | |
| WRITE_ONCE(req->ns->pr.notify_mask, mask); | |
| goto success; | |
| } | |
| nvmet_for_each_enabled_ns(&ctrl->subsys->namespaces, idx, ns) { | |
| if (ns->pr.enable) | |
| WRITE_ONCE(ns->pr.notify_mask, mask); | |
| } | |
| success: | |
| nvmet_set_result(req, mask); | |
| return NVME_SC_SUCCESS; | |
| } | |
| u16 nvmet_get_feat_resv_notif_mask(struct nvmet_req *req) | |
| { | |
| u16 status; | |
| status = nvmet_req_find_ns(req); | |
| if (status == (NVME_SC_INVALID_NS | NVME_STATUS_DNR)) | |
| return NVME_SC_INVALID_FIELD | NVME_STATUS_DNR; | |
| if (status) | |
| return status; | |
| if (!req->ns->pr.enable) | |
| return NVME_SC_INVALID_FIELD | NVME_STATUS_DNR; | |
| nvmet_set_result(req, READ_ONCE(req->ns->pr.notify_mask)); | |
| return status; | |
| } | |
| void nvmet_execute_get_log_page_resv(struct nvmet_req *req) | |
| { | |
| struct nvmet_pr_log_mgr *log_mgr = &req->sq->ctrl->pr_log_mgr; | |
| struct nvme_pr_log next_log = {0}; | |
| struct nvme_pr_log log = {0}; | |
| u16 status = NVME_SC_SUCCESS; | |
| u64 lost_count; | |
| u64 cur_count; | |
| u64 next_count; | |
| mutex_lock(&log_mgr->lock); | |
| if (!kfifo_get(&log_mgr->log_queue, &log)) | |
| goto out; | |
| /* | |
| * We can't get the last in kfifo. | |
| * Utilize the current count and the count from the next log to | |
| * calculate the number of lost logs, while also addressing cases | |
| * of overflow. If there is no subsequent log, the number of lost | |
| * logs is equal to the lost_count within the nvmet_pr_log_mgr. | |
| */ | |
| cur_count = le64_to_cpu(log.count); | |
| if (kfifo_peek(&log_mgr->log_queue, &next_log)) { | |
| next_count = le64_to_cpu(next_log.count); | |
| if (next_count > cur_count) | |
| lost_count = next_count - cur_count - 1; | |
| else | |
| lost_count = U64_MAX - cur_count + next_count - 1; | |
| } else { | |
| lost_count = log_mgr->lost_count; | |
| } | |
| log.count = cpu_to_le64((cur_count + lost_count) == 0 ? | |
| 1 : (cur_count + lost_count)); | |
| log_mgr->lost_count -= lost_count; | |
| log.nr_pages = kfifo_len(&log_mgr->log_queue); | |
| out: | |
| status = nvmet_copy_to_sgl(req, 0, &log, sizeof(log)); | |
| mutex_unlock(&log_mgr->lock); | |
| nvmet_req_complete(req, status); | |
| } | |
| static void nvmet_pr_add_resv_log(struct nvmet_ctrl *ctrl, u8 log_type, | |
| u32 nsid) | |
| { | |
| struct nvmet_pr_log_mgr *log_mgr = &ctrl->pr_log_mgr; | |
| struct nvme_pr_log log = {0}; | |
| mutex_lock(&log_mgr->lock); | |
| log_mgr->counter++; | |
| if (log_mgr->counter == 0) | |
| log_mgr->counter = 1; | |
| log.count = cpu_to_le64(log_mgr->counter); | |
| log.type = log_type; | |
| log.nsid = cpu_to_le32(nsid); | |
| if (!kfifo_put(&log_mgr->log_queue, log)) { | |
| pr_info("a reservation log lost, cntlid:%d, log_type:%d, nsid:%u\n", | |
| ctrl->cntlid, log_type, nsid); | |
| log_mgr->lost_count++; | |
| } | |
| mutex_unlock(&log_mgr->lock); | |
| } | |
| static void nvmet_pr_resv_released(struct nvmet_pr *pr, uuid_t *hostid) | |
| { | |
| struct nvmet_ns *ns = nvmet_pr_to_ns(pr); | |
| struct nvmet_subsys *subsys = ns->subsys; | |
| struct nvmet_ctrl *ctrl; | |
| if (test_bit(NVME_PR_NOTIFY_BIT_RESV_RELEASED, &pr->notify_mask)) | |
| return; | |
| mutex_lock(&subsys->lock); | |
| list_for_each_entry(ctrl, &subsys->ctrls, subsys_entry) { | |
| if (!uuid_equal(&ctrl->hostid, hostid) && | |
| nvmet_pr_find_registrant(pr, &ctrl->hostid)) { | |
| nvmet_pr_add_resv_log(ctrl, | |
| NVME_PR_LOG_RESERVATION_RELEASED, ns->nsid); | |
| nvmet_add_async_event(ctrl, NVME_AER_CSS, | |
| NVME_AEN_RESV_LOG_PAGE_AVAILABLE, | |
| NVME_LOG_RESERVATION); | |
| } | |
| } | |
| mutex_unlock(&subsys->lock); | |
| } | |
| static void nvmet_pr_send_event_to_host(struct nvmet_pr *pr, uuid_t *hostid, | |
| u8 log_type) | |
| { | |
| struct nvmet_ns *ns = nvmet_pr_to_ns(pr); | |
| struct nvmet_subsys *subsys = ns->subsys; | |
| struct nvmet_ctrl *ctrl; | |
| mutex_lock(&subsys->lock); | |
| list_for_each_entry(ctrl, &subsys->ctrls, subsys_entry) { | |
| if (uuid_equal(hostid, &ctrl->hostid)) { | |
| nvmet_pr_add_resv_log(ctrl, log_type, ns->nsid); | |
| nvmet_add_async_event(ctrl, NVME_AER_CSS, | |
| NVME_AEN_RESV_LOG_PAGE_AVAILABLE, | |
| NVME_LOG_RESERVATION); | |
| } | |
| } | |
| mutex_unlock(&subsys->lock); | |
| } | |
| static void nvmet_pr_resv_preempted(struct nvmet_pr *pr, uuid_t *hostid) | |
| { | |
| if (test_bit(NVME_PR_NOTIFY_BIT_RESV_PREEMPTED, &pr->notify_mask)) | |
| return; | |
| nvmet_pr_send_event_to_host(pr, hostid, | |
| NVME_PR_LOG_RESERVATION_PREEMPTED); | |
| } | |
| static void nvmet_pr_registration_preempted(struct nvmet_pr *pr, | |
| uuid_t *hostid) | |
| { | |
| if (test_bit(NVME_PR_NOTIFY_BIT_REG_PREEMPTED, &pr->notify_mask)) | |
| return; | |
| nvmet_pr_send_event_to_host(pr, hostid, | |
| NVME_PR_LOG_REGISTRATION_PREEMPTED); | |
| } | |
| static inline void nvmet_pr_set_new_holder(struct nvmet_pr *pr, u8 new_rtype, | |
| struct nvmet_pr_registrant *reg) | |
| { | |
| reg->rtype = new_rtype; | |
| rcu_assign_pointer(pr->holder, reg); | |
| } | |
| static u16 nvmet_pr_register(struct nvmet_req *req, | |
| struct nvmet_pr_register_data *d) | |
| { | |
| struct nvmet_ctrl *ctrl = req->sq->ctrl; | |
| struct nvmet_pr_registrant *new, *reg; | |
| struct nvmet_pr *pr = &req->ns->pr; | |
| u16 status = NVME_SC_SUCCESS; | |
| u64 nrkey = le64_to_cpu(d->nrkey); | |
| new = kmalloc_obj(*new); | |
| if (!new) | |
| return NVME_SC_INTERNAL; | |
| down(&pr->pr_sem); | |
| reg = nvmet_pr_find_registrant(pr, &ctrl->hostid); | |
| if (reg) { | |
| if (reg->rkey != nrkey) | |
| status = NVME_SC_RESERVATION_CONFLICT | NVME_STATUS_DNR; | |
| kfree(new); | |
| goto out; | |
| } | |
| memset(new, 0, sizeof(*new)); | |
| INIT_LIST_HEAD(&new->entry); | |
| new->rkey = nrkey; | |
| uuid_copy(&new->hostid, &ctrl->hostid); | |
| list_add_tail_rcu(&new->entry, &pr->registrant_list); | |
| out: | |
| up(&pr->pr_sem); | |
| return status; | |
| } | |
| static void nvmet_pr_unregister_one(struct nvmet_pr *pr, | |
| struct nvmet_pr_registrant *reg) | |
| { | |
| struct nvmet_pr_registrant *first_reg; | |
| struct nvmet_pr_registrant *holder; | |
| u8 original_rtype; | |
| list_del_rcu(®->entry); | |
| holder = rcu_dereference_protected(pr->holder, 1); | |
| if (reg != holder) | |
| goto out; | |
| original_rtype = holder->rtype; | |
| if (original_rtype == NVME_PR_WRITE_EXCLUSIVE_ALL_REGS || | |
| original_rtype == NVME_PR_EXCLUSIVE_ACCESS_ALL_REGS) { | |
| first_reg = list_first_or_null_rcu(&pr->registrant_list, | |
| struct nvmet_pr_registrant, entry); | |
| if (first_reg) | |
| first_reg->rtype = original_rtype; | |
| rcu_assign_pointer(pr->holder, first_reg); | |
| } else { | |
| rcu_assign_pointer(pr->holder, NULL); | |
| if (original_rtype == NVME_PR_WRITE_EXCLUSIVE_REG_ONLY || | |
| original_rtype == NVME_PR_EXCLUSIVE_ACCESS_REG_ONLY) | |
| nvmet_pr_resv_released(pr, ®->hostid); | |
| } | |
| out: | |
| kfree_rcu(reg, rcu); | |
| } | |
| static u16 nvmet_pr_unregister(struct nvmet_req *req, | |
| struct nvmet_pr_register_data *d, | |
| bool ignore_key) | |
| { | |
| u16 status = NVME_SC_RESERVATION_CONFLICT | NVME_STATUS_DNR; | |
| struct nvmet_ctrl *ctrl = req->sq->ctrl; | |
| struct nvmet_pr *pr = &req->ns->pr; | |
| struct nvmet_pr_registrant *reg; | |
| down(&pr->pr_sem); | |
| list_for_each_entry_rcu(reg, &pr->registrant_list, entry) { | |
| if (uuid_equal(®->hostid, &ctrl->hostid)) { | |
| if (ignore_key || reg->rkey == le64_to_cpu(d->crkey)) { | |
| status = NVME_SC_SUCCESS; | |
| nvmet_pr_unregister_one(pr, reg); | |
| } | |
| break; | |
| } | |
| } | |
| up(&pr->pr_sem); | |
| return status; | |
| } | |
| static void nvmet_pr_update_reg_rkey(struct nvmet_pr_registrant *reg, | |
| void *attr) | |
| { | |
| reg->rkey = *(u64 *)attr; | |
| } | |
| static u16 nvmet_pr_update_reg_attr(struct nvmet_pr *pr, | |
| struct nvmet_pr_registrant *reg, | |
| void (*change_attr)(struct nvmet_pr_registrant *reg, | |
| void *attr), | |
| void *attr) | |
| { | |
| struct nvmet_pr_registrant *holder; | |
| struct nvmet_pr_registrant *new; | |
| holder = rcu_dereference_protected(pr->holder, 1); | |
| if (reg != holder) { | |
| change_attr(reg, attr); | |
| return NVME_SC_SUCCESS; | |
| } | |
| new = kmalloc_obj(*new, GFP_ATOMIC); | |
| if (!new) | |
| return NVME_SC_INTERNAL; | |
| new->rkey = holder->rkey; | |
| new->rtype = holder->rtype; | |
| uuid_copy(&new->hostid, &holder->hostid); | |
| INIT_LIST_HEAD(&new->entry); | |
| change_attr(new, attr); | |
| list_replace_rcu(&holder->entry, &new->entry); | |
| rcu_assign_pointer(pr->holder, new); | |
| kfree_rcu(holder, rcu); | |
| return NVME_SC_SUCCESS; | |
| } | |
| static u16 nvmet_pr_replace(struct nvmet_req *req, | |
| struct nvmet_pr_register_data *d, | |
| bool ignore_key) | |
| { | |
| u16 status = NVME_SC_RESERVATION_CONFLICT | NVME_STATUS_DNR; | |
| struct nvmet_ctrl *ctrl = req->sq->ctrl; | |
| struct nvmet_pr *pr = &req->ns->pr; | |
| struct nvmet_pr_registrant *reg, *new = NULL; | |
| u64 nrkey = le64_to_cpu(d->nrkey); | |
| if (ignore_key && nrkey) { | |
| new = kzalloc_obj(*new); | |
| if (!new) | |
| return NVME_SC_INTERNAL; | |
| } | |
| down(&pr->pr_sem); | |
| list_for_each_entry_rcu(reg, &pr->registrant_list, entry) { | |
| if (uuid_equal(®->hostid, &ctrl->hostid)) { | |
| if (ignore_key || reg->rkey == le64_to_cpu(d->crkey)) | |
| status = nvmet_pr_update_reg_attr(pr, reg, | |
| nvmet_pr_update_reg_rkey, | |
| &nrkey); | |
| goto free_data; | |
| } | |
| } | |
| if (ignore_key) { | |
| if (!nrkey) { | |
| status = NVME_SC_INVALID_FIELD | NVME_STATUS_DNR; | |
| goto free_data; | |
| } | |
| INIT_LIST_HEAD(&new->entry); | |
| new->rkey = nrkey; | |
| uuid_copy(&new->hostid, &ctrl->hostid); | |
| list_add_tail_rcu(&new->entry, &pr->registrant_list); | |
| status = NVME_SC_SUCCESS; | |
| goto out; | |
| } | |
| free_data: | |
| kfree(new); | |
| out: | |
| up(&pr->pr_sem); | |
| return status; | |
| } | |
| static void nvmet_execute_pr_register(struct nvmet_req *req) | |
| { | |
| u32 cdw10 = le32_to_cpu(req->cmd->common.cdw10); | |
| bool ignore_key = nvmet_pr_parse_ignore_key(cdw10); | |
| struct nvmet_pr_register_data *d; | |
| u8 reg_act = cdw10 & 0x07; /* Reservation Register Action, bit 02:00 */ | |
| u16 status; | |
| d = kmalloc_obj(*d); | |
| if (!d) { | |
| status = NVME_SC_INTERNAL; | |
| goto out; | |
| } | |
| status = nvmet_copy_from_sgl(req, 0, d, sizeof(*d)); | |
| if (status) | |
| goto free_data; | |
| switch (reg_act) { | |
| case NVME_PR_REGISTER_ACT_REG: | |
| status = nvmet_pr_register(req, d); | |
| break; | |
| case NVME_PR_REGISTER_ACT_UNREG: | |
| status = nvmet_pr_unregister(req, d, ignore_key); | |
| break; | |
| case NVME_PR_REGISTER_ACT_REPLACE: | |
| status = nvmet_pr_replace(req, d, ignore_key); | |
| break; | |
| default: | |
| req->error_loc = offsetof(struct nvme_common_command, cdw10); | |
| status = NVME_SC_INVALID_OPCODE | NVME_STATUS_DNR; | |
| break; | |
| } | |
| free_data: | |
| kfree(d); | |
| out: | |
| if (!status) | |
| atomic_inc(&req->ns->pr.generation); | |
| nvmet_req_complete(req, status); | |
| } | |
| static u16 nvmet_pr_acquire(struct nvmet_req *req, | |
| struct nvmet_pr_registrant *reg, | |
| u8 rtype) | |
| { | |
| struct nvmet_pr *pr = &req->ns->pr; | |
| struct nvmet_pr_registrant *holder; | |
| holder = rcu_dereference_protected(pr->holder, 1); | |
| if (holder && reg != holder) | |
| return NVME_SC_RESERVATION_CONFLICT | NVME_STATUS_DNR; | |
| if (holder && reg == holder) { | |
| if (holder->rtype == rtype) | |
| return NVME_SC_SUCCESS; | |
| return NVME_SC_RESERVATION_CONFLICT | NVME_STATUS_DNR; | |
| } | |
| nvmet_pr_set_new_holder(pr, rtype, reg); | |
| return NVME_SC_SUCCESS; | |
| } | |
| static void nvmet_pr_confirm_ns_pc_ref(struct percpu_ref *ref) | |
| { | |
| struct nvmet_pr_per_ctrl_ref *pc_ref = | |
| container_of(ref, struct nvmet_pr_per_ctrl_ref, ref); | |
| complete(&pc_ref->confirm_done); | |
| } | |
| static void nvmet_pr_set_ctrl_to_abort(struct nvmet_req *req, uuid_t *hostid) | |
| { | |
| struct nvmet_pr_per_ctrl_ref *pc_ref; | |
| struct nvmet_ns *ns = req->ns; | |
| unsigned long idx; | |
| xa_for_each(&ns->pr_per_ctrl_refs, idx, pc_ref) { | |
| if (uuid_equal(&pc_ref->hostid, hostid)) { | |
| percpu_ref_kill_and_confirm(&pc_ref->ref, | |
| nvmet_pr_confirm_ns_pc_ref); | |
| wait_for_completion(&pc_ref->confirm_done); | |
| } | |
| } | |
| } | |
| static u16 nvmet_pr_unreg_all_host_by_prkey(struct nvmet_req *req, u64 prkey, | |
| uuid_t *send_hostid, | |
| bool abort) | |
| { | |
| u16 status = NVME_SC_RESERVATION_CONFLICT | NVME_STATUS_DNR; | |
| struct nvmet_pr_registrant *reg, *tmp; | |
| struct nvmet_pr *pr = &req->ns->pr; | |
| uuid_t hostid; | |
| list_for_each_entry_safe(reg, tmp, &pr->registrant_list, entry) { | |
| if (reg->rkey == prkey) { | |
| status = NVME_SC_SUCCESS; | |
| uuid_copy(&hostid, ®->hostid); | |
| if (abort) | |
| nvmet_pr_set_ctrl_to_abort(req, &hostid); | |
| nvmet_pr_unregister_one(pr, reg); | |
| if (!uuid_equal(&hostid, send_hostid)) | |
| nvmet_pr_registration_preempted(pr, &hostid); | |
| } | |
| } | |
| return status; | |
| } | |
| static void nvmet_pr_unreg_all_others_by_prkey(struct nvmet_req *req, | |
| u64 prkey, | |
| uuid_t *send_hostid, | |
| bool abort) | |
| { | |
| struct nvmet_pr_registrant *reg, *tmp; | |
| struct nvmet_pr *pr = &req->ns->pr; | |
| uuid_t hostid; | |
| list_for_each_entry_safe(reg, tmp, &pr->registrant_list, entry) { | |
| if (reg->rkey == prkey && | |
| !uuid_equal(®->hostid, send_hostid)) { | |
| uuid_copy(&hostid, ®->hostid); | |
| if (abort) | |
| nvmet_pr_set_ctrl_to_abort(req, &hostid); | |
| nvmet_pr_unregister_one(pr, reg); | |
| nvmet_pr_registration_preempted(pr, &hostid); | |
| } | |
| } | |
| } | |
| static void nvmet_pr_unreg_all_others(struct nvmet_req *req, | |
| uuid_t *send_hostid, | |
| bool abort) | |
| { | |
| struct nvmet_pr_registrant *reg, *tmp; | |
| struct nvmet_pr *pr = &req->ns->pr; | |
| uuid_t hostid; | |
| list_for_each_entry_safe(reg, tmp, &pr->registrant_list, entry) { | |
| if (!uuid_equal(®->hostid, send_hostid)) { | |
| uuid_copy(&hostid, ®->hostid); | |
| if (abort) | |
| nvmet_pr_set_ctrl_to_abort(req, &hostid); | |
| nvmet_pr_unregister_one(pr, reg); | |
| nvmet_pr_registration_preempted(pr, &hostid); | |
| } | |
| } | |
| } | |
| static void nvmet_pr_update_holder_rtype(struct nvmet_pr_registrant *reg, | |
| void *attr) | |
| { | |
| u8 new_rtype = *(u8 *)attr; | |
| reg->rtype = new_rtype; | |
| } | |
| static u16 nvmet_pr_preempt(struct nvmet_req *req, | |
| struct nvmet_pr_registrant *reg, | |
| u8 rtype, | |
| struct nvmet_pr_acquire_data *d, | |
| bool abort) | |
| { | |
| struct nvmet_ctrl *ctrl = req->sq->ctrl; | |
| struct nvmet_pr *pr = &req->ns->pr; | |
| struct nvmet_pr_registrant *holder; | |
| enum nvme_pr_type original_rtype; | |
| u64 prkey = le64_to_cpu(d->prkey); | |
| u16 status; | |
| holder = rcu_dereference_protected(pr->holder, 1); | |
| if (!holder) | |
| return nvmet_pr_unreg_all_host_by_prkey(req, prkey, | |
| &ctrl->hostid, abort); | |
| original_rtype = holder->rtype; | |
| if (original_rtype == NVME_PR_WRITE_EXCLUSIVE_ALL_REGS || | |
| original_rtype == NVME_PR_EXCLUSIVE_ACCESS_ALL_REGS) { | |
| if (!prkey) { | |
| /* | |
| * To prevent possible access from other hosts, and | |
| * avoid terminate the holder, set the new holder | |
| * first before unregistering. | |
| */ | |
| nvmet_pr_set_new_holder(pr, rtype, reg); | |
| nvmet_pr_unreg_all_others(req, &ctrl->hostid, abort); | |
| return NVME_SC_SUCCESS; | |
| } | |
| return nvmet_pr_unreg_all_host_by_prkey(req, prkey, | |
| &ctrl->hostid, abort); | |
| } | |
| if (holder == reg) { | |
| status = nvmet_pr_update_reg_attr(pr, holder, | |
| nvmet_pr_update_holder_rtype, &rtype); | |
| if (!status && original_rtype != rtype) | |
| nvmet_pr_resv_released(pr, ®->hostid); | |
| return status; | |
| } | |
| if (prkey == holder->rkey) { | |
| /* | |
| * Same as before, set the new holder first. | |
| */ | |
| nvmet_pr_set_new_holder(pr, rtype, reg); | |
| nvmet_pr_unreg_all_others_by_prkey(req, prkey, &ctrl->hostid, | |
| abort); | |
| if (original_rtype != rtype) | |
| nvmet_pr_resv_released(pr, ®->hostid); | |
| return NVME_SC_SUCCESS; | |
| } | |
| if (prkey) | |
| return nvmet_pr_unreg_all_host_by_prkey(req, prkey, | |
| &ctrl->hostid, abort); | |
| return NVME_SC_INVALID_FIELD | NVME_STATUS_DNR; | |
| } | |
| static void nvmet_pr_do_abort(struct work_struct *w) | |
| { | |
| struct nvmet_req *req = container_of(w, struct nvmet_req, r.abort_work); | |
| struct nvmet_pr_per_ctrl_ref *pc_ref; | |
| struct nvmet_ns *ns = req->ns; | |
| unsigned long idx; | |
| /* | |
| * The target does not support abort, just wait per-controller ref to 0. | |
| */ | |
| xa_for_each(&ns->pr_per_ctrl_refs, idx, pc_ref) { | |
| if (percpu_ref_is_dying(&pc_ref->ref)) { | |
| wait_for_completion(&pc_ref->free_done); | |
| reinit_completion(&pc_ref->confirm_done); | |
| reinit_completion(&pc_ref->free_done); | |
| percpu_ref_resurrect(&pc_ref->ref); | |
| } | |
| } | |
| up(&ns->pr.pr_sem); | |
| nvmet_req_complete(req, NVME_SC_SUCCESS); | |
| } | |
| static u16 __nvmet_execute_pr_acquire(struct nvmet_req *req, | |
| struct nvmet_pr_registrant *reg, | |
| u8 acquire_act, | |
| u8 rtype, | |
| struct nvmet_pr_acquire_data *d) | |
| { | |
| u16 status; | |
| switch (acquire_act) { | |
| case NVME_PR_ACQUIRE_ACT_ACQUIRE: | |
| status = nvmet_pr_acquire(req, reg, rtype); | |
| goto out; | |
| case NVME_PR_ACQUIRE_ACT_PREEMPT: | |
| status = nvmet_pr_preempt(req, reg, rtype, d, false); | |
| goto inc_gen; | |
| case NVME_PR_ACQUIRE_ACT_PREEMPT_AND_ABORT: | |
| status = nvmet_pr_preempt(req, reg, rtype, d, true); | |
| goto inc_gen; | |
| default: | |
| req->error_loc = offsetof(struct nvme_common_command, cdw10); | |
| status = NVME_SC_INVALID_OPCODE | NVME_STATUS_DNR; | |
| goto out; | |
| } | |
| inc_gen: | |
| if (!status) | |
| atomic_inc(&req->ns->pr.generation); | |
| out: | |
| return status; | |
| } | |
| static void nvmet_execute_pr_acquire(struct nvmet_req *req) | |
| { | |
| u32 cdw10 = le32_to_cpu(req->cmd->common.cdw10); | |
| bool ignore_key = nvmet_pr_parse_ignore_key(cdw10); | |
| /* Reservation type, bit 15:08 */ | |
| u8 rtype = (u8)((cdw10 >> 8) & 0xff); | |
| /* Reservation acquire action, bit 02:00 */ | |
| u8 acquire_act = cdw10 & 0x07; | |
| struct nvmet_ctrl *ctrl = req->sq->ctrl; | |
| struct nvmet_pr_acquire_data *d = NULL; | |
| struct nvmet_pr *pr = &req->ns->pr; | |
| struct nvmet_pr_registrant *reg; | |
| u16 status = NVME_SC_SUCCESS; | |
| if (ignore_key || | |
| rtype < NVME_PR_WRITE_EXCLUSIVE || | |
| rtype > NVME_PR_EXCLUSIVE_ACCESS_ALL_REGS) { | |
| status = NVME_SC_INVALID_FIELD | NVME_STATUS_DNR; | |
| goto out; | |
| } | |
| d = kmalloc_obj(*d); | |
| if (!d) { | |
| status = NVME_SC_INTERNAL; | |
| goto out; | |
| } | |
| status = nvmet_copy_from_sgl(req, 0, d, sizeof(*d)); | |
| if (status) | |
| goto free_data; | |
| status = NVME_SC_RESERVATION_CONFLICT | NVME_STATUS_DNR; | |
| down(&pr->pr_sem); | |
| list_for_each_entry_rcu(reg, &pr->registrant_list, entry) { | |
| if (uuid_equal(®->hostid, &ctrl->hostid) && | |
| reg->rkey == le64_to_cpu(d->crkey)) { | |
| status = __nvmet_execute_pr_acquire(req, reg, | |
| acquire_act, rtype, d); | |
| break; | |
| } | |
| } | |
| if (!status && acquire_act == NVME_PR_ACQUIRE_ACT_PREEMPT_AND_ABORT) { | |
| kfree(d); | |
| INIT_WORK(&req->r.abort_work, nvmet_pr_do_abort); | |
| queue_work(nvmet_wq, &req->r.abort_work); | |
| return; | |
| } | |
| up(&pr->pr_sem); | |
| free_data: | |
| kfree(d); | |
| out: | |
| nvmet_req_complete(req, status); | |
| } | |
| static u16 nvmet_pr_release(struct nvmet_req *req, | |
| struct nvmet_pr_registrant *reg, | |
| u8 rtype) | |
| { | |
| struct nvmet_pr *pr = &req->ns->pr; | |
| struct nvmet_pr_registrant *holder; | |
| u8 original_rtype; | |
| holder = rcu_dereference_protected(pr->holder, 1); | |
| if (!holder || reg != holder) | |
| return NVME_SC_SUCCESS; | |
| original_rtype = holder->rtype; | |
| if (original_rtype != rtype) | |
| return NVME_SC_RESERVATION_CONFLICT | NVME_STATUS_DNR; | |
| rcu_assign_pointer(pr->holder, NULL); | |
| if (original_rtype != NVME_PR_WRITE_EXCLUSIVE && | |
| original_rtype != NVME_PR_EXCLUSIVE_ACCESS) | |
| nvmet_pr_resv_released(pr, ®->hostid); | |
| return NVME_SC_SUCCESS; | |
| } | |
| static void nvmet_pr_clear(struct nvmet_req *req) | |
| { | |
| struct nvmet_pr_registrant *reg, *tmp; | |
| struct nvmet_pr *pr = &req->ns->pr; | |
| rcu_assign_pointer(pr->holder, NULL); | |
| list_for_each_entry_safe(reg, tmp, &pr->registrant_list, entry) { | |
| list_del_rcu(®->entry); | |
| if (!uuid_equal(&req->sq->ctrl->hostid, ®->hostid)) | |
| nvmet_pr_resv_preempted(pr, ®->hostid); | |
| kfree_rcu(reg, rcu); | |
| } | |
| atomic_inc(&pr->generation); | |
| } | |
| static u16 __nvmet_execute_pr_release(struct nvmet_req *req, | |
| struct nvmet_pr_registrant *reg, | |
| u8 release_act, u8 rtype) | |
| { | |
| switch (release_act) { | |
| case NVME_PR_RELEASE_ACT_RELEASE: | |
| return nvmet_pr_release(req, reg, rtype); | |
| case NVME_PR_RELEASE_ACT_CLEAR: | |
| nvmet_pr_clear(req); | |
| return NVME_SC_SUCCESS; | |
| default: | |
| req->error_loc = offsetof(struct nvme_common_command, cdw10); | |
| return NVME_SC_INVALID_OPCODE | NVME_STATUS_DNR; | |
| } | |
| } | |
| static void nvmet_execute_pr_release(struct nvmet_req *req) | |
| { | |
| u32 cdw10 = le32_to_cpu(req->cmd->common.cdw10); | |
| bool ignore_key = nvmet_pr_parse_ignore_key(cdw10); | |
| u8 rtype = (u8)((cdw10 >> 8) & 0xff); /* Reservation type, bit 15:08 */ | |
| u8 release_act = cdw10 & 0x07; /* Reservation release action, bit 02:00 */ | |
| struct nvmet_ctrl *ctrl = req->sq->ctrl; | |
| struct nvmet_pr *pr = &req->ns->pr; | |
| struct nvmet_pr_release_data *d; | |
| struct nvmet_pr_registrant *reg; | |
| u16 status; | |
| if (ignore_key) { | |
| status = NVME_SC_INVALID_FIELD | NVME_STATUS_DNR; | |
| goto out; | |
| } | |
| d = kmalloc_obj(*d); | |
| if (!d) { | |
| status = NVME_SC_INTERNAL; | |
| goto out; | |
| } | |
| status = nvmet_copy_from_sgl(req, 0, d, sizeof(*d)); | |
| if (status) | |
| goto free_data; | |
| status = NVME_SC_RESERVATION_CONFLICT | NVME_STATUS_DNR; | |
| down(&pr->pr_sem); | |
| list_for_each_entry_rcu(reg, &pr->registrant_list, entry) { | |
| if (uuid_equal(®->hostid, &ctrl->hostid) && | |
| reg->rkey == le64_to_cpu(d->crkey)) { | |
| status = __nvmet_execute_pr_release(req, reg, | |
| release_act, rtype); | |
| break; | |
| } | |
| } | |
| up(&pr->pr_sem); | |
| free_data: | |
| kfree(d); | |
| out: | |
| nvmet_req_complete(req, status); | |
| } | |
| static void nvmet_execute_pr_report(struct nvmet_req *req) | |
| { | |
| u32 cdw11 = le32_to_cpu(req->cmd->common.cdw11); | |
| u32 cdw10 = le32_to_cpu(req->cmd->common.cdw10); | |
| u32 num_bytes = 4 * (cdw10 + 1); /* cdw10 is number of dwords */ | |
| u8 eds = cdw11 & 1; /* Extended data structure, bit 00 */ | |
| struct nvme_registered_ctrl_ext *ctrl_eds; | |
| struct nvme_reservation_status_ext *data; | |
| struct nvmet_pr *pr = &req->ns->pr; | |
| struct nvmet_pr_registrant *holder; | |
| struct nvmet_pr_registrant *reg; | |
| u16 num_ctrls = 0; | |
| u16 status; | |
| u8 rtype; | |
| /* nvmet hostid(uuid_t) is 128 bit. */ | |
| if (!eds) { | |
| req->error_loc = offsetof(struct nvme_common_command, cdw11); | |
| status = NVME_SC_HOST_ID_INCONSIST | NVME_STATUS_DNR; | |
| goto out; | |
| } | |
| if (num_bytes < sizeof(struct nvme_reservation_status_ext)) { | |
| req->error_loc = offsetof(struct nvme_common_command, cdw10); | |
| status = NVME_SC_INVALID_FIELD | NVME_STATUS_DNR; | |
| goto out; | |
| } | |
| data = kzalloc(num_bytes, GFP_KERNEL); | |
| if (!data) { | |
| status = NVME_SC_INTERNAL; | |
| goto out; | |
| } | |
| data->gen = cpu_to_le32(atomic_read(&pr->generation)); | |
| data->ptpls = 0; | |
| ctrl_eds = data->regctl_eds; | |
| rcu_read_lock(); | |
| holder = rcu_dereference(pr->holder); | |
| rtype = holder ? holder->rtype : 0; | |
| data->rtype = rtype; | |
| list_for_each_entry_rcu(reg, &pr->registrant_list, entry) { | |
| num_ctrls++; | |
| /* | |
| * continue to get the number of all registrans. | |
| */ | |
| if (((void *)ctrl_eds + sizeof(*ctrl_eds)) > | |
| ((void *)data + num_bytes)) | |
| continue; | |
| /* | |
| * Dynamic controller, set cntlid to 0xffff. | |
| */ | |
| ctrl_eds->cntlid = cpu_to_le16(NVME_CNTLID_DYNAMIC); | |
| if (rtype == NVME_PR_WRITE_EXCLUSIVE_ALL_REGS || | |
| rtype == NVME_PR_EXCLUSIVE_ACCESS_ALL_REGS) | |
| ctrl_eds->rcsts = 1; | |
| if (reg == holder) | |
| ctrl_eds->rcsts = 1; | |
| uuid_copy((uuid_t *)&ctrl_eds->hostid, ®->hostid); | |
| ctrl_eds->rkey = cpu_to_le64(reg->rkey); | |
| ctrl_eds++; | |
| } | |
| rcu_read_unlock(); | |
| put_unaligned_le16(num_ctrls, data->regctl); | |
| status = nvmet_copy_to_sgl(req, 0, data, num_bytes); | |
| kfree(data); | |
| out: | |
| nvmet_req_complete(req, status); | |
| } | |
| u16 nvmet_parse_pr_cmd(struct nvmet_req *req) | |
| { | |
| struct nvme_command *cmd = req->cmd; | |
| switch (cmd->common.opcode) { | |
| case nvme_cmd_resv_register: | |
| req->execute = nvmet_execute_pr_register; | |
| break; | |
| case nvme_cmd_resv_acquire: | |
| req->execute = nvmet_execute_pr_acquire; | |
| break; | |
| case nvme_cmd_resv_release: | |
| req->execute = nvmet_execute_pr_release; | |
| break; | |
| case nvme_cmd_resv_report: | |
| req->execute = nvmet_execute_pr_report; | |
| break; | |
| default: | |
| return 1; | |
| } | |
| return NVME_SC_SUCCESS; | |
| } | |
| static bool nvmet_is_req_write_cmd_group(struct nvmet_req *req) | |
| { | |
| u8 opcode = req->cmd->common.opcode; | |
| if (req->sq->qid) { | |
| switch (opcode) { | |
| case nvme_cmd_flush: | |
| case nvme_cmd_write: | |
| case nvme_cmd_write_zeroes: | |
| case nvme_cmd_dsm: | |
| case nvme_cmd_zone_append: | |
| case nvme_cmd_zone_mgmt_send: | |
| return true; | |
| default: | |
| return false; | |
| } | |
| } | |
| return false; | |
| } | |
| static bool nvmet_is_req_read_cmd_group(struct nvmet_req *req) | |
| { | |
| u8 opcode = req->cmd->common.opcode; | |
| if (req->sq->qid) { | |
| switch (opcode) { | |
| case nvme_cmd_read: | |
| case nvme_cmd_zone_mgmt_recv: | |
| return true; | |
| default: | |
| return false; | |
| } | |
| } | |
| return false; | |
| } | |
| u16 nvmet_pr_check_cmd_access(struct nvmet_req *req) | |
| { | |
| struct nvmet_ctrl *ctrl = req->sq->ctrl; | |
| struct nvmet_pr_registrant *holder; | |
| struct nvmet_ns *ns = req->ns; | |
| struct nvmet_pr *pr = &ns->pr; | |
| u16 status = NVME_SC_SUCCESS; | |
| rcu_read_lock(); | |
| holder = rcu_dereference(pr->holder); | |
| if (!holder) | |
| goto unlock; | |
| if (uuid_equal(&ctrl->hostid, &holder->hostid)) | |
| goto unlock; | |
| /* | |
| * The Reservation command group is checked in executing, | |
| * allow it here. | |
| */ | |
| switch (holder->rtype) { | |
| case NVME_PR_WRITE_EXCLUSIVE: | |
| if (nvmet_is_req_write_cmd_group(req)) | |
| status = NVME_SC_RESERVATION_CONFLICT | NVME_STATUS_DNR; | |
| break; | |
| case NVME_PR_EXCLUSIVE_ACCESS: | |
| if (nvmet_is_req_read_cmd_group(req) || | |
| nvmet_is_req_write_cmd_group(req)) | |
| status = NVME_SC_RESERVATION_CONFLICT | NVME_STATUS_DNR; | |
| break; | |
| case NVME_PR_WRITE_EXCLUSIVE_REG_ONLY: | |
| case NVME_PR_WRITE_EXCLUSIVE_ALL_REGS: | |
| if ((nvmet_is_req_write_cmd_group(req)) && | |
| !nvmet_pr_find_registrant(pr, &ctrl->hostid)) | |
| status = NVME_SC_RESERVATION_CONFLICT | NVME_STATUS_DNR; | |
| break; | |
| case NVME_PR_EXCLUSIVE_ACCESS_REG_ONLY: | |
| case NVME_PR_EXCLUSIVE_ACCESS_ALL_REGS: | |
| if ((nvmet_is_req_read_cmd_group(req) || | |
| nvmet_is_req_write_cmd_group(req)) && | |
| !nvmet_pr_find_registrant(pr, &ctrl->hostid)) | |
| status = NVME_SC_RESERVATION_CONFLICT | NVME_STATUS_DNR; | |
| break; | |
| default: | |
| pr_warn("the reservation type is set wrong, type:%d\n", | |
| holder->rtype); | |
| break; | |
| } | |
| unlock: | |
| rcu_read_unlock(); | |
| if (status) | |
| req->error_loc = offsetof(struct nvme_common_command, opcode); | |
| return status; | |
| } | |
| u16 nvmet_pr_get_ns_pc_ref(struct nvmet_req *req) | |
| { | |
| struct nvmet_pr_per_ctrl_ref *pc_ref; | |
| pc_ref = xa_load(&req->ns->pr_per_ctrl_refs, | |
| req->sq->ctrl->cntlid); | |
| if (unlikely(!percpu_ref_tryget_live(&pc_ref->ref))) | |
| return NVME_SC_INTERNAL; | |
| req->pc_ref = pc_ref; | |
| return NVME_SC_SUCCESS; | |
| } | |
| static void nvmet_pr_ctrl_ns_all_cmds_done(struct percpu_ref *ref) | |
| { | |
| struct nvmet_pr_per_ctrl_ref *pc_ref = | |
| container_of(ref, struct nvmet_pr_per_ctrl_ref, ref); | |
| complete(&pc_ref->free_done); | |
| } | |
| static int nvmet_pr_alloc_and_insert_pc_ref(struct nvmet_ns *ns, | |
| unsigned long idx, | |
| uuid_t *hostid) | |
| { | |
| struct nvmet_pr_per_ctrl_ref *pc_ref; | |
| int ret; | |
| pc_ref = kmalloc_obj(*pc_ref, GFP_ATOMIC); | |
| if (!pc_ref) | |
| return -ENOMEM; | |
| ret = percpu_ref_init(&pc_ref->ref, nvmet_pr_ctrl_ns_all_cmds_done, | |
| PERCPU_REF_ALLOW_REINIT, GFP_KERNEL); | |
| if (ret) | |
| goto free; | |
| init_completion(&pc_ref->free_done); | |
| init_completion(&pc_ref->confirm_done); | |
| uuid_copy(&pc_ref->hostid, hostid); | |
| ret = xa_insert(&ns->pr_per_ctrl_refs, idx, pc_ref, GFP_KERNEL); | |
| if (ret) | |
| goto exit; | |
| return ret; | |
| exit: | |
| percpu_ref_exit(&pc_ref->ref); | |
| free: | |
| kfree(pc_ref); | |
| return ret; | |
| } | |
| int nvmet_ctrl_init_pr(struct nvmet_ctrl *ctrl) | |
| { | |
| struct nvmet_subsys *subsys = ctrl->subsys; | |
| struct nvmet_pr_per_ctrl_ref *pc_ref; | |
| struct nvmet_ns *ns = NULL; | |
| unsigned long idx; | |
| int ret; | |
| ctrl->pr_log_mgr.counter = 0; | |
| ctrl->pr_log_mgr.lost_count = 0; | |
| mutex_init(&ctrl->pr_log_mgr.lock); | |
| INIT_KFIFO(ctrl->pr_log_mgr.log_queue); | |
| /* | |
| * Here we are under subsys lock, if an ns not in subsys->namespaces, | |
| * we can make sure that ns is not enabled, and not call | |
| * nvmet_pr_init_ns(), see more details in nvmet_ns_enable(). | |
| * So just check ns->pr.enable. | |
| */ | |
| nvmet_for_each_enabled_ns(&subsys->namespaces, idx, ns) { | |
| if (ns->pr.enable) { | |
| ret = nvmet_pr_alloc_and_insert_pc_ref(ns, ctrl->cntlid, | |
| &ctrl->hostid); | |
| if (ret) | |
| goto free_per_ctrl_refs; | |
| } | |
| } | |
| return 0; | |
| free_per_ctrl_refs: | |
| nvmet_for_each_enabled_ns(&subsys->namespaces, idx, ns) { | |
| if (ns->pr.enable) { | |
| pc_ref = xa_erase(&ns->pr_per_ctrl_refs, ctrl->cntlid); | |
| if (pc_ref) | |
| percpu_ref_exit(&pc_ref->ref); | |
| kfree(pc_ref); | |
| } | |
| } | |
| return ret; | |
| } | |
| void nvmet_ctrl_destroy_pr(struct nvmet_ctrl *ctrl) | |
| { | |
| struct nvmet_pr_per_ctrl_ref *pc_ref; | |
| struct nvmet_ns *ns; | |
| unsigned long idx; | |
| kfifo_free(&ctrl->pr_log_mgr.log_queue); | |
| mutex_destroy(&ctrl->pr_log_mgr.lock); | |
| nvmet_for_each_enabled_ns(&ctrl->subsys->namespaces, idx, ns) { | |
| if (ns->pr.enable) { | |
| pc_ref = xa_erase(&ns->pr_per_ctrl_refs, ctrl->cntlid); | |
| if (pc_ref) | |
| percpu_ref_exit(&pc_ref->ref); | |
| kfree(pc_ref); | |
| } | |
| } | |
| } | |
| int nvmet_pr_init_ns(struct nvmet_ns *ns) | |
| { | |
| struct nvmet_subsys *subsys = ns->subsys; | |
| struct nvmet_pr_per_ctrl_ref *pc_ref; | |
| struct nvmet_ctrl *ctrl = NULL; | |
| unsigned long idx; | |
| int ret; | |
| ns->pr.holder = NULL; | |
| atomic_set(&ns->pr.generation, 0); | |
| sema_init(&ns->pr.pr_sem, 1); | |
| INIT_LIST_HEAD(&ns->pr.registrant_list); | |
| ns->pr.notify_mask = 0; | |
| xa_init(&ns->pr_per_ctrl_refs); | |
| list_for_each_entry(ctrl, &subsys->ctrls, subsys_entry) { | |
| ret = nvmet_pr_alloc_and_insert_pc_ref(ns, ctrl->cntlid, | |
| &ctrl->hostid); | |
| if (ret) | |
| goto free_per_ctrl_refs; | |
| } | |
| return 0; | |
| free_per_ctrl_refs: | |
| xa_for_each(&ns->pr_per_ctrl_refs, idx, pc_ref) { | |
| xa_erase(&ns->pr_per_ctrl_refs, idx); | |
| percpu_ref_exit(&pc_ref->ref); | |
| kfree(pc_ref); | |
| } | |
| return ret; | |
| } | |
| void nvmet_pr_exit_ns(struct nvmet_ns *ns) | |
| { | |
| struct nvmet_pr_registrant *reg, *tmp; | |
| struct nvmet_pr_per_ctrl_ref *pc_ref; | |
| struct nvmet_pr *pr = &ns->pr; | |
| unsigned long idx; | |
| list_for_each_entry_safe(reg, tmp, &pr->registrant_list, entry) { | |
| list_del(®->entry); | |
| kfree(reg); | |
| } | |
| xa_for_each(&ns->pr_per_ctrl_refs, idx, pc_ref) { | |
| /* | |
| * No command on ns here, we can safely free pc_ref. | |
| */ | |
| pc_ref = xa_erase(&ns->pr_per_ctrl_refs, idx); | |
| percpu_ref_exit(&pc_ref->ref); | |
| kfree(pc_ref); | |
| } | |
| xa_destroy(&ns->pr_per_ctrl_refs); | |
| } |