/* * Copyright 2023 Red Hat Inc. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include #include "nvrm/rpcfn.h" #define GSP_MSG_MIN_SIZE GSP_PAGE_SIZE #define GSP_MSG_MAX_SIZE (GSP_MSG_MIN_SIZE * 16) /** * DOC: GSP message queue element * * https://github.com/NVIDIA/open-gpu-kernel-modules/blob/535/src/nvidia/inc/kernel/gpu/gsp/message_queue_priv.h * * The GSP command queue and status queue are message queues for the * communication between software and GSP. The software submits the GSP * RPC via the GSP command queue, GSP writes the status of the submitted * RPC in the status queue. * * A GSP message queue element consists of three parts: * * - message element header (struct r535_gsp_msg), which mostly maintains * the metadata for queuing the element. * * - RPC message header (struct nvfw_gsp_rpc), which maintains the info * of the RPC. E.g., the RPC function number. * * - The payload, where the RPC message stays. E.g. the params of a * specific RPC function. Some RPC functions also have their headers * in the payload. E.g. rm_alloc, rm_control. * * The memory layout of a GSP message element can be illustrated below:: * * +------------------------+ * | Message Element Header | * | (r535_gsp_msg) | * | | * | (r535_gsp_msg.data) | * | | | * |----------V-------------| * | GSP RPC Header | * | (nvfw_gsp_rpc) | * | | * | (nvfw_gsp_rpc.data) | * | | | * |----------V-------------| * | Payload | * | | * | header(optional) | * | params | * +------------------------+ * * The max size of a message queue element is 16 pages (including the * headers). When a GSP message to be sent is larger than 16 pages, the * message should be split into multiple elements and sent accordingly. * * In the bunch of the split elements, the first element has the expected * function number, while the rest of the elements are sent with the * function number NV_VGPU_MSG_FUNCTION_CONTINUATION_RECORD. * * GSP consumes the elements from the cmdq and always writes the result * back to the msgq. The result is also formed as split elements. * * Terminology: * * - gsp_msg(msg): GSP message element (element header + GSP RPC header + * payload) * - gsp_rpc(rpc): GSP RPC (RPC header + payload) * - gsp_rpc_buf: buffer for (GSP RPC header + payload) * - gsp_rpc_len: size of (GSP RPC header + payload) * - params_size: size of params in the payload * - payload_size: size of (header if exists + params) in the payload */ struct r535_gsp_msg { u8 auth_tag_buffer[16]; u8 aad_buffer[16]; u32 checksum; u32 sequence; u32 elem_count; u32 pad; u8 data[]; }; struct nvfw_gsp_rpc { u32 header_version; u32 signature; u32 length; u32 function; u32 rpc_result; u32 rpc_result_private; u32 sequence; union { u32 spare; u32 cpuRmGfid; }; u8 data[]; }; #define GSP_MSG_HDR_SIZE offsetof(struct r535_gsp_msg, data) #define to_gsp_hdr(p, header) \ container_of((void *)p, typeof(*header), data) #define to_payload_hdr(p, header) \ container_of((void *)p, typeof(*header), params) int r535_rpc_status_to_errno(uint32_t rpc_status) { switch (rpc_status) { case 0x55: /* NV_ERR_NOT_READY */ case 0x66: /* NV_ERR_TIMEOUT_RETRY */ return -EBUSY; case 0x51: /* NV_ERR_NO_MEMORY */ return -ENOMEM; default: return -EINVAL; } } static int r535_gsp_msgq_wait(struct nvkm_gsp *gsp, u32 gsp_rpc_len, int *ptime) { u32 size, rptr = *gsp->msgq.rptr; int used; size = DIV_ROUND_UP(GSP_MSG_HDR_SIZE + gsp_rpc_len, GSP_PAGE_SIZE); if (WARN_ON(!size || size >= gsp->msgq.cnt)) return -EINVAL; do { u32 wptr = *gsp->msgq.wptr; used = wptr + gsp->msgq.cnt - rptr; if (used >= gsp->msgq.cnt) used -= gsp->msgq.cnt; if (used >= size) break; usleep_range(1, 2); } while (--(*ptime)); if (WARN_ON(!*ptime)) return -ETIMEDOUT; return used; } static struct r535_gsp_msg * r535_gsp_msgq_get_entry(struct nvkm_gsp *gsp) { u32 rptr = *gsp->msgq.rptr; /* Skip the first page, which is the message queue info */ return (void *)((u8 *)gsp->shm.msgq.ptr + GSP_PAGE_SIZE + rptr * GSP_PAGE_SIZE); } /** * DOC: Receive a GSP message queue element * * Receiving a GSP message queue element from the message queue consists of * the following steps: * * - Peek the element from the queue: r535_gsp_msgq_peek(). * Peek the first page of the element to determine the total size of the * message before allocating the proper memory. * * - Allocate memory for the message. * Once the total size of the message is determined from the GSP message * queue element, the caller of r535_gsp_msgq_recv() allocates the * required memory. * * - Receive the message: r535_gsp_msgq_recv(). * Copy the message into the allocated memory. Advance the read pointer. * If the message is a large GSP message, r535_gsp_msgq_recv() calls * r535_gsp_msgq_recv_one_elem() repeatedly to receive continuation parts * until the complete message is received. * r535_gsp_msgq_recv() assembles the payloads of cotinuation parts into * the return of the large GSP message. * * - Free the allocated memory: r535_gsp_msg_done(). * The user is responsible for freeing the memory allocated for the GSP * message pages after they have been processed. */ static void * r535_gsp_msgq_peek(struct nvkm_gsp *gsp, u32 gsp_rpc_len, int *retries) { struct r535_gsp_msg *mqe; int ret; ret = r535_gsp_msgq_wait(gsp, gsp_rpc_len, retries); if (ret < 0) return ERR_PTR(ret); mqe = r535_gsp_msgq_get_entry(gsp); return mqe->data; } struct r535_gsp_msg_info { int *retries; u32 gsp_rpc_len; void *gsp_rpc_buf; bool continuation; }; static void r535_gsp_msg_dump(struct nvkm_gsp *gsp, struct nvfw_gsp_rpc *msg, int lvl); static void * r535_gsp_msgq_recv_one_elem(struct nvkm_gsp *gsp, struct r535_gsp_msg_info *info) { u8 *buf = info->gsp_rpc_buf; u32 rptr = *gsp->msgq.rptr; struct r535_gsp_msg *mqe; u32 size, expected, len; int ret; expected = info->gsp_rpc_len; ret = r535_gsp_msgq_wait(gsp, expected, info->retries); if (ret < 0) return ERR_PTR(ret); mqe = r535_gsp_msgq_get_entry(gsp); if (info->continuation) { struct nvfw_gsp_rpc *rpc = (struct nvfw_gsp_rpc *)mqe->data; if (rpc->function != NV_VGPU_MSG_FUNCTION_CONTINUATION_RECORD) { nvkm_error(&gsp->subdev, "Not a continuation of a large RPC\n"); r535_gsp_msg_dump(gsp, rpc, NV_DBG_ERROR); return ERR_PTR(-EIO); } } size = ALIGN(expected + GSP_MSG_HDR_SIZE, GSP_PAGE_SIZE); len = ((gsp->msgq.cnt - rptr) * GSP_PAGE_SIZE) - sizeof(*mqe); len = min_t(u32, expected, len); if (info->continuation) memcpy(buf, mqe->data + sizeof(struct nvfw_gsp_rpc), len - sizeof(struct nvfw_gsp_rpc)); else memcpy(buf, mqe->data, len); expected -= len; if (expected) { mqe = (void *)((u8 *)gsp->shm.msgq.ptr + 0x1000 + 0 * 0x1000); memcpy(buf + len, mqe, expected); } rptr = (rptr + DIV_ROUND_UP(size, GSP_PAGE_SIZE)) % gsp->msgq.cnt; mb(); (*gsp->msgq.rptr) = rptr; return buf; } static void * r535_gsp_msgq_recv(struct nvkm_gsp *gsp, u32 gsp_rpc_len, int *retries) { struct r535_gsp_msg *mqe; const u32 max_rpc_size = GSP_MSG_MAX_SIZE - sizeof(*mqe); struct nvfw_gsp_rpc *rpc; struct r535_gsp_msg_info info = {0}; u32 expected = gsp_rpc_len; void *buf; mqe = r535_gsp_msgq_get_entry(gsp); rpc = (struct nvfw_gsp_rpc *)mqe->data; if (WARN_ON(rpc->length > max_rpc_size)) return NULL; buf = kvmalloc(max_t(u32, rpc->length, expected), GFP_KERNEL); if (!buf) return ERR_PTR(-ENOMEM); info.gsp_rpc_buf = buf; info.retries = retries; info.gsp_rpc_len = rpc->length; buf = r535_gsp_msgq_recv_one_elem(gsp, &info); if (IS_ERR(buf)) { kvfree(info.gsp_rpc_buf); info.gsp_rpc_buf = NULL; return buf; } if (expected <= max_rpc_size) return buf; info.gsp_rpc_buf += info.gsp_rpc_len; expected -= info.gsp_rpc_len; while (expected) { u32 size; rpc = r535_gsp_msgq_peek(gsp, sizeof(*rpc), info.retries); if (IS_ERR_OR_NULL(rpc)) { kfree(buf); return rpc; } info.gsp_rpc_len = rpc->length; info.continuation = true; rpc = r535_gsp_msgq_recv_one_elem(gsp, &info); if (IS_ERR_OR_NULL(rpc)) { kfree(buf); return rpc; } size = info.gsp_rpc_len - sizeof(*rpc); expected -= size; info.gsp_rpc_buf += size; } rpc = buf; rpc->length = gsp_rpc_len; return buf; } static int r535_gsp_cmdq_push(struct nvkm_gsp *gsp, void *rpc) { struct r535_gsp_msg *msg = to_gsp_hdr(rpc, msg); struct r535_gsp_msg *cqe; u32 gsp_rpc_len = msg->checksum; u64 *ptr = (void *)msg; u64 *end; u64 csum = 0; int free, time = 1000000; u32 wptr, size, step, len; u32 off = 0; len = ALIGN(GSP_MSG_HDR_SIZE + gsp_rpc_len, GSP_PAGE_SIZE); end = (u64 *)((char *)ptr + len); msg->pad = 0; msg->checksum = 0; msg->sequence = gsp->cmdq.seq++; msg->elem_count = DIV_ROUND_UP(len, 0x1000); while (ptr < end) csum ^= *ptr++; msg->checksum = upper_32_bits(csum) ^ lower_32_bits(csum); wptr = *gsp->cmdq.wptr; do { do { free = *gsp->cmdq.rptr + gsp->cmdq.cnt - wptr - 1; if (free >= gsp->cmdq.cnt) free -= gsp->cmdq.cnt; if (free >= 1) break; usleep_range(1, 2); } while(--time); if (WARN_ON(!time)) { kvfree(msg); return -ETIMEDOUT; } cqe = (void *)((u8 *)gsp->shm.cmdq.ptr + 0x1000 + wptr * 0x1000); step = min_t(u32, free, (gsp->cmdq.cnt - wptr)); size = min_t(u32, len, step * GSP_PAGE_SIZE); memcpy(cqe, (u8 *)msg + off, size); wptr += DIV_ROUND_UP(size, 0x1000); if (wptr == gsp->cmdq.cnt) wptr = 0; off += size; len -= size; } while (len); nvkm_trace(&gsp->subdev, "cmdq: wptr %d\n", wptr); wmb(); (*gsp->cmdq.wptr) = wptr; mb(); nvkm_falcon_wr32(&gsp->falcon, 0xc00, 0x00000000); kvfree(msg); return 0; } static void * r535_gsp_cmdq_get(struct nvkm_gsp *gsp, u32 gsp_rpc_len) { struct r535_gsp_msg *msg; u32 size = GSP_MSG_HDR_SIZE + gsp_rpc_len; size = ALIGN(size, GSP_MSG_MIN_SIZE); msg = kvzalloc(size, GFP_KERNEL); if (!msg) return ERR_PTR(-ENOMEM); msg->checksum = gsp_rpc_len; return msg->data; } static void r535_gsp_msg_done(struct nvkm_gsp *gsp, struct nvfw_gsp_rpc *msg) { kvfree(msg); } static void r535_gsp_msg_dump(struct nvkm_gsp *gsp, struct nvfw_gsp_rpc *msg, int lvl) { if (gsp->subdev.debug >= lvl) { nvkm_printk__(&gsp->subdev, lvl, info, "msg fn:%d len:0x%x/0x%zx res:0x%x resp:0x%x\n", msg->function, msg->length, msg->length - sizeof(*msg), msg->rpc_result, msg->rpc_result_private); print_hex_dump(KERN_INFO, "msg: ", DUMP_PREFIX_OFFSET, 16, 1, msg->data, msg->length - sizeof(*msg), true); } } struct nvfw_gsp_rpc * r535_gsp_msg_recv(struct nvkm_gsp *gsp, int fn, u32 gsp_rpc_len) { struct nvkm_subdev *subdev = &gsp->subdev; struct nvfw_gsp_rpc *rpc; int retries = 4000000, i; retry: rpc = r535_gsp_msgq_peek(gsp, sizeof(*rpc), &retries); if (IS_ERR_OR_NULL(rpc)) return rpc; rpc = r535_gsp_msgq_recv(gsp, gsp_rpc_len, &retries); if (IS_ERR_OR_NULL(rpc)) return rpc; if (rpc->rpc_result) { r535_gsp_msg_dump(gsp, rpc, NV_DBG_ERROR); r535_gsp_msg_done(gsp, rpc); return ERR_PTR(-EINVAL); } r535_gsp_msg_dump(gsp, rpc, NV_DBG_TRACE); if (fn && rpc->function == fn) { if (gsp_rpc_len) { if (rpc->length < gsp_rpc_len) { nvkm_error(subdev, "rpc len %d < %d\n", rpc->length, gsp_rpc_len); r535_gsp_msg_dump(gsp, rpc, NV_DBG_ERROR); r535_gsp_msg_done(gsp, rpc); return ERR_PTR(-EIO); } return rpc; } r535_gsp_msg_done(gsp, rpc); return NULL; } for (i = 0; i < gsp->msgq.ntfy_nr; i++) { struct nvkm_gsp_msgq_ntfy *ntfy = &gsp->msgq.ntfy[i]; if (ntfy->fn == rpc->function) { if (ntfy->func) ntfy->func(ntfy->priv, ntfy->fn, rpc->data, rpc->length - sizeof(*rpc)); break; } } if (i == gsp->msgq.ntfy_nr) r535_gsp_msg_dump(gsp, rpc, NV_DBG_WARN); r535_gsp_msg_done(gsp, rpc); if (fn) goto retry; if (*gsp->msgq.rptr != *gsp->msgq.wptr) goto retry; return NULL; } int r535_gsp_msg_ntfy_add(struct nvkm_gsp *gsp, u32 fn, nvkm_gsp_msg_ntfy_func func, void *priv) { int ret = 0; mutex_lock(&gsp->msgq.mutex); if (WARN_ON(gsp->msgq.ntfy_nr >= ARRAY_SIZE(gsp->msgq.ntfy))) { ret = -ENOSPC; } else { gsp->msgq.ntfy[gsp->msgq.ntfy_nr].fn = fn; gsp->msgq.ntfy[gsp->msgq.ntfy_nr].func = func; gsp->msgq.ntfy[gsp->msgq.ntfy_nr].priv = priv; gsp->msgq.ntfy_nr++; } mutex_unlock(&gsp->msgq.mutex); return ret; } int r535_gsp_rpc_poll(struct nvkm_gsp *gsp, u32 fn) { void *repv; mutex_lock(&gsp->cmdq.mutex); repv = r535_gsp_msg_recv(gsp, fn, 0); mutex_unlock(&gsp->cmdq.mutex); if (IS_ERR(repv)) return PTR_ERR(repv); return 0; } static void * r535_gsp_rpc_handle_reply(struct nvkm_gsp *gsp, u32 fn, enum nvkm_gsp_rpc_reply_policy policy, u32 gsp_rpc_len) { struct nvfw_gsp_rpc *reply; void *repv = NULL; switch (policy) { case NVKM_GSP_RPC_REPLY_NOWAIT: break; case NVKM_GSP_RPC_REPLY_RECV: reply = r535_gsp_msg_recv(gsp, fn, gsp_rpc_len); if (!IS_ERR_OR_NULL(reply)) repv = reply->data; else repv = reply; break; case NVKM_GSP_RPC_REPLY_POLL: repv = r535_gsp_msg_recv(gsp, fn, 0); break; } return repv; } static void * r535_gsp_rpc_send(struct nvkm_gsp *gsp, void *payload, enum nvkm_gsp_rpc_reply_policy policy, u32 gsp_rpc_len) { struct nvfw_gsp_rpc *rpc = to_gsp_hdr(payload, rpc); u32 fn = rpc->function; int ret; if (gsp->subdev.debug >= NV_DBG_TRACE) { nvkm_trace(&gsp->subdev, "rpc fn:%d len:0x%x/0x%zx\n", rpc->function, rpc->length, rpc->length - sizeof(*rpc)); print_hex_dump(KERN_INFO, "rpc: ", DUMP_PREFIX_OFFSET, 16, 1, rpc->data, rpc->length - sizeof(*rpc), true); } ret = r535_gsp_cmdq_push(gsp, rpc); if (ret) return ERR_PTR(ret); return r535_gsp_rpc_handle_reply(gsp, fn, policy, gsp_rpc_len); } static void r535_gsp_rpc_done(struct nvkm_gsp *gsp, void *repv) { struct nvfw_gsp_rpc *rpc = container_of(repv, typeof(*rpc), data); r535_gsp_msg_done(gsp, rpc); } static void * r535_gsp_rpc_get(struct nvkm_gsp *gsp, u32 fn, u32 payload_size) { struct nvfw_gsp_rpc *rpc; rpc = r535_gsp_cmdq_get(gsp, ALIGN(sizeof(*rpc) + payload_size, sizeof(u64))); if (IS_ERR(rpc)) return ERR_CAST(rpc); rpc->header_version = 0x03000000; rpc->signature = ('C' << 24) | ('P' << 16) | ('R' << 8) | 'V'; rpc->function = fn; rpc->rpc_result = 0xffffffff; rpc->rpc_result_private = 0xffffffff; rpc->length = sizeof(*rpc) + payload_size; return rpc->data; } static void * r535_gsp_rpc_push(struct nvkm_gsp *gsp, void *payload, enum nvkm_gsp_rpc_reply_policy policy, u32 gsp_rpc_len) { struct nvfw_gsp_rpc *rpc = to_gsp_hdr(payload, rpc); struct r535_gsp_msg *msg = to_gsp_hdr(rpc, msg); const u32 max_rpc_size = GSP_MSG_MAX_SIZE - sizeof(*msg); const u32 max_payload_size = max_rpc_size - sizeof(*rpc); u32 payload_size = rpc->length - sizeof(*rpc); void *repv; mutex_lock(&gsp->cmdq.mutex); if (payload_size > max_payload_size) { const u32 fn = rpc->function; u32 remain_payload_size = payload_size; void *next; /* Send initial RPC. */ next = r535_gsp_rpc_get(gsp, fn, max_payload_size); if (IS_ERR(next)) { repv = next; goto done; } memcpy(next, payload, max_payload_size); repv = r535_gsp_rpc_send(gsp, next, NVKM_GSP_RPC_REPLY_NOWAIT, 0); if (IS_ERR(repv)) goto done; payload += max_payload_size; remain_payload_size -= max_payload_size; /* Remaining chunks sent as CONTINUATION_RECORD RPCs. */ while (remain_payload_size) { u32 size = min(remain_payload_size, max_payload_size); next = r535_gsp_rpc_get(gsp, NV_VGPU_MSG_FUNCTION_CONTINUATION_RECORD, size); if (IS_ERR(next)) { repv = next; goto done; } memcpy(next, payload, size); repv = r535_gsp_rpc_send(gsp, next, NVKM_GSP_RPC_REPLY_NOWAIT, 0); if (IS_ERR(repv)) goto done; payload += size; remain_payload_size -= size; } /* Wait for reply. */ repv = r535_gsp_rpc_handle_reply(gsp, fn, policy, payload_size + sizeof(*rpc)); if (!IS_ERR(repv)) kvfree(msg); } else { repv = r535_gsp_rpc_send(gsp, payload, policy, gsp_rpc_len); } done: mutex_unlock(&gsp->cmdq.mutex); return repv; } const struct nvkm_rm_api_rpc r535_rpc = { .get = r535_gsp_rpc_get, .push = r535_gsp_rpc_push, .done = r535_gsp_rpc_done, };