/** * DOC: CTB Blob * * We allocate single blob to hold both CTB descriptors and buffers: * * +--------+-----------------------------------------------+------+ * | offset | contents | size | * +========+===============================================+======+ * | 0x0000 | H2G `CTB Descriptor`_ (send) | | * +--------+-----------------------------------------------+ 4K | * | 0x0800 | G2H `CTB Descriptor`_ (recv) | | * +--------+-----------------------------------------------+------+ * | 0x1000 | H2G `CT Buffer`_ (send) | n*4K | * | | | | * +--------+-----------------------------------------------+------+ * | 0x1000 | G2H `CT Buffer`_ (recv) | m*4K | * | + n*4K | | | * +--------+-----------------------------------------------+------+ * * Size of each `CT Buffer`_ must be multiple of 4K. * We don't expect too many messages in flight at any time, unless we are * using the GuC submission. In that case each request requires a minimum * 2 dwords which gives us a maximum 256 queue'd requests. Hopefully this * enough space to avoid backpressure on the driver. We increase the size * of the receive buffer (relative to the send) to ensure a G2H response * CTB has a landing spot.
*/ #define CTB_DESC_SIZE ALIGN(sizeof(struct guc_ct_buffer_desc), SZ_2K) #define CTB_H2G_BUFFER_SIZE (SZ_4K) #define CTB_G2H_BUFFER_SIZE (4 * CTB_H2G_BUFFER_SIZE) #define G2H_ROOM_BUFFER_SIZE (CTB_G2H_BUFFER_SIZE / 4)
/* * Some H2G commands involve a synchronous response that the driver needs * to wait for. In such cases, a timeout is required to prevent the driver * from waiting forever in the case of an error (either no error response * is defined in the protocol or something has died and requires a reset). * The specific command may be defined as having a time bound response but * the CT is a queue and that time guarantee only starts from the point * when the command reaches the head of the queue and is processed by GuC. * * Ideally there would be a helper to report the progress of a given * command through the CT. However, that would require a significant * amount of work in the CT layer. In the meantime, provide a reasonable * estimation of the worst case latency it should take for the entire * queue to drain. And therefore, how long a caller should wait before * giving up on their request. The current estimate is based on empirical * measurement of a test that fills the buffer with context creation and * destruction requests as they seem to be the slowest operation.
*/ long intel_guc_ct_max_queue_time_jiffies(void)
{ /* * A 4KB buffer full of context destroy commands takes a little * over a second to process so bump that to 2s to be super safe.
*/ return (CTB_H2G_BUFFER_SIZE * HZ) / SZ_2K;
}
/** * intel_guc_ct_enable - Enable buffer based command transport. * @ct: pointer to CT struct * * Return: 0 on success, a negative errno code on failure.
*/ int intel_guc_ct_enable(struct intel_guc_ct *ct)
{ struct intel_guc *guc = ct_to_guc(ct);
u32 base, desc, cmds, size; void *blob; int err;
GEM_BUG_ON(ct->enabled);
/* vma should be already allocated and map'ed */
GEM_BUG_ON(!ct->vma);
GEM_BUG_ON(!i915_gem_object_has_pinned_pages(ct->vma->obj));
base = intel_guc_ggtt_offset(guc, ct->vma);
/* blob should start with send descriptor */
blob = __px_vaddr(ct->vma->obj);
GEM_BUG_ON(blob != ct->ctbs.send.desc);
/* * Register both CT buffers starting with RECV buffer. * Descriptors are in first half of the blob.
*/
desc = base + ptrdiff(ct->ctbs.recv.desc, blob);
cmds = base + ptrdiff(ct->ctbs.recv.cmds, blob);
size = ct->ctbs.recv.size * 4;
err = ct_register_buffer(ct, false, desc, cmds, size); if (unlikely(err)) goto err_out;
desc = base + ptrdiff(ct->ctbs.send.desc, blob);
cmds = base + ptrdiff(ct->ctbs.send.cmds, blob);
size = ct->ctbs.send.size * 4;
err = ct_register_buffer(ct, true, desc, cmds, size); if (unlikely(err)) goto err_out;
err = ct_control_enable(ct, true); if (unlikely(err)) goto err_out;
n = stack_trace_save(entries, ARRAY_SIZE(entries), 1);
/* May be called under spinlock, so avoid sleeping */
ct->requests.lost_and_found[lost].stack = stack_depot_save(entries, n, GFP_NOWAIT); #endif
ct->requests.lost_and_found[lost].fence = fence;
ct->requests.lost_and_found[lost].action = action;
} #endif
static u32 ct_get_next_fence(struct intel_guc_ct *ct)
{ /* For now it's trivial */ return ++ct->requests.last_fence;
}
/* * make sure H2G buffer update and LRC tail update (if this triggering a * submission) are visible before updating the descriptor tail
*/
intel_guc_write_barrier(ct_to_guc(ct));
/* update local copies */
ctb->tail = tail;
GEM_BUG_ON(atomic_read(&ctb->space) < len + GUC_CTB_HDR_LEN);
atomic_sub(len + GUC_CTB_HDR_LEN, &ctb->space);
/* now update descriptor */
WRITE_ONCE(desc->tail, tail);
/** * wait_for_ct_request_update - Wait for CT request state update. * @ct: pointer to CT * @req: pointer to pending request * @status: placeholder for status * * For each sent request, GuC shall send back CT response message. * Our message handler will update status of tracked request once * response message with given fence is received. Wait here and * check for valid response status value. * * Return: * * 0 response received (status is valid) * * -ETIMEDOUT no response within hardcoded timeout
*/ staticint wait_for_ct_request_update(struct intel_guc_ct *ct, struct ct_request *req, u32 *status)
{ int err; bool ct_enabled;
/* * Fast commands should complete in less than 10us, so sample quickly * up to that length of time, then switch to a slower sleep-wait loop. * No GuC command should ever take longer than 10ms but many GuC * commands can be inflight at time, so use a 1s timeout on the slower * sleep-wait loop.
*/ #define GUC_CTB_RESPONSE_TIMEOUT_SHORT_MS 10 #define GUC_CTB_RESPONSE_TIMEOUT_LONG_MS 1000 #define done \
(!(ct_enabled = intel_guc_ct_enabled(ct)) || \
FIELD_GET(GUC_HXG_MSG_0_ORIGIN, READ_ONCE(req->status)) == \
GUC_HXG_ORIGIN_GUC)
err = wait_for_us(done, GUC_CTB_RESPONSE_TIMEOUT_SHORT_MS); if (err)
err = wait_for(done, GUC_CTB_RESPONSE_TIMEOUT_LONG_MS); #undef done if (!ct_enabled)
err = -ENODEV;
*status = req->status; return err;
}
#define GUC_CTB_TIMEOUT_MS 1500 staticinlinebool ct_deadlocked(struct intel_guc_ct *ct)
{ long timeout = GUC_CTB_TIMEOUT_MS; bool ret = ktime_ms_delta(ktime_get(), ct->stall_time) > timeout;
/* * We leave a certain amount of space in the G2H CTB buffer for * unexpected G2H CTBs (e.g. logging, engine hang, etc...)
*/ return !g2h_len_dw || atomic_read(&ctb->space) >= g2h_len_dw;
}
/* * We use a lazy spin wait loop here as we believe that if the CT * buffers are sized correctly the flow control condition should be * rare. Reserving the maximum size in the G2H credits as we don't know * how big the response is going to be.
*/
retry:
spin_lock_irqsave(&ctb->lock, flags); if (unlikely(!h2g_has_room(ct, len + GUC_CTB_HDR_LEN) ||
!g2h_has_room(ct, GUC_CTB_HXG_MSG_MAX_LEN))) { if (ct->stall_time == KTIME_MAX)
ct->stall_time = ktime_get();
spin_unlock_irqrestore(&ctb->lock, flags);
if (unlikely(ct_deadlocked(ct))) return -EPIPE;
if (msleep_interruptible(sleep_period_ms)) return -EINTR;
sleep_period_ms = sleep_period_ms << 1;
err = wait_for_ct_request_update(ct, &request, status);
g2h_release_space(ct, GUC_CTB_HXG_MSG_MAX_LEN); if (unlikely(err)) { if (err == -ENODEV) /* wait_for_ct_request_update returns -ENODEV on reset/suspend in progress. * In this case, output is debug rather than error info
*/
CT_DEBUG(ct, "Request %#x (fence %u) cancelled as CTB is disabled\n",
action[0], request.fence); else
CT_ERROR(ct, "No response for request %#x (fence %u)\n",
action[0], request.fence); goto unlink;
}
if (response_buf) { /* There shall be no data in the status */
WARN_ON(FIELD_GET(GUC_HXG_RESPONSE_MSG_0_DATA0, request.status)); /* Return actual response len */
err = request.response_len;
} else { /* There shall be no response payload */
WARN_ON(request.response_len); /* Return data decoded from the status dword */
err = FIELD_GET(GUC_HXG_RESPONSE_MSG_0_DATA0, *status);
}
/* * Return: number available remaining dwords to read (0 if empty) * or a negative error code on failure
*/ staticint ct_read(struct intel_guc_ct *ct, struct ct_incoming_msg **msg)
{ struct intel_guc_ct_buffer *ctb = &ct->ctbs.recv; struct guc_ct_buffer_desc *desc = ctb->desc;
u32 head = ctb->head;
u32 tail = READ_ONCE(desc->tail);
u32 size = ctb->size;
u32 *cmds = ctb->cmds;
s32 available; unsignedint len; unsignedint i;
u32 header;
if (unlikely(ctb->broken)) return -EPIPE;
if (unlikely(desc->status)) {
u32 status = desc->status;
if (status & GUC_CTB_STATUS_UNUSED) { /* * Potentially valid if a CLIENT_RESET request resulted in * contexts/engines being reset. But should never happen as * no contexts should be active when CLIENT_RESET is sent.
*/
CT_ERROR(ct, "Unexpected G2H after GuC has stopped!\n");
status &= ~GUC_CTB_STATUS_UNUSED;
}
switch (action) { case INTEL_GUC_ACTION_DEFAULT:
ret = intel_guc_to_host_process_recv_msg(guc, payload, len); break; case INTEL_GUC_ACTION_DEREGISTER_CONTEXT_DONE:
ret = intel_guc_deregister_done_process_msg(guc, payload,
len); break; case INTEL_GUC_ACTION_SCHED_CONTEXT_MODE_DONE:
ret = intel_guc_sched_done_process_msg(guc, payload, len); break; case INTEL_GUC_ACTION_CONTEXT_RESET_NOTIFICATION:
ret = intel_guc_context_reset_process_msg(guc, payload, len); break; case INTEL_GUC_ACTION_STATE_CAPTURE_NOTIFICATION:
ret = intel_guc_error_capture_process_msg(guc, payload, len); if (unlikely(ret))
CT_ERROR(ct, "error capture notification failed %x %*ph\n",
action, 4 * len, payload); break; case INTEL_GUC_ACTION_ENGINE_FAILURE_NOTIFICATION:
ret = intel_guc_engine_failure_process_msg(guc, payload, len); break; case INTEL_GUC_ACTION_NOTIFY_FLUSH_LOG_BUFFER_TO_FILE:
intel_guc_log_handle_flush_event(&guc->log);
ret = 0; break; case INTEL_GUC_ACTION_NOTIFY_CRASH_DUMP_POSTED: case INTEL_GUC_ACTION_NOTIFY_EXCEPTION:
ret = intel_guc_crash_process_msg(guc, action); break; case INTEL_GUC_ACTION_TLB_INVALIDATION_DONE:
ret = intel_guc_tlb_invalidation_done(guc, payload, len); break; default:
ret = -EOPNOTSUPP; break;
}
if (unlikely(ret)) {
CT_ERROR(ct, "Failed to process request %04x (%pe)\n",
action, ERR_PTR(ret)); return ret;
}
/* * Adjusting the space must be done in IRQ or deadlock can occur as the * CTB processing in the below workqueue can send CTBs which creates a * circular dependency if the space was returned there.
*/ switch (action) { case INTEL_GUC_ACTION_SCHED_CONTEXT_MODE_DONE: case INTEL_GUC_ACTION_DEREGISTER_CONTEXT_DONE: case INTEL_GUC_ACTION_TLB_INVALIDATION_DONE:
g2h_release_space(ct, request->size);
}
/* * TLB invalidation responses must be handled immediately as processing * of other G2H notifications may be blocked by an invalidation request.
*/ if (action == INTEL_GUC_ACTION_TLB_INVALIDATION_DONE) return ct_process_request(ct, request);
if (unlikely(err)) {
CT_ERROR(ct, "Failed to process CT message (%pe) %*ph\n",
ERR_PTR(err), 4 * msg->size, msg->msg);
ct_free_msg(msg);
}
}
/* * Return: number available remaining dwords to read (0 if empty) * or a negative error code on failure
*/ staticint ct_receive(struct intel_guc_ct *ct)
{ struct ct_incoming_msg *msg = NULL; unsignedlong flags; int ret;
spin_lock_irqsave(&ct->ctbs.recv.lock, flags);
ret = ct_read(ct, &msg);
spin_unlock_irqrestore(&ct->ctbs.recv.lock, flags); if (ret < 0) return ret;
/* * When we're communicating with the GuC over CT, GuC uses events * to notify us about new messages being posted on the RECV buffer.
*/ void intel_guc_ct_event_handler(struct intel_guc_ct *ct)
{ if (unlikely(!ct->enabled)) {
WARN(1, "Unexpected GuC event received while CT disabled!\n"); return;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.