| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Copyright (c) 2021, The Linux Foundation. All rights reserved. |
| * Copyright (c) 2023-2024, Qualcomm Innovation Center, Inc. All rights reserved. |
| */ |
| |
| #include <dt-bindings/soc/qcom,ipcc.h> |
| #include <linux/dma-fence-array.h> |
| #include <linux/iommu.h> |
| #include <linux/sched/clock.h> |
| #include <soc/qcom/msm_performance.h> |
| |
| #include "adreno.h" |
| #include "adreno_gen8.h" |
| #include "adreno_gen8_hwsched.h" |
| #include "adreno_hfi.h" |
| #include "adreno_pm4types.h" |
| #include "adreno_trace.h" |
| #include "kgsl_device.h" |
| #include "kgsl_eventlog.h" |
| #include "kgsl_pwrctrl.h" |
| #include "kgsl_trace.h" |
| #include "kgsl_util.h" |
| |
| #if (KERNEL_VERSION(6, 3, 0) <= LINUX_VERSION_CODE) |
| #include <msm_hw_fence.h> |
| #else |
| #include <linux/soc/qcom/msm_hw_fence.h> |
| #endif |
| |
| |
| #define HFI_QUEUE_MAX (HFI_QUEUE_DEFAULT_CNT + HFI_QUEUE_DISPATCH_MAX_CNT) |
| |
| #define DEFINE_QHDR(gmuaddr, id, prio) \ |
| {\ |
| .status = 1, \ |
| .start_addr = GMU_QUEUE_START_ADDR(gmuaddr, id), \ |
| .type = QUEUE_HDR_TYPE(id, prio, 0, 0), \ |
| .queue_size = SZ_4K >> 2, \ |
| .msg_size = 0, \ |
| .unused0 = 0, \ |
| .unused1 = 0, \ |
| .unused2 = 0, \ |
| .unused3 = 0, \ |
| .unused4 = 0, \ |
| .read_index = 0, \ |
| .write_index = 0, \ |
| } |
| |
| static struct dq_info { |
| /** @max_dq: Maximum number of dispatch queues per RB level */ |
| u32 max_dq; |
| /** @base_dq_id: Base dqid for level */ |
| u32 base_dq_id; |
| /** @offset: Next dqid to use for roundrobin context assignment */ |
| u32 offset; |
| } gen8_hfi_dqs[KGSL_PRIORITY_MAX_RB_LEVELS] = { |
| { 4, 0, }, /* RB0 */ |
| { 4, 4, }, /* RB1 */ |
| { 3, 8, }, /* RB2 */ |
| { 3, 11, }, /* RB3 */ |
| }, gen8_hfi_dqs_lpac[KGSL_PRIORITY_MAX_RB_LEVELS + 1] = { |
| { 4, 0, }, /* RB0 */ |
| { 4, 4, }, /* RB1 */ |
| { 3, 8, }, /* RB2 */ |
| { 2, 11, }, /* RB3 */ |
| { 1, 13, }, /* RB LPAC */ |
| }; |
| |
| struct pending_cmd gen8_hw_fence_ack; |
| |
| struct gen8_hwsched_hfi *to_gen8_hwsched_hfi( |
| struct adreno_device *adreno_dev) |
| { |
| struct gen8_device *gen8_dev = container_of(adreno_dev, |
| struct gen8_device, adreno_dev); |
| struct gen8_hwsched_device *gen8_hwsched = container_of(gen8_dev, |
| struct gen8_hwsched_device, gen8_dev); |
| |
| return &gen8_hwsched->hwsched_hfi; |
| } |
| |
| int gen8_hfi_send_lpac_feature_ctrl(struct adreno_device *adreno_dev) |
| { |
| if (!adreno_dev->lpac_enabled) |
| return 0; |
| |
| return gen8_hfi_send_feature_ctrl(adreno_dev, HFI_FEATURE_LPAC, 1, 0); |
| } |
| |
| static void add_waiter(struct gen8_hwsched_hfi *hfi, u32 hdr, |
| struct pending_cmd *ack) |
| { |
| memset(ack, 0x0, sizeof(*ack)); |
| |
| init_completion(&ack->complete); |
| write_lock_irq(&hfi->msglock); |
| list_add_tail(&ack->node, &hfi->msglist); |
| write_unlock_irq(&hfi->msglock); |
| |
| ack->sent_hdr = hdr; |
| } |
| |
| static void del_waiter(struct gen8_hwsched_hfi *hfi, struct pending_cmd *ack) |
| { |
| write_lock_irq(&hfi->msglock); |
| list_del(&ack->node); |
| write_unlock_irq(&hfi->msglock); |
| } |
| |
| static void gen8_receive_ack_async(struct adreno_device *adreno_dev, void *rcvd) |
| { |
| struct gen8_gmu_device *gmu = to_gen8_gmu(adreno_dev); |
| struct gen8_hwsched_hfi *hfi = to_gen8_hwsched_hfi(adreno_dev); |
| struct pending_cmd *cmd = NULL; |
| u32 waiters[64], num_waiters = 0, i; |
| u32 *ack = rcvd; |
| u32 hdr = ack[0]; |
| u32 req_hdr = ack[1]; |
| u32 size_bytes = MSG_HDR_GET_SIZE(hdr) << 2; |
| |
| if (size_bytes > sizeof(cmd->results)) |
| dev_err_ratelimited(&gmu->pdev->dev, |
| "Ack result too big: %d Truncating to: %ld\n", |
| size_bytes, sizeof(cmd->results)); |
| |
| read_lock(&hfi->msglock); |
| |
| list_for_each_entry(cmd, &hfi->msglist, node) { |
| if (CMP_HFI_ACK_HDR(cmd->sent_hdr, req_hdr)) { |
| memcpy(cmd->results, ack, |
| min_t(u32, size_bytes, |
| sizeof(cmd->results))); |
| complete(&cmd->complete); |
| read_unlock(&hfi->msglock); |
| return; |
| } |
| |
| if (num_waiters < ARRAY_SIZE(waiters)) |
| waiters[num_waiters++] = cmd->sent_hdr; |
| } |
| |
| read_unlock(&hfi->msglock); |
| |
| /* Didn't find the sender, list the waiter */ |
| dev_err_ratelimited(&gmu->pdev->dev, |
| "Unexpectedly got id %d seqnum %d. Total waiters: %d Top %d Waiters:\n", |
| MSG_HDR_GET_ID(req_hdr), MSG_HDR_GET_SEQNUM(req_hdr), |
| num_waiters, min_t(u32, num_waiters, 5)); |
| |
| for (i = 0; i < num_waiters && i < 5; i++) |
| dev_err_ratelimited(&gmu->pdev->dev, |
| " id %d seqnum %d\n", |
| MSG_HDR_GET_ID(waiters[i]), |
| MSG_HDR_GET_SEQNUM(waiters[i])); |
| } |
| |
| /* This function is called while holding the drawctxt spinlock */ |
| void gen8_remove_hw_fence_entry(struct adreno_device *adreno_dev, |
| struct adreno_hw_fence_entry *entry) |
| { |
| struct adreno_hwsched *hwsched = &adreno_dev->hwsched; |
| struct adreno_context *drawctxt = entry->drawctxt; |
| |
| atomic_dec(&hwsched->hw_fence_count); |
| drawctxt->hw_fence_count--; |
| |
| dma_fence_put(&entry->kfence->fence); |
| list_del_init(&entry->node); |
| kmem_cache_free(hwsched->hw_fence_cache, entry); |
| } |
| |
| static void _retire_inflight_hw_fences(struct adreno_device *adreno_dev, |
| struct kgsl_context *context) |
| { |
| struct adreno_context *drawctxt = ADRENO_CONTEXT(context); |
| struct adreno_hw_fence_entry *entry, *tmp; |
| |
| if (!test_bit(ADRENO_HWSCHED_HW_FENCE, &adreno_dev->hwsched.flags)) |
| return; |
| |
| spin_lock(&drawctxt->lock); |
| |
| list_for_each_entry_safe(entry, tmp, &drawctxt->hw_fence_inflight_list, node) { |
| struct gmu_context_queue_header *hdr = drawctxt->gmu_context_queue.hostptr; |
| |
| /* |
| * Since this list is sorted by timestamp, abort on the first fence that hasn't |
| * yet been sent to TxQueue |
| */ |
| if (timestamp_cmp((u32)entry->cmd.ts, hdr->out_fence_ts) > 0) |
| break; |
| |
| gen8_remove_hw_fence_entry(adreno_dev, entry); |
| } |
| spin_unlock(&drawctxt->lock); |
| } |
| |
| static void log_profiling_info(struct adreno_device *adreno_dev, u32 *rcvd) |
| { |
| struct hfi_ts_retire_cmd *cmd = (struct hfi_ts_retire_cmd *)rcvd; |
| struct kgsl_context *context; |
| struct retire_info info = {0}; |
| struct gen8_gmu_device *gmu = to_gen8_gmu(adreno_dev); |
| struct kgsl_device *device = KGSL_DEVICE(adreno_dev); |
| |
| context = kgsl_context_get(device, cmd->ctxt_id); |
| if (context == NULL) |
| return; |
| |
| info.timestamp = cmd->ts; |
| info.rb_id = adreno_get_level(context); |
| info.gmu_dispatch_queue = context->gmu_dispatch_queue; |
| info.submitted_to_rb = cmd->submitted_to_rb; |
| info.sop = cmd->sop; |
| info.eop = cmd->eop; |
| if (GMU_VER_MINOR(gmu->ver.hfi) < 4) |
| info.active = cmd->eop - cmd->sop; |
| else |
| info.active = cmd->active; |
| info.retired_on_gmu = cmd->retired_on_gmu; |
| |
| /* protected GPU work must not be reported */ |
| if (!(context->flags & KGSL_CONTEXT_SECURE)) |
| kgsl_work_period_update(device, context->proc_priv->period, |
| info.active); |
| |
| trace_adreno_cmdbatch_retired(context, &info, 0, 0, 0); |
| |
| log_kgsl_cmdbatch_retired_event(context->id, cmd->ts, |
| context->priority, 0, cmd->sop, cmd->eop); |
| |
| _retire_inflight_hw_fences(adreno_dev, context); |
| |
| kgsl_context_put(context); |
| } |
| |
| u32 gen8_hwsched_parse_payload(struct payload_section *payload, u32 key) |
| { |
| u32 i; |
| |
| /* Each key-value pair is 2 dwords */ |
| for (i = 0; i < payload->dwords; i += 2) { |
| if (payload->data[i] == key) |
| return payload->data[i + 1]; |
| } |
| |
| return 0; |
| } |
| |
| struct syncobj_flags { |
| unsigned long mask; |
| const char *name; |
| }; |
| |
| static void _get_syncobj_string(char *str, u32 max_size, struct hfi_syncobj *syncobj, u32 index) |
| { |
| u32 count = scnprintf(str, max_size, "syncobj[%d] ctxt_id:%llu seqno:%llu flags:", index, |
| syncobj->ctxt_id, syncobj->seq_no); |
| u32 i; |
| bool first = true; |
| static const struct syncobj_flags _flags[] = { |
| GMU_SYNCOBJ_FLAGS, { -1, NULL }}; |
| |
| for (i = 0; _flags[i].name; i++) { |
| if (!(syncobj->flags & _flags[i].mask)) |
| continue; |
| |
| if (first) { |
| count += scnprintf(str + count, max_size - count, "%s", _flags[i].name); |
| first = false; |
| } else { |
| count += scnprintf(str + count, max_size - count, "|%s", _flags[i].name); |
| } |
| } |
| } |
| |
| static void log_syncobj(struct gen8_gmu_device *gmu, struct hfi_submit_syncobj *cmd) |
| { |
| struct hfi_syncobj *syncobj = (struct hfi_syncobj *)&cmd[1]; |
| char str[128]; |
| u32 i = 0; |
| |
| for (i = 0; i < cmd->num_syncobj; i++) { |
| _get_syncobj_string(str, sizeof(str), syncobj, i); |
| dev_err(&gmu->pdev->dev, "%s\n", str); |
| syncobj++; |
| } |
| } |
| |
| static void find_timeout_syncobj(struct adreno_device *adreno_dev, u32 ctxt_id, u32 ts) |
| { |
| struct gen8_gmu_device *gmu = to_gen8_gmu(adreno_dev); |
| struct kgsl_context *context = NULL; |
| struct adreno_context *drawctxt; |
| struct kgsl_device *device = KGSL_DEVICE(adreno_dev); |
| struct gmu_context_queue_header *hdr; |
| struct hfi_submit_syncobj *cmd; |
| u32 *queue, i; |
| int ret; |
| |
| /* We want to get the context even if it is detached */ |
| read_lock(&device->context_lock); |
| context = idr_find(&device->context_idr, ctxt_id); |
| ret = _kgsl_context_get(context); |
| read_unlock(&device->context_lock); |
| |
| if (!ret) |
| return; |
| |
| drawctxt = ADRENO_CONTEXT(context); |
| |
| hdr = drawctxt->gmu_context_queue.hostptr; |
| queue = (u32 *)(drawctxt->gmu_context_queue.hostptr + sizeof(*hdr)); |
| |
| for (i = hdr->read_index; i != hdr->write_index;) { |
| if (MSG_HDR_GET_ID(queue[i]) != H2F_MSG_ISSUE_SYNCOBJ) { |
| i = (i + MSG_HDR_GET_SIZE(queue[i])) % hdr->queue_size; |
| continue; |
| } |
| |
| cmd = (struct hfi_submit_syncobj *)&queue[i]; |
| |
| if (cmd->timestamp == ts) { |
| log_syncobj(gmu, cmd); |
| break; |
| } |
| i = (i + MSG_HDR_GET_SIZE(queue[i])) % hdr->queue_size; |
| } |
| |
| if (i == hdr->write_index) |
| dev_err(&gmu->pdev->dev, "Couldn't find unsignaled syncobj ctx:%d ts:%d\n", |
| ctxt_id, ts); |
| |
| kgsl_context_put(context); |
| } |
| |
| /* Look up a particular key's value for a given type of payload */ |
| static u32 gen8_hwsched_lookup_key_value(struct adreno_device *adreno_dev, |
| u32 type, u32 key) |
| { |
| struct hfi_context_bad_cmd *cmd = adreno_dev->hwsched.ctxt_bad; |
| u32 i = 0, payload_bytes; |
| void *start; |
| |
| if (!cmd->hdr) |
| return 0; |
| |
| payload_bytes = (MSG_HDR_GET_SIZE(cmd->hdr) << 2) - |
| offsetof(struct hfi_context_bad_cmd, payload); |
| |
| start = &cmd->payload[0]; |
| |
| while (i < payload_bytes) { |
| struct payload_section *payload = start + i; |
| |
| if (payload->type == type) |
| return gen8_hwsched_parse_payload(payload, key); |
| |
| i += struct_size(payload, data, payload->dwords); |
| } |
| |
| return 0; |
| } |
| |
| static u32 get_payload_rb_key(struct adreno_device *adreno_dev, |
| u32 rb_id, u32 key) |
| { |
| struct hfi_context_bad_cmd *cmd = adreno_dev->hwsched.ctxt_bad; |
| u32 i = 0, payload_bytes; |
| void *start; |
| |
| if (!cmd->hdr) |
| return 0; |
| |
| payload_bytes = (MSG_HDR_GET_SIZE(cmd->hdr) << 2) - |
| offsetof(struct hfi_context_bad_cmd, payload); |
| |
| start = &cmd->payload[0]; |
| |
| while (i < payload_bytes) { |
| struct payload_section *payload = start + i; |
| |
| if (payload->type == PAYLOAD_RB) { |
| u32 id = gen8_hwsched_parse_payload(payload, KEY_RB_ID); |
| |
| if (id == rb_id) |
| return gen8_hwsched_parse_payload(payload, key); |
| } |
| |
| i += struct_size(payload, data, payload->dwords); |
| } |
| |
| return 0; |
| } |
| |
| static bool log_gpu_fault(struct adreno_device *adreno_dev) |
| { |
| struct gen8_gmu_device *gmu = to_gen8_gmu(adreno_dev); |
| struct device *dev = &gmu->pdev->dev; |
| struct hfi_context_bad_cmd *cmd = adreno_dev->hwsched.ctxt_bad; |
| |
| /* Return false for non fatal errors */ |
| if (adreno_hwsched_log_nonfatal_gpu_fault(adreno_dev, dev, cmd->error)) |
| return false; |
| |
| switch (cmd->error) { |
| case GMU_GPU_HW_HANG: |
| dev_crit_ratelimited(dev, "MISC: GPU hang detected\n"); |
| break; |
| case GMU_GPU_SW_HANG: |
| dev_crit_ratelimited(dev, "gpu timeout ctx %d ts %d\n", |
| cmd->gc.ctxt_id, cmd->gc.ts); |
| break; |
| case GMU_CP_OPCODE_ERROR: |
| dev_crit_ratelimited(dev, |
| "CP opcode error interrupt | opcode=0x%8.8x\n", |
| gen8_hwsched_lookup_key_value(adreno_dev, PAYLOAD_FAULT_REGS, |
| KEY_CP_OPCODE_ERROR)); |
| break; |
| case GMU_CP_PROTECTED_ERROR: { |
| u32 status = gen8_hwsched_lookup_key_value(adreno_dev, PAYLOAD_FAULT_REGS, |
| KEY_CP_PROTECTED_ERROR); |
| |
| dev_crit_ratelimited(dev, |
| "CP | Protected mode error | %s | addr=0x%5.5x | status=0x%8.8x\n", |
| status & (1 << 20) ? "READ" : "WRITE", |
| status & 0x3FFFF, status); |
| } |
| break; |
| case GMU_CP_ILLEGAL_INST_ERROR: |
| dev_crit_ratelimited(dev, "CP Illegal instruction error\n"); |
| break; |
| case GMU_CP_UCODE_ERROR: |
| dev_crit_ratelimited(dev, "CP ucode error interrupt\n"); |
| break; |
| case GMU_CP_HW_FAULT_ERROR: |
| dev_crit_ratelimited(dev, |
| "CP | Ringbuffer HW fault | status=0x%8.8x\n", |
| gen8_hwsched_lookup_key_value(adreno_dev, PAYLOAD_FAULT_REGS, |
| KEY_CP_HW_FAULT)); |
| break; |
| case GMU_GPU_PREEMPT_TIMEOUT: { |
| u32 cur, next, cur_rptr, cur_wptr, next_rptr, next_wptr; |
| |
| cur = gen8_hwsched_lookup_key_value(adreno_dev, |
| PAYLOAD_PREEMPT_TIMEOUT, KEY_PREEMPT_TIMEOUT_CUR_RB_ID); |
| next = gen8_hwsched_lookup_key_value(adreno_dev, |
| PAYLOAD_PREEMPT_TIMEOUT, |
| KEY_PREEMPT_TIMEOUT_NEXT_RB_ID); |
| cur_rptr = get_payload_rb_key(adreno_dev, cur, KEY_RB_RPTR); |
| cur_wptr = get_payload_rb_key(adreno_dev, cur, KEY_RB_WPTR); |
| next_rptr = get_payload_rb_key(adreno_dev, next, KEY_RB_RPTR); |
| next_wptr = get_payload_rb_key(adreno_dev, next, KEY_RB_WPTR); |
| |
| dev_crit_ratelimited(dev, |
| "Preemption Fault: cur=%d R/W=0x%x/0x%x, next=%d R/W=0x%x/0x%x\n", |
| cur, cur_rptr, cur_wptr, next, next_rptr, next_wptr); |
| } |
| break; |
| case GMU_CP_GPC_ERROR: |
| dev_crit_ratelimited(dev, "RBBM: GPC error\n"); |
| break; |
| case GMU_CP_BV_OPCODE_ERROR: |
| dev_crit_ratelimited(dev, |
| "CP BV opcode error | opcode=0x%8.8x\n", |
| gen8_hwsched_lookup_key_value(adreno_dev, PAYLOAD_FAULT_REGS, |
| KEY_CP_BV_OPCODE_ERROR)); |
| break; |
| case GMU_CP_BV_PROTECTED_ERROR: { |
| u32 status = gen8_hwsched_lookup_key_value(adreno_dev, PAYLOAD_FAULT_REGS, |
| KEY_CP_BV_PROTECTED_ERROR); |
| |
| dev_crit_ratelimited(dev, |
| "CP BV | Protected mode error | %s | addr=0x%5.5x | status=0x%8.8x\n", |
| status & (1 << 20) ? "READ" : "WRITE", |
| status & 0x3FFFF, status); |
| } |
| break; |
| case GMU_CP_BV_HW_FAULT_ERROR: |
| dev_crit_ratelimited(dev, |
| "CP BV | Ringbuffer HW fault | status=0x%8.8x\n", |
| gen8_hwsched_lookup_key_value(adreno_dev, PAYLOAD_FAULT_REGS, |
| KEY_CP_HW_FAULT)); |
| break; |
| case GMU_CP_BV_ILLEGAL_INST_ERROR: |
| dev_crit_ratelimited(dev, "CP BV Illegal instruction error\n"); |
| break; |
| case GMU_CP_BV_UCODE_ERROR: |
| dev_crit_ratelimited(dev, "CP BV ucode error interrupt\n"); |
| break; |
| case GMU_CP_LPAC_OPCODE_ERROR: |
| dev_crit_ratelimited(dev, |
| "CP LPAC opcode error | opcode=0x%8.8x\n", |
| gen8_hwsched_lookup_key_value(adreno_dev, PAYLOAD_FAULT_REGS, |
| KEY_CP_LPAC_OPCODE_ERROR)); |
| break; |
| case GMU_CP_LPAC_PROTECTED_ERROR: { |
| u32 status = gen8_hwsched_lookup_key_value(adreno_dev, PAYLOAD_FAULT_REGS, |
| KEY_CP_LPAC_PROTECTED_ERROR); |
| |
| dev_crit_ratelimited(dev, |
| "CP LPAC | Protected mode error | %s | addr=0x%5.5x | status=0x%8.8x\n", |
| status & (1 << 20) ? "READ" : "WRITE", |
| status & 0x3FFFF, status); |
| } |
| break; |
| case GMU_CP_LPAC_HW_FAULT_ERROR: |
| dev_crit_ratelimited(dev, |
| "CP LPAC | Ringbuffer HW fault | status=0x%8.8x\n", |
| gen8_hwsched_lookup_key_value(adreno_dev, PAYLOAD_FAULT_REGS, |
| KEY_CP_LPAC_HW_FAULT)); |
| break; |
| case GMU_CP_LPAC_ILLEGAL_INST_ERROR: |
| dev_crit_ratelimited(dev, "CP LPAC Illegal instruction error\n"); |
| break; |
| case GMU_CP_LPAC_UCODE_ERROR: |
| dev_crit_ratelimited(dev, "CP LPAC ucode error interrupt\n"); |
| break; |
| case GMU_GPU_LPAC_SW_HANG: |
| dev_crit_ratelimited(dev, "LPAC: gpu timeout ctx %d ts %d\n", |
| cmd->lpac.ctxt_id, cmd->lpac.ts); |
| break; |
| case GMU_GPU_SW_FUSE_VIOLATION: |
| dev_crit_ratelimited(dev, "RBBM: SW Feature Fuse violation status=0x%8.8x\n", |
| gen8_hwsched_lookup_key_value(adreno_dev, PAYLOAD_FAULT_REGS, |
| KEY_SWFUSE_VIOLATION_FAULT)); |
| break; |
| case GMU_GPU_AQE0_OPCODE_ERRROR: |
| dev_crit_ratelimited(dev, "AQE0 opcode error | opcode=0x%8.8x\n", |
| gen8_hwsched_lookup_key_value(adreno_dev, |
| PAYLOAD_FAULT_REGS, KEY_AQE0_OPCODE_ERROR)); |
| break; |
| case GMU_GPU_AQE0_UCODE_ERROR: |
| dev_crit_ratelimited(dev, "AQE0 ucode error interrupt\n"); |
| break; |
| case GMU_GPU_AQE0_HW_FAULT_ERROR: |
| dev_crit_ratelimited(dev, "AQE0 HW fault | status=0x%8.8x\n", |
| gen8_hwsched_lookup_key_value(adreno_dev, |
| PAYLOAD_FAULT_REGS, KEY_AQE0_HW_FAULT)); |
| break; |
| case GMU_GPU_AQE0_ILLEGAL_INST_ERROR: |
| dev_crit_ratelimited(dev, "AQE0 Illegal instruction error\n"); |
| break; |
| case GMU_GPU_AQE1_OPCODE_ERRROR: |
| dev_crit_ratelimited(dev, "AQE1 opcode error | opcode=0x%8.8x\n", |
| gen8_hwsched_lookup_key_value(adreno_dev, |
| PAYLOAD_FAULT_REGS, KEY_AQE1_OPCODE_ERROR)); |
| break; |
| case GMU_GPU_AQE1_UCODE_ERROR: |
| dev_crit_ratelimited(dev, "AQE1 ucode error interrupt\n"); |
| break; |
| case GMU_GPU_AQE1_HW_FAULT_ERROR: |
| dev_crit_ratelimited(dev, "AQE1 HW fault | status=0x%8.8x\n", |
| gen8_hwsched_lookup_key_value(adreno_dev, |
| PAYLOAD_FAULT_REGS, KEY_AQE1_HW_FAULT)); |
| break; |
| case GMU_GPU_AQE1_ILLEGAL_INST_ERROR: |
| dev_crit_ratelimited(dev, "AQE1 Illegal instruction error\n"); |
| break; |
| case GMU_SYNCOBJ_TIMEOUT_ERROR: |
| dev_crit_ratelimited(dev, "syncobj timeout ctx %d ts %u\n", |
| cmd->gc.ctxt_id, cmd->gc.ts); |
| find_timeout_syncobj(adreno_dev, cmd->gc.ctxt_id, cmd->gc.ts); |
| break; |
| case GMU_CP_UNKNOWN_ERROR: |
| fallthrough; |
| default: |
| dev_crit_ratelimited(dev, "Unknown GPU fault: %u\n", |
| cmd->error); |
| break; |
| } |
| |
| /* Return true for fatal errors to perform recovery sequence */ |
| return true; |
| } |
| |
| static u32 peek_next_header(struct gen8_gmu_device *gmu, uint32_t queue_idx) |
| { |
| struct kgsl_memdesc *mem_addr = gmu->hfi.hfi_mem; |
| struct hfi_queue_table *tbl = mem_addr->hostptr; |
| struct hfi_queue_header *hdr = &tbl->qhdr[queue_idx]; |
| u32 *queue; |
| |
| if (hdr->status == HFI_QUEUE_STATUS_DISABLED) |
| return 0; |
| |
| if (hdr->read_index == hdr->write_index) |
| return 0; |
| |
| queue = HOST_QUEUE_START_ADDR(mem_addr, queue_idx); |
| |
| return queue[hdr->read_index]; |
| } |
| |
| static void process_ctx_bad(struct adreno_device *adreno_dev) |
| { |
| /* Non fatal RBBM error interrupts don't go through reset and recovery */ |
| if (!log_gpu_fault(adreno_dev)) { |
| memset(adreno_dev->hwsched.ctxt_bad, 0x0, HFI_MAX_MSG_SIZE); |
| return; |
| } |
| |
| gen8_hwsched_fault(adreno_dev, ADRENO_HARD_FAULT); |
| } |
| |
| #define GET_QUERIED_FENCE_INDEX(x) (x / BITS_PER_SYNCOBJ_QUERY) |
| #define GET_QUERIED_FENCE_BIT(x) (x % BITS_PER_SYNCOBJ_QUERY) |
| |
| static bool fence_is_queried(struct hfi_syncobj_query_cmd *cmd, u32 fence_index) |
| { |
| u32 index = GET_QUERIED_FENCE_INDEX(fence_index); |
| u32 bit = GET_QUERIED_FENCE_BIT(fence_index); |
| |
| return (cmd->queries[index].query_bitmask & BIT(bit)); |
| } |
| |
| static void set_fence_signal_bit(struct adreno_device *adreno_dev, |
| struct hfi_syncobj_query_cmd *reply, struct dma_fence *fence, u32 fence_index, |
| char *name) |
| { |
| u32 index = GET_QUERIED_FENCE_INDEX(fence_index); |
| u32 bit = GET_QUERIED_FENCE_BIT(fence_index); |
| struct gen8_gmu_device *gmu = to_gen8_gmu(adreno_dev); |
| u64 flags = ADRENO_HW_FENCE_SW_STATUS_PENDING; |
| char value[32] = "unknown"; |
| |
| if (fence->ops->timeline_value_str) |
| fence->ops->timeline_value_str(fence, value, sizeof(value)); |
| |
| if (test_bit(DMA_FENCE_FLAG_SIGNALED_BIT, &fence->flags)) { |
| dev_err(&gmu->pdev->dev, |
| "GMU is waiting for signaled fence(ctx:%llu seqno:%llu value:%s)\n", |
| fence->context, fence->seqno, value); |
| reply->queries[index].query_bitmask |= BIT(bit); |
| flags = ADRENO_HW_FENCE_SW_STATUS_SIGNALED; |
| } |
| trace_adreno_hw_fence_query(fence->context, fence->seqno, flags, name, value); |
| } |
| |
| static void gen8_syncobj_query_reply(struct adreno_device *adreno_dev, |
| struct kgsl_drawobj *drawobj, struct hfi_syncobj_query_cmd *cmd) |
| { |
| struct hfi_syncobj_query_cmd reply = {0}; |
| int i, j, fence_index = 0; |
| struct kgsl_drawobj_sync *syncobj = SYNCOBJ(drawobj); |
| const struct adreno_gpudev *gpudev = ADRENO_GPU_DEVICE(adreno_dev); |
| |
| for (i = 0; i < syncobj->numsyncs; i++) { |
| struct kgsl_drawobj_sync_event *event = &syncobj->synclist[i]; |
| struct kgsl_sync_fence_cb *kcb = event->handle; |
| struct dma_fence **fences; |
| struct dma_fence_array *array; |
| struct event_fence_info *info = event->priv; |
| u32 num_fences; |
| |
| array = to_dma_fence_array(kcb->fence); |
| if (array != NULL) { |
| num_fences = array->num_fences; |
| fences = array->fences; |
| } else { |
| num_fences = 1; |
| fences = &kcb->fence; |
| } |
| |
| for (j = 0; j < num_fences; j++, fence_index++) { |
| if (!fence_is_queried(cmd, fence_index)) |
| continue; |
| |
| set_fence_signal_bit(adreno_dev, &reply, fences[j], fence_index, |
| info ? info->fences[j].name : "unknown"); |
| } |
| } |
| |
| reply.hdr = CREATE_MSG_HDR(F2H_MSG_SYNCOBJ_QUERY, HFI_MSG_CMD); |
| reply.gmu_ctxt_id = cmd->gmu_ctxt_id; |
| reply.sync_obj_ts = cmd->sync_obj_ts; |
| |
| trace_adreno_syncobj_query_reply(reply.gmu_ctxt_id, reply.sync_obj_ts, |
| gpudev->read_alwayson(adreno_dev)); |
| |
| gen8_hfi_send_cmd_async(adreno_dev, &reply, sizeof(reply)); |
| } |
| |
| struct syncobj_query_work { |
| /** @cmd: The query command to be processed */ |
| struct hfi_syncobj_query_cmd cmd; |
| /** @context: kgsl context that is waiting for this sync object */ |
| struct kgsl_context *context; |
| /** @work: The work structure to execute syncobj query reply */ |
| struct kthread_work work; |
| }; |
| |
| static void gen8_process_syncobj_query_work(struct kthread_work *work) |
| { |
| struct syncobj_query_work *query_work = container_of(work, |
| struct syncobj_query_work, work); |
| struct hfi_syncobj_query_cmd *cmd = (struct hfi_syncobj_query_cmd *)&query_work->cmd; |
| struct kgsl_context *context = query_work->context; |
| struct kgsl_device *device = context->device; |
| struct adreno_device *adreno_dev = ADRENO_DEVICE(device); |
| struct adreno_hwsched *hwsched = &adreno_dev->hwsched; |
| struct cmd_list_obj *obj; |
| bool missing = true; |
| |
| mutex_lock(&hwsched->mutex); |
| mutex_lock(&device->mutex); |
| |
| list_for_each_entry(obj, &hwsched->cmd_list, node) { |
| struct kgsl_drawobj *drawobj = obj->drawobj; |
| |
| if ((drawobj->type & SYNCOBJ_TYPE) == 0) |
| continue; |
| |
| if ((drawobj->context->id == cmd->gmu_ctxt_id) && |
| (drawobj->timestamp == cmd->sync_obj_ts)) { |
| gen8_syncobj_query_reply(adreno_dev, drawobj, cmd); |
| missing = false; |
| break; |
| } |
| } |
| |
| if (missing) { |
| struct gen8_gmu_device *gmu = to_gen8_gmu(adreno_dev); |
| struct adreno_context *drawctxt = ADRENO_CONTEXT(context); |
| struct gmu_context_queue_header *hdr = drawctxt->gmu_context_queue.hostptr; |
| |
| /* |
| * If the sync object is not found, it can only mean that the sync object was |
| * retired by the GMU in the meanwhile. However, if that is not the case, then |
| * we have a problem. |
| */ |
| if (timestamp_cmp(cmd->sync_obj_ts, hdr->sync_obj_ts) > 0) { |
| dev_err(&gmu->pdev->dev, "Missing sync object ctx:%d ts:%d retired:%d\n", |
| context->id, cmd->sync_obj_ts, hdr->sync_obj_ts); |
| gmu_core_fault_snapshot(device); |
| gen8_hwsched_fault(adreno_dev, ADRENO_GMU_FAULT); |
| } |
| } |
| |
| mutex_unlock(&device->mutex); |
| mutex_unlock(&hwsched->mutex); |
| |
| kgsl_context_put(context); |
| kfree(query_work); |
| } |
| |
| static void gen8_trigger_syncobj_query(struct adreno_device *adreno_dev, |
| u32 *rcvd) |
| { |
| struct syncobj_query_work *query_work; |
| struct adreno_hwsched *hwsched = &adreno_dev->hwsched; |
| struct hfi_syncobj_query_cmd *cmd = (struct hfi_syncobj_query_cmd *)rcvd; |
| struct kgsl_context *context = NULL; |
| struct kgsl_device *device = KGSL_DEVICE(adreno_dev); |
| const struct adreno_gpudev *gpudev = ADRENO_GPU_DEVICE(adreno_dev); |
| int ret; |
| |
| trace_adreno_syncobj_query(cmd->gmu_ctxt_id, cmd->sync_obj_ts, |
| gpudev->read_alwayson(adreno_dev)); |
| |
| /* |
| * We need the context even if it is detached. Hence, we can't use kgsl_context_get here. |
| * We must make sure that this context id doesn't get destroyed (to avoid re-use) until GMU |
| * has ack'd the query reply. |
| */ |
| read_lock(&device->context_lock); |
| context = idr_find(&device->context_idr, cmd->gmu_ctxt_id); |
| ret = _kgsl_context_get(context); |
| read_unlock(&device->context_lock); |
| |
| if (!ret) |
| return; |
| |
| query_work = kzalloc(sizeof(*query_work), GFP_KERNEL); |
| if (!query_work) { |
| kgsl_context_put(context); |
| return; |
| } |
| |
| kthread_init_work(&query_work->work, gen8_process_syncobj_query_work); |
| memcpy(&query_work->cmd, cmd, sizeof(*cmd)); |
| query_work->context = context; |
| |
| kthread_queue_work(hwsched->worker, &query_work->work); |
| } |
| |
| /* |
| * This defines the maximum unack'd hardware fences that we allow. When this limit is reached, we |
| * will put all threads (that want to create a hardware fence) to sleep until the maximum unack'd |
| * hardware fence count drops to MIN_HW_FENCE_UNACK_COUNT |
| */ |
| #define MAX_HW_FENCE_UNACK_COUNT 20 |
| |
| /* |
| * Once the maximum unack'd hardware fences drops to this value, wake up all the threads (that want |
| * to create hardware fences) |
| */ |
| #define MIN_HW_FENCE_UNACK_COUNT 10 |
| |
| /* |
| * This is the maximum duration (in milliseconds) a thread (that wants to create a hardware fence) |
| * is put to sleep while we wait for the maximum number of unack'd hardware fences to drop from |
| * MAX_HW_FENCE_UNACK_COUNT to MIN_HW_FENCE_UNACK_COUNT. If the count doesn't drop to the desired |
| * value, then log an error and trigger snapshot and recovery. |
| */ |
| #define HW_FENCE_SLEEP_MS 200 |
| |
| static void _enable_hw_fence_throttle(struct adreno_device *adreno_dev) |
| { |
| struct gen8_hwsched_hfi *hfi = to_gen8_hwsched_hfi(adreno_dev); |
| |
| set_bit(GEN8_HWSCHED_HW_FENCE_SLEEP_BIT, &hfi->hw_fence.flags); |
| set_bit(GEN8_HWSCHED_HW_FENCE_MAX_BIT, &hfi->hw_fence.flags); |
| |
| /* Avoid submitting new work to gpu until the unack count drops to a desired threshold */ |
| adreno_get_gpu_halt(adreno_dev); |
| |
| mod_timer(&hfi->hw_fence_timer, jiffies + msecs_to_jiffies(HW_FENCE_SLEEP_MS)); |
| } |
| |
| static void _increment_hw_fence_unack_count(struct adreno_device *adreno_dev) |
| { |
| struct gen8_hwsched_hfi *hfi = to_gen8_hwsched_hfi(adreno_dev); |
| |
| if ((++hfi->hw_fence.unack_count) == MAX_HW_FENCE_UNACK_COUNT) |
| _enable_hw_fence_throttle(adreno_dev); |
| } |
| |
| /** |
| * _send_hw_fence_no_ack - Send a hardware fence hfi packet to GMU without waiting for its ack. |
| * Increment the unack count on success |
| * |
| * Return: 0 on success or negative error on failure |
| */ |
| static int _send_hw_fence_no_ack(struct adreno_device *adreno_dev, |
| struct adreno_hw_fence_entry *entry) |
| { |
| struct gen8_hwsched_hfi *hfi = to_gen8_hwsched_hfi(adreno_dev); |
| u32 seqnum; |
| int ret; |
| |
| seqnum = atomic_inc_return(&hfi->hw_fence.seqnum); |
| entry->cmd.hdr = MSG_HDR_SET_SEQNUM_SIZE(entry->cmd.hdr, seqnum, sizeof(entry->cmd) >> 2); |
| |
| ret = gen8_hfi_cmdq_write(adreno_dev, (u32 *)&entry->cmd, sizeof(entry->cmd)); |
| if (!ret) |
| _increment_hw_fence_unack_count(adreno_dev); |
| |
| return ret; |
| } |
| |
| static struct adreno_hw_fence_entry *_get_deferred_hw_fence(struct adreno_context *drawctxt, u32 ts) |
| { |
| struct adreno_hw_fence_entry *entry = NULL, *next, *deferred_hw_fence_entry = NULL; |
| |
| spin_lock(&drawctxt->lock); |
| list_for_each_entry_safe(entry, next, &drawctxt->hw_fence_list, node) { |
| |
| if (timestamp_cmp((u32)entry->cmd.ts, ts) > 0) |
| break; |
| |
| /* We found a deferred hardware fence */ |
| deferred_hw_fence_entry = entry; |
| break; |
| } |
| spin_unlock(&drawctxt->lock); |
| |
| /* |
| * This path executes in isolation from any paths that may release this entry. So, it is |
| * safe to handle this entry outside of the drawctxt spinlock |
| */ |
| return deferred_hw_fence_entry; |
| } |
| |
| static int _send_deferred_hw_fence(struct adreno_device *adreno_dev, |
| struct adreno_context *drawctxt, struct adreno_hw_fence_entry *entry, u32 ts) |
| { |
| bool retired = kgsl_check_timestamp(KGSL_DEVICE(adreno_dev), &drawctxt->base, ts) || |
| kgsl_context_is_bad(&drawctxt->base); |
| int ret = 0; |
| u32 flags = 0; |
| |
| if (retired) |
| flags |= HW_FENCE_FLAG_SKIP_MEMSTORE; |
| |
| ret = gen8_send_hw_fence_hfi_wait_ack(adreno_dev, entry, flags); |
| if (ret) |
| return ret; |
| |
| spin_lock(&drawctxt->lock); |
| if (!retired) |
| list_move_tail(&entry->node, &drawctxt->hw_fence_inflight_list); |
| else |
| gen8_remove_hw_fence_entry(adreno_dev, entry); |
| spin_unlock(&drawctxt->lock); |
| |
| return 0; |
| } |
| |
| /** |
| * process_hw_fence_deferred_ctxt - This function sends hardware fences to GMU (from the |
| * deferred drawctxt) which couldn't be sent earlier |
| */ |
| static int process_hw_fence_deferred_ctxt(struct adreno_device *adreno_dev, |
| struct adreno_context *drawctxt, u32 ts) |
| { |
| struct adreno_hw_fence_entry *deferred_hw_fence_entry = NULL; |
| int ret = 0; |
| |
| do { |
| deferred_hw_fence_entry = _get_deferred_hw_fence(drawctxt, ts); |
| |
| if (!deferred_hw_fence_entry) |
| break; |
| |
| ret = _send_deferred_hw_fence(adreno_dev, drawctxt, deferred_hw_fence_entry, ts); |
| if (ret) |
| break; |
| |
| } while (deferred_hw_fence_entry != NULL); |
| |
| return ret; |
| } |
| |
| static void _disable_hw_fence_throttle(struct adreno_device *adreno_dev, bool clear_abort_bit) |
| { |
| struct gen8_hwsched_hfi *hfi = to_gen8_hwsched_hfi(adreno_dev); |
| bool max; |
| |
| spin_lock(&hfi->hw_fence.lock); |
| |
| hfi->hw_fence.defer_drawctxt = NULL; |
| hfi->hw_fence.defer_ts = 0; |
| max = test_bit(GEN8_HWSCHED_HW_FENCE_MAX_BIT, &hfi->hw_fence.flags); |
| if (max) { |
| clear_bit(GEN8_HWSCHED_HW_FENCE_SLEEP_BIT, &hfi->hw_fence.flags); |
| clear_bit(GEN8_HWSCHED_HW_FENCE_MAX_BIT, &hfi->hw_fence.flags); |
| } |
| |
| if (clear_abort_bit) |
| clear_bit(GEN8_HWSCHED_HW_FENCE_ABORT_BIT, &hfi->hw_fence.flags); |
| spin_unlock(&hfi->hw_fence.lock); |
| |
| /* Wake up dispatcher and any sleeping threads that want to create hardware fences */ |
| if (max) { |
| adreno_put_gpu_halt(adreno_dev); |
| adreno_hwsched_trigger(adreno_dev); |
| wake_up_all(&hfi->hw_fence.unack_wq); |
| } |
| } |
| |
| static void gen8_defer_hw_fence_work(struct kthread_work *work) |
| { |
| struct gen8_hwsched_hfi *hfi = container_of(work, |
| struct gen8_hwsched_hfi, defer_hw_fence_work); |
| struct adreno_context *drawctxt = NULL; |
| struct kgsl_device *device; |
| struct adreno_device *adreno_dev; |
| u32 ts; |
| int ret; |
| |
| spin_lock(&hfi->hw_fence.lock); |
| drawctxt = hfi->hw_fence.defer_drawctxt; |
| ts = hfi->hw_fence.defer_ts; |
| spin_unlock(&hfi->hw_fence.lock); |
| |
| device = drawctxt->base.device; |
| adreno_dev = ADRENO_DEVICE(device); |
| |
| /* |
| * Grab the dispatcher and device mutex as we don't want to race with concurrent fault |
| * recovery |
| */ |
| mutex_lock(&adreno_dev->hwsched.mutex); |
| mutex_lock(&device->mutex); |
| |
| ret = process_hw_fence_deferred_ctxt(adreno_dev, drawctxt, ts); |
| if (ret) { |
| /* the deferred drawctxt will be handled post fault recovery */ |
| gen8_hwsched_fault(adreno_dev, ADRENO_GMU_FAULT); |
| goto unlock; |
| } |
| |
| /* |
| * Put back the context reference which was incremented when hw_fence.defer_drawctxt was set |
| */ |
| kgsl_context_put(&drawctxt->base); |
| |
| gen8_hwsched_active_count_put(adreno_dev); |
| |
| _disable_hw_fence_throttle(adreno_dev, false); |
| |
| unlock: |
| mutex_unlock(&device->mutex); |
| mutex_unlock(&adreno_dev->hwsched.mutex); |
| } |
| |
| static void process_hw_fence_ack(struct adreno_device *adreno_dev, u32 received_hdr) |
| { |
| struct gen8_hwsched_hfi *hfi = to_gen8_hwsched_hfi(adreno_dev); |
| struct adreno_context *drawctxt = NULL; |
| struct gen8_gmu_device *gmu = to_gen8_gmu(adreno_dev); |
| |
| spin_lock(&hfi->hw_fence.lock); |
| |
| /* If this ack is being waited on, we don't need to touch the unack count */ |
| if (gen8_hw_fence_ack.sent_hdr && |
| CMP_HFI_ACK_HDR(gen8_hw_fence_ack.sent_hdr, received_hdr)) { |
| spin_unlock(&hfi->hw_fence.lock); |
| complete(&gen8_hw_fence_ack.complete); |
| return; |
| } |
| |
| hfi->hw_fence.unack_count--; |
| |
| /* The unack count should never be greater than MAX_HW_FENCE_UNACK_COUNT */ |
| if (hfi->hw_fence.unack_count > MAX_HW_FENCE_UNACK_COUNT) |
| dev_err(&gmu->pdev->dev, "unexpected hardware fence unack count:%d\n", |
| hfi->hw_fence.unack_count); |
| |
| if (!test_bit(GEN8_HWSCHED_HW_FENCE_MAX_BIT, &hfi->hw_fence.flags) || |
| (hfi->hw_fence.unack_count != MIN_HW_FENCE_UNACK_COUNT)) { |
| spin_unlock(&hfi->hw_fence.lock); |
| return; |
| } |
| |
| drawctxt = hfi->hw_fence.defer_drawctxt; |
| |
| spin_unlock(&hfi->hw_fence.lock); |
| |
| del_timer_sync(&hfi->hw_fence_timer); |
| |
| /* |
| * We need to handle the deferred context in another thread so that we can unblock the f2h |
| * daemon here as it will need to process the acks for the hardware fences belonging to the |
| * deferred context |
| */ |
| if (drawctxt) { |
| kthread_init_work(&hfi->defer_hw_fence_work, gen8_defer_hw_fence_work); |
| kthread_queue_work(adreno_dev->hwsched.worker, &hfi->defer_hw_fence_work); |
| return; |
| } |
| |
| _disable_hw_fence_throttle(adreno_dev, false); |
| } |
| |
| void gen8_hwsched_process_msgq(struct adreno_device *adreno_dev) |
| { |
| struct gen8_gmu_device *gmu = to_gen8_gmu(adreno_dev); |
| struct gen8_hwsched_hfi *hw_hfi = to_gen8_hwsched_hfi(adreno_dev); |
| u32 rcvd[MAX_RCVD_SIZE], next_hdr, type; |
| |
| mutex_lock(&hw_hfi->msgq_mutex); |
| |
| for (;;) { |
| next_hdr = peek_next_header(gmu, HFI_MSG_ID); |
| |
| if (!next_hdr) |
| break; |
| |
| if (MSG_HDR_GET_TYPE(next_hdr) == HFI_MSG_ACK) |
| type = HFI_MSG_ACK; |
| else |
| type = MSG_HDR_GET_ID(next_hdr); |
| |
| if (type != F2H_MSG_CONTEXT_BAD) |
| gen8_hfi_queue_read(gmu, HFI_MSG_ID, rcvd, sizeof(rcvd)); |
| |
| switch (type) { |
| case HFI_MSG_ACK: |
| /* |
| * We are assuming that there is only one outstanding ack because hfi |
| * sending thread waits for completion while holding the device mutex |
| * (except when we send H2F_MSG_HW_FENCE_INFO packets) |
| */ |
| if (MSG_HDR_GET_ID(rcvd[1]) == H2F_MSG_HW_FENCE_INFO) |
| process_hw_fence_ack(adreno_dev, rcvd[1]); |
| else |
| gen8_receive_ack_async(adreno_dev, rcvd); |
| break; |
| case F2H_MSG_CONTEXT_BAD: |
| gen8_hfi_queue_read(gmu, HFI_MSG_ID, (u32 *)adreno_dev->hwsched.ctxt_bad, |
| HFI_MAX_MSG_SIZE); |
| process_ctx_bad(adreno_dev); |
| break; |
| case F2H_MSG_TS_RETIRE: |
| log_profiling_info(adreno_dev, rcvd); |
| adreno_hwsched_trigger(adreno_dev); |
| break; |
| case F2H_MSG_SYNCOBJ_QUERY: |
| gen8_trigger_syncobj_query(adreno_dev, rcvd); |
| break; |
| case F2H_MSG_GMU_CNTR_RELEASE: { |
| struct hfi_gmu_cntr_release_cmd *cmd = |
| (struct hfi_gmu_cntr_release_cmd *) rcvd; |
| |
| adreno_perfcounter_put(adreno_dev, |
| cmd->group_id, cmd->countable, PERFCOUNTER_FLAG_KERNEL); |
| |
| adreno_mark_for_coldboot(adreno_dev); |
| } |
| break; |
| } |
| } |
| mutex_unlock(&hw_hfi->msgq_mutex); |
| } |
| |
| static void process_log_block(struct adreno_device *adreno_dev, void *data) |
| { |
| struct gen8_gmu_device *gmu = to_gen8_gmu(adreno_dev); |
| struct hfi_log_block *cmd = data; |
| u32 *log_event = gmu->gmu_log->hostptr; |
| u32 start, end; |
| |
| start = cmd->start_index; |
| end = cmd->stop_index; |
| |
| log_event += start * 4; |
| while (start != end) { |
| trace_gmu_event(log_event); |
| log_event += 4; |
| start++; |
| } |
| } |
| |
| static void gen8_hwsched_process_dbgq(struct adreno_device *adreno_dev, bool limited) |
| { |
| struct gen8_gmu_device *gmu = to_gen8_gmu(adreno_dev); |
| u32 rcvd[MAX_RCVD_SIZE]; |
| bool recovery = false; |
| |
| while (gen8_hfi_queue_read(gmu, HFI_DBG_ID, rcvd, sizeof(rcvd)) > 0) { |
| |
| if (MSG_HDR_GET_ID(rcvd[0]) == F2H_MSG_ERR) { |
| adreno_gen8_receive_err_req(gmu, rcvd); |
| recovery = true; |
| break; |
| } |
| |
| if (MSG_HDR_GET_ID(rcvd[0]) == F2H_MSG_DEBUG) |
| adreno_gen8_receive_debug_req(gmu, rcvd); |
| |
| if (MSG_HDR_GET_ID(rcvd[0]) == F2H_MSG_LOG_BLOCK) |
| process_log_block(adreno_dev, rcvd); |
| |
| /* Process one debug queue message and return to not delay msgq processing */ |
| if (limited) |
| break; |
| } |
| |
| if (!recovery) |
| return; |
| |
| gen8_hwsched_fault(adreno_dev, ADRENO_GMU_FAULT); |
| } |
| |
| /* HFI interrupt handler */ |
| static irqreturn_t gen8_hwsched_hfi_handler(int irq, void *data) |
| { |
| struct adreno_device *adreno_dev = data; |
| struct gen8_gmu_device *gmu = to_gen8_gmu(adreno_dev); |
| struct gen8_hwsched_hfi *hfi = to_gen8_hwsched_hfi(adreno_dev); |
| struct kgsl_device *device = KGSL_DEVICE(adreno_dev); |
| u32 status = 0; |
| |
| /* |
| * GEN8_GMUCX_GMU2HOST_INTR_INFO may have bits set not specified in hfi->irq_mask. |
| * Read and clear only those irq bits that we are processing here. |
| */ |
| gmu_core_regread(device, GEN8_GMUCX_GMU2HOST_INTR_INFO, &status); |
| gmu_core_regwrite(device, GEN8_GMUCX_GMU2HOST_INTR_CLR, status & hfi->irq_mask); |
| |
| /* |
| * If interrupts are not enabled on the HFI message queue, |
| * the inline message processing loop will process it, |
| * else, process it here. |
| */ |
| if (!(hfi->irq_mask & HFI_IRQ_MSGQ_MASK)) |
| status &= ~HFI_IRQ_MSGQ_MASK; |
| |
| if (status & (HFI_IRQ_MSGQ_MASK | HFI_IRQ_DBGQ_MASK)) { |
| wake_up_interruptible(&hfi->f2h_wq); |
| adreno_hwsched_trigger(adreno_dev); |
| } |
| if (status & HFI_IRQ_CM3_FAULT_MASK) { |
| atomic_set(&gmu->cm3_fault, 1); |
| |
| /* make sure other CPUs see the update */ |
| smp_wmb(); |
| |
| dev_err_ratelimited(&gmu->pdev->dev, |
| "GMU CM3 fault interrupt received\n"); |
| |
| gen8_hwsched_fault(adreno_dev, ADRENO_GMU_FAULT); |
| } |
| |
| /* Ignore OOB bits */ |
| status &= GENMASK(31 - (oob_max - 1), 0); |
| |
| if (status & ~hfi->irq_mask) |
| dev_err_ratelimited(&gmu->pdev->dev, |
| "Unhandled HFI interrupts 0x%x\n", |
| status & ~hfi->irq_mask); |
| |
| return IRQ_HANDLED; |
| } |
| |
| #define HFI_IRQ_MSGQ_MASK BIT(0) |
| |
| static int check_ack_failure(struct adreno_device *adreno_dev, |
| struct pending_cmd *ack) |
| { |
| struct gen8_gmu_device *gmu = to_gen8_gmu(adreno_dev); |
| |
| if (ack->results[2] != 0xffffffff) |
| return 0; |
| |
| dev_err(&gmu->pdev->dev, |
| "ACK error: sender id %d seqnum %d\n", |
| MSG_HDR_GET_ID(ack->sent_hdr), |
| MSG_HDR_GET_SEQNUM(ack->sent_hdr)); |
| |
| return -EINVAL; |
| } |
| |
| int gen8_hfi_send_cmd_async(struct adreno_device *adreno_dev, void *data, u32 size_bytes) |
| { |
| struct gen8_gmu_device *gmu = to_gen8_gmu(adreno_dev); |
| struct gen8_hwsched_hfi *hfi = to_gen8_hwsched_hfi(adreno_dev); |
| u32 *cmd = data; |
| u32 seqnum; |
| int rc; |
| struct pending_cmd pending_ack; |
| |
| seqnum = atomic_inc_return(&gmu->hfi.seqnum); |
| *cmd = MSG_HDR_SET_SEQNUM_SIZE(*cmd, seqnum, size_bytes >> 2); |
| |
| add_waiter(hfi, *cmd, &pending_ack); |
| |
| rc = gen8_hfi_cmdq_write(adreno_dev, cmd, size_bytes); |
| if (rc) |
| goto done; |
| |
| rc = adreno_hwsched_wait_ack_completion(adreno_dev, &gmu->pdev->dev, &pending_ack, |
| gen8_hwsched_process_msgq); |
| if (rc) |
| goto done; |
| |
| rc = check_ack_failure(adreno_dev, &pending_ack); |
| |
| done: |
| del_waiter(hfi, &pending_ack); |
| |
| return rc; |
| } |
| |
| static void init_queues(struct gen8_hfi *hfi) |
| { |
| u32 gmuaddr = hfi->hfi_mem->gmuaddr; |
| struct hfi_queue_table hfi_table = { |
| .qtbl_hdr = { |
| .version = 0, |
| .size = sizeof(struct hfi_queue_table) >> 2, |
| .qhdr0_offset = |
| sizeof(struct hfi_queue_table_header) >> 2, |
| .qhdr_size = sizeof(struct hfi_queue_header) >> 2, |
| .num_q = HFI_QUEUE_MAX, |
| .num_active_q = HFI_QUEUE_MAX, |
| }, |
| .qhdr = { |
| DEFINE_QHDR(gmuaddr, HFI_CMD_ID, 0), |
| DEFINE_QHDR(gmuaddr, HFI_MSG_ID, 0), |
| DEFINE_QHDR(gmuaddr, HFI_DBG_ID, 0), |
| /* 4 DQs for RB priority 0 */ |
| DEFINE_QHDR(gmuaddr, 3, 0), |
| DEFINE_QHDR(gmuaddr, 4, 0), |
| DEFINE_QHDR(gmuaddr, 5, 0), |
| DEFINE_QHDR(gmuaddr, 6, 0), |
| /* 4 DQs for RB priority 1 */ |
| DEFINE_QHDR(gmuaddr, 7, 1), |
| DEFINE_QHDR(gmuaddr, 8, 1), |
| DEFINE_QHDR(gmuaddr, 9, 1), |
| DEFINE_QHDR(gmuaddr, 10, 1), |
| /* 3 DQs for RB priority 2 */ |
| DEFINE_QHDR(gmuaddr, 11, 2), |
| DEFINE_QHDR(gmuaddr, 12, 2), |
| DEFINE_QHDR(gmuaddr, 13, 2), |
| /* 2 DQs for RB priority 3 */ |
| DEFINE_QHDR(gmuaddr, 14, 3), |
| DEFINE_QHDR(gmuaddr, 15, 3), |
| /* 1 DQ for LPAC RB priority 4 */ |
| DEFINE_QHDR(gmuaddr, 16, 4), |
| }, |
| }; |
| |
| memcpy(hfi->hfi_mem->hostptr, &hfi_table, sizeof(hfi_table)); |
| } |
| |
| /* Total header sizes + queue sizes + 16 for alignment */ |
| #define HFIMEM_SIZE (sizeof(struct hfi_queue_table) + 16 + \ |
| (SZ_4K * HFI_QUEUE_MAX)) |
| |
| static int hfi_f2h_main(void *arg); |
| |
| int gen8_hwsched_hfi_init(struct adreno_device *adreno_dev) |
| { |
| struct gen8_hwsched_hfi *hw_hfi = to_gen8_hwsched_hfi(adreno_dev); |
| struct gen8_hfi *hfi = to_gen8_hfi(adreno_dev); |
| |
| if (IS_ERR_OR_NULL(hw_hfi->big_ib)) { |
| hw_hfi->big_ib = gen8_reserve_gmu_kernel_block( |
| to_gen8_gmu(adreno_dev), 0, |
| HWSCHED_MAX_IBS * sizeof(struct hfi_issue_ib), |
| GMU_NONCACHED_KERNEL, 0); |
| if (IS_ERR(hw_hfi->big_ib)) |
| return PTR_ERR(hw_hfi->big_ib); |
| } |
| |
| if (ADRENO_FEATURE(adreno_dev, ADRENO_LSR) && |
| IS_ERR_OR_NULL(hw_hfi->big_ib_recurring)) { |
| hw_hfi->big_ib_recurring = gen8_reserve_gmu_kernel_block( |
| to_gen8_gmu(adreno_dev), 0, |
| HWSCHED_MAX_IBS * sizeof(struct hfi_issue_ib), |
| GMU_NONCACHED_KERNEL, 0); |
| if (IS_ERR(hw_hfi->big_ib_recurring)) |
| return PTR_ERR(hw_hfi->big_ib_recurring); |
| } |
| |
| if (IS_ERR_OR_NULL(hfi->hfi_mem)) { |
| hfi->hfi_mem = gen8_reserve_gmu_kernel_block( |
| to_gen8_gmu(adreno_dev), |
| 0, HFIMEM_SIZE, GMU_NONCACHED_KERNEL, 0); |
| if (IS_ERR(hfi->hfi_mem)) |
| return PTR_ERR(hfi->hfi_mem); |
| init_queues(hfi); |
| } |
| |
| if (IS_ERR_OR_NULL(hw_hfi->f2h_task)) { |
| hw_hfi->f2h_task = kthread_run(hfi_f2h_main, adreno_dev, "gmu_f2h"); |
| if (!IS_ERR(hw_hfi->f2h_task)) |
| sched_set_fifo(hw_hfi->f2h_task); |
| } |
| |
| return PTR_ERR_OR_ZERO(hw_hfi->f2h_task); |
| } |
| |
| static int get_attrs(u32 flags) |
| { |
| int attrs = IOMMU_READ; |
| |
| if (flags & HFI_MEMFLAG_GMU_PRIV) |
| attrs |= IOMMU_PRIV; |
| |
| if (flags & HFI_MEMFLAG_GMU_WRITEABLE) |
| attrs |= IOMMU_WRITE; |
| |
| return attrs; |
| } |
| |
| static int gmu_import_buffer(struct adreno_device *adreno_dev, |
| struct hfi_mem_alloc_entry *entry) |
| { |
| struct gen8_gmu_device *gmu = to_gen8_gmu(adreno_dev); |
| struct hfi_mem_alloc_desc *desc = &entry->desc; |
| u32 vma_id = (desc->flags & HFI_MEMFLAG_GMU_CACHEABLE) ? GMU_CACHE : GMU_NONCACHED_KERNEL; |
| |
| return gen8_gmu_import_buffer(gmu, vma_id, entry->md, get_attrs(desc->flags), desc->align); |
| } |
| |
| static struct hfi_mem_alloc_entry *lookup_mem_alloc_table( |
| struct adreno_device *adreno_dev, struct hfi_mem_alloc_desc *desc) |
| { |
| struct gen8_hwsched_hfi *hw_hfi = to_gen8_hwsched_hfi(adreno_dev); |
| int i; |
| |
| for (i = 0; i < hw_hfi->mem_alloc_entries; i++) { |
| struct hfi_mem_alloc_entry *entry = &hw_hfi->mem_alloc_table[i]; |
| |
| if ((entry->desc.mem_kind == desc->mem_kind) && |
| (entry->desc.gmu_mem_handle == desc->gmu_mem_handle)) |
| return entry; |
| } |
| |
| return NULL; |
| } |
| |
| static struct hfi_mem_alloc_entry *get_mem_alloc_entry( |
| struct adreno_device *adreno_dev, struct hfi_mem_alloc_desc *desc) |
| { |
| struct kgsl_device *device = KGSL_DEVICE(adreno_dev); |
| struct gen8_hwsched_hfi *hfi = to_gen8_hwsched_hfi(adreno_dev); |
| struct hfi_mem_alloc_entry *entry = |
| lookup_mem_alloc_table(adreno_dev, desc); |
| struct gen8_gmu_device *gmu = to_gen8_gmu(adreno_dev); |
| u64 flags = 0; |
| u32 priv = 0; |
| int ret; |
| const char *memkind_string = desc->mem_kind < HFI_MEMKIND_MAX ? |
| hfi_memkind_strings[desc->mem_kind] : "UNKNOWN"; |
| |
| if (entry) |
| return entry; |
| |
| if (desc->mem_kind >= HFI_MEMKIND_MAX) { |
| dev_err(&gmu->pdev->dev, "Invalid mem kind: %d\n", |
| desc->mem_kind); |
| return ERR_PTR(-EINVAL); |
| } |
| |
| if (hfi->mem_alloc_entries == ARRAY_SIZE(hfi->mem_alloc_table)) { |
| dev_err(&gmu->pdev->dev, |
| "Reached max mem alloc entries\n"); |
| return ERR_PTR(-ENOMEM); |
| } |
| |
| entry = &hfi->mem_alloc_table[hfi->mem_alloc_entries]; |
| |
| memcpy(&entry->desc, desc, sizeof(*desc)); |
| |
| entry->desc.host_mem_handle = desc->gmu_mem_handle; |
| |
| if (desc->flags & HFI_MEMFLAG_GFX_PRIV) |
| priv |= KGSL_MEMDESC_PRIVILEGED; |
| |
| if (!(desc->flags & HFI_MEMFLAG_GFX_WRITEABLE)) |
| flags |= KGSL_MEMFLAGS_GPUREADONLY; |
| |
| if (desc->flags & HFI_MEMFLAG_GFX_SECURE) |
| flags |= KGSL_MEMFLAGS_SECURE; |
| |
| if (!(desc->flags & HFI_MEMFLAG_GFX_ACC) && |
| (desc->mem_kind != HFI_MEMKIND_HW_FENCE)) { |
| if (desc->mem_kind == HFI_MEMKIND_MMIO_IPC_CORE) |
| entry->md = gen8_reserve_gmu_kernel_block_fixed(gmu, 0, |
| desc->size, |
| (desc->flags & HFI_MEMFLAG_GMU_CACHEABLE) ? |
| GMU_CACHE : GMU_NONCACHED_KERNEL, |
| "qcom,ipc-core", get_attrs(desc->flags), |
| desc->align); |
| else |
| entry->md = gen8_reserve_gmu_kernel_block(gmu, 0, |
| desc->size, |
| (desc->flags & HFI_MEMFLAG_GMU_CACHEABLE) ? |
| GMU_CACHE : GMU_NONCACHED_KERNEL, |
| desc->align); |
| |
| if (IS_ERR(entry->md)) { |
| int ret = PTR_ERR(entry->md); |
| |
| memset(entry, 0, sizeof(*entry)); |
| return ERR_PTR(ret); |
| } |
| entry->desc.size = entry->md->size; |
| entry->desc.gmu_addr = entry->md->gmuaddr; |
| |
| goto done; |
| } |
| |
| /* |
| * Use pre-allocated memory descriptors to map the HFI_MEMKIND_HW_FENCE and |
| * HFI_MEMKIND_MEMSTORE |
| */ |
| switch (desc->mem_kind) { |
| case HFI_MEMKIND_HW_FENCE: |
| entry->md = &adreno_dev->hwsched.hw_fence.memdesc; |
| break; |
| case HFI_MEMKIND_MEMSTORE: |
| entry->md = device->memstore; |
| break; |
| default: |
| entry->md = kgsl_allocate_global(device, desc->size, 0, flags, |
| priv, memkind_string); |
| break; |
| } |
| if (IS_ERR(entry->md)) { |
| int ret = PTR_ERR(entry->md); |
| |
| memset(entry, 0, sizeof(*entry)); |
| return ERR_PTR(ret); |
| } |
| |
| entry->desc.size = entry->md->size; |
| entry->desc.gpu_addr = entry->md->gpuaddr; |
| |
| if (!(desc->flags & HFI_MEMFLAG_GMU_ACC)) |
| goto done; |
| |
| /* |
| * If gmu mapping fails, then we have to live with |
| * leaking the gpu global buffer allocated above. |
| */ |
| ret = gmu_import_buffer(adreno_dev, entry); |
| if (ret) { |
| dev_err(&gmu->pdev->dev, |
| "gpuaddr: 0x%llx size: %lld bytes lost\n", |
| entry->md->gpuaddr, entry->md->size); |
| memset(entry, 0, sizeof(*entry)); |
| return ERR_PTR(ret); |
| } |
| |
| entry->desc.gmu_addr = entry->md->gmuaddr; |
| done: |
| hfi->mem_alloc_entries++; |
| |
| return entry; |
| } |
| |
| static int process_mem_alloc(struct adreno_device *adreno_dev, |
| struct hfi_mem_alloc_desc *mad) |
| { |
| struct hfi_mem_alloc_entry *entry; |
| |
| entry = get_mem_alloc_entry(adreno_dev, mad); |
| if (IS_ERR(entry)) |
| return PTR_ERR(entry); |
| |
| if (entry->md) { |
| mad->gpu_addr = entry->md->gpuaddr; |
| mad->gmu_addr = entry->md->gmuaddr; |
| } |
| |
| /* |
| * GMU uses the host_mem_handle to check if this memalloc was |
| * successful |
| */ |
| mad->host_mem_handle = mad->gmu_mem_handle; |
| |
| return 0; |
| } |
| |
| static int mem_alloc_reply(struct adreno_device *adreno_dev, void *rcvd) |
| { |
| struct hfi_mem_alloc_desc desc = {0}; |
| struct hfi_mem_alloc_reply_cmd out = {0}; |
| struct gen8_gmu_device *gmu = to_gen8_gmu(adreno_dev); |
| u32 seqnum; |
| int ret; |
| |
| hfi_get_mem_alloc_desc(rcvd, &desc); |
| |
| ret = process_mem_alloc(adreno_dev, &desc); |
| if (ret) |
| return ret; |
| |
| memcpy(&out.desc, &desc, sizeof(out.desc)); |
| |
| out.hdr = ACK_MSG_HDR(F2H_MSG_MEM_ALLOC); |
| |
| seqnum = atomic_inc_return(&gmu->hfi.seqnum); |
| out.hdr = MSG_HDR_SET_SEQNUM_SIZE(out.hdr, seqnum, sizeof(out) >> 2); |
| |
| out.req_hdr = *(u32 *)rcvd; |
| |
| return gen8_hfi_cmdq_write(adreno_dev, (u32 *)&out, sizeof(out)); |
| } |
| |
| static int gmu_cntr_register_reply(struct adreno_device *adreno_dev, void *rcvd) |
| { |
| struct hfi_gmu_cntr_register_cmd *in = (struct hfi_gmu_cntr_register_cmd *)rcvd; |
| struct hfi_gmu_cntr_register_reply_cmd out = {0}; |
| struct gen8_gmu_device *gmu = to_gen8_gmu(adreno_dev); |
| u32 lo = 0, hi = 0, seqnum; |
| |
| /* |
| * Failure to allocate counter is not fatal. Sending lo = 0, hi = 0 |
| * indicates to GMU that counter allocation failed. |
| */ |
| adreno_perfcounter_get(adreno_dev, |
| in->group_id, in->countable, &lo, &hi, PERFCOUNTER_FLAG_KERNEL); |
| |
| out.hdr = ACK_MSG_HDR(F2H_MSG_GMU_CNTR_REGISTER); |
| seqnum = atomic_inc_return(&gmu->hfi.seqnum); |
| out.hdr = MSG_HDR_SET_SEQNUM_SIZE(out.hdr, seqnum, sizeof(out) >> 2); |
| out.req_hdr = in->hdr; |
| out.group_id = in->group_id; |
| out.countable = in->countable; |
| /* Fill in byte offset of counter */ |
| out.cntr_lo = lo << 2; |
| out.cntr_hi = hi << 2; |
| |
| return gen8_hfi_cmdq_write(adreno_dev, (u32 *)&out, sizeof(out)); |
| } |
| |
| static int send_warmboot_start_msg(struct adreno_device *adreno_dev) |
| { |
| int ret = 0; |
| struct hfi_start_cmd cmd; |
| |
| if (!adreno_dev->warmboot_enabled) |
| return ret; |
| |
| ret = CMD_MSG_HDR(cmd, H2F_MSG_START); |
| if (ret) |
| return ret; |
| |
| cmd.hdr = RECORD_NOP_MSG_HDR(cmd.hdr); |
| |
| return gen8_hfi_send_generic_req(adreno_dev, &cmd, sizeof(cmd)); |
| } |
| |
| static int send_start_msg(struct adreno_device *adreno_dev) |
| { |
| struct gen8_gmu_device *gmu = to_gen8_gmu(adreno_dev); |
| struct kgsl_device *device = KGSL_DEVICE(adreno_dev); |
| int ret, rc = 0; |
| struct hfi_start_cmd cmd; |
| u32 seqnum, rcvd[MAX_RCVD_SIZE]; |
| struct pending_cmd pending_ack = {0}; |
| |
| ret = CMD_MSG_HDR(cmd, H2F_MSG_START); |
| if (ret) |
| return ret; |
| |
| seqnum = atomic_inc_return(&gmu->hfi.seqnum); |
| cmd.hdr = MSG_HDR_SET_SEQNUM_SIZE(cmd.hdr, seqnum, sizeof(cmd) >> 2); |
| |
| pending_ack.sent_hdr = cmd.hdr; |
| |
| rc = gen8_hfi_cmdq_write(adreno_dev, (u32 *)&cmd, sizeof(cmd)); |
| if (rc) |
| return rc; |
| |
| poll: |
| rc = gmu_core_timed_poll_check(device, GEN8_GMUCX_GMU2HOST_INTR_INFO, |
| HFI_IRQ_MSGQ_MASK, HFI_RSP_TIMEOUT, HFI_IRQ_MSGQ_MASK); |
| |
| if (rc) { |
| dev_err(&gmu->pdev->dev, |
| "Timed out processing MSG_START seqnum: %d\n", |
| seqnum); |
| gmu_core_fault_snapshot(device); |
| return rc; |
| } |
| |
| /* Clear the interrupt */ |
| gmu_core_regwrite(device, GEN8_GMUCX_GMU2HOST_INTR_CLR, |
| HFI_IRQ_MSGQ_MASK); |
| |
| if (gen8_hfi_queue_read(gmu, HFI_MSG_ID, rcvd, sizeof(rcvd)) <= 0) { |
| dev_err(&gmu->pdev->dev, "MSG_START: no payload\n"); |
| gmu_core_fault_snapshot(device); |
| return -EINVAL; |
| } |
| |
| if (MSG_HDR_GET_TYPE(rcvd[0]) == HFI_MSG_ACK) { |
| rc = gen8_receive_ack_cmd(gmu, rcvd, &pending_ack); |
| if (rc) |
| return rc; |
| |
| return check_ack_failure(adreno_dev, &pending_ack); |
| } |
| |
| if (MSG_HDR_GET_ID(rcvd[0]) == F2H_MSG_MEM_ALLOC) { |
| rc = mem_alloc_reply(adreno_dev, rcvd); |
| if (rc) |
| return rc; |
| |
| goto poll; |
| } |
| |
| if (MSG_HDR_GET_ID(rcvd[0]) == F2H_MSG_GMU_CNTR_REGISTER) { |
| rc = gmu_cntr_register_reply(adreno_dev, rcvd); |
| if (rc) |
| return rc; |
| goto poll; |
| } |
| |
| dev_err(&gmu->pdev->dev, |
| "MSG_START: unexpected response id:%d, type:%d\n", |
| MSG_HDR_GET_ID(rcvd[0]), |
| MSG_HDR_GET_TYPE(rcvd[0])); |
| |
| gmu_core_fault_snapshot(device); |
| |
| return rc; |
| } |
| |
| static void reset_hfi_mem_records(struct adreno_device *adreno_dev) |
| { |
| struct gen8_hwsched_hfi *hw_hfi = to_gen8_hwsched_hfi(adreno_dev); |
| struct kgsl_memdesc *md = NULL; |
| u32 i; |
| |
| for (i = 0; i < hw_hfi->mem_alloc_entries; i++) { |
| struct hfi_mem_alloc_desc *desc = &hw_hfi->mem_alloc_table[i].desc; |
| |
| if (desc->flags & HFI_MEMFLAG_HOST_INIT) { |
| md = hw_hfi->mem_alloc_table[i].md; |
| memset(md->hostptr, 0x0, md->size); |
| } |
| } |
| } |
| |
| static void reset_hfi_queues(struct adreno_device *adreno_dev) |
| { |
| struct gen8_gmu_device *gmu = to_gen8_gmu(adreno_dev); |
| struct hfi_queue_table *tbl = gmu->hfi.hfi_mem->hostptr; |
| u32 i; |
| |
| /* Flush HFI queues */ |
| for (i = 0; i < HFI_QUEUE_MAX; i++) { |
| struct hfi_queue_header *hdr = &tbl->qhdr[i]; |
| |
| if (hdr->status == HFI_QUEUE_STATUS_DISABLED) |
| continue; |
| |
| hdr->read_index = hdr->write_index; |
| } |
| } |
| |
| void gen8_hwsched_hfi_stop(struct adreno_device *adreno_dev) |
| { |
| struct gen8_gmu_device *gmu = to_gen8_gmu(adreno_dev); |
| struct gen8_hwsched_hfi *hfi = to_gen8_hwsched_hfi(adreno_dev); |
| |
| hfi->irq_mask &= ~HFI_IRQ_MSGQ_MASK; |
| |
| /* |
| * In some corner cases, it is possible that GMU put TS_RETIRE |
| * on the msgq after we have turned off gmu interrupts. Hence, |
| * drain the queue one last time before we reset HFI queues. |
| */ |
| gen8_hwsched_process_msgq(adreno_dev); |
| |
| /* Drain the debug queue before we reset HFI queues */ |
| gen8_hwsched_process_dbgq(adreno_dev, false); |
| |
| kgsl_pwrctrl_axi(KGSL_DEVICE(adreno_dev), false); |
| |
| clear_bit(GMU_PRIV_HFI_STARTED, &gmu->flags); |
| |
| /* |
| * Reset the hfi host access memory records, As GMU expects hfi memory |
| * records to be clear in bootup. |
| */ |
| reset_hfi_mem_records(adreno_dev); |
| } |
| |
| static void gen8_hwsched_enable_async_hfi(struct adreno_device *adreno_dev) |
| { |
| struct gen8_hwsched_hfi *hfi = to_gen8_hwsched_hfi(adreno_dev); |
| |
| hfi->irq_mask |= HFI_IRQ_MSGQ_MASK; |
| |
| gmu_core_regwrite(KGSL_DEVICE(adreno_dev), GEN8_GMUCX_GMU2HOST_INTR_MASK, |
| (u32)~hfi->irq_mask); |
| } |
| |
| static int enable_preemption(struct adreno_device *adreno_dev) |
| { |
| const struct adreno_gen8_core *gen8_core = to_gen8_core(adreno_dev); |
| struct kgsl_device *device = KGSL_DEVICE(adreno_dev); |
| u32 data; |
| int ret; |
| |
| if (!adreno_is_preemption_enabled(adreno_dev)) |
| return 0; |
| |
| /* |
| * Bits [0:1] contains the preemption level |
| * Bit 2 is to enable/disable gmem save/restore |
| * Bit 3 is to enable/disable skipsaverestore |
| */ |
| data = FIELD_PREP(GENMASK(1, 0), adreno_dev->preempt.preempt_level) | |
| FIELD_PREP(BIT(2), adreno_dev->preempt.usesgmem) | |
| FIELD_PREP(BIT(3), adreno_dev->preempt.skipsaverestore); |
| |
| ret = gen8_hfi_send_feature_ctrl(adreno_dev, HFI_FEATURE_PREEMPTION, 1, |
| data); |
| if (ret) |
| return ret; |
| |
| if (gen8_core->qos_value) { |
| int i; |
| |
| for (i = 0; i < KGSL_PRIORITY_MAX_RB_LEVELS; i++) { |
| if (!gen8_core->qos_value[i]) |
| continue; |
| |
| gen8_hfi_send_set_value(adreno_dev, |
| HFI_VALUE_RB_GPU_QOS, i, |
| gen8_core->qos_value[i]); |
| } |
| } |
| |
| if (device->pwrctrl.rt_bus_hint) { |
| ret = gen8_hfi_send_set_value(adreno_dev, HFI_VALUE_RB_IB_RULE, 0, |
| device->pwrctrl.rt_bus_hint); |
| if (ret) |
| device->pwrctrl.rt_bus_hint = 0; |
| } |
| |
| /* |
| * Bits[3:0] contain the preemption timeout enable bit per ringbuffer |
| * Bits[31:4] contain the timeout in ms |
| */ |
| return gen8_hfi_send_set_value(adreno_dev, HFI_VALUE_BIN_TIME, 1, |
| FIELD_PREP(GENMASK(31, 4), ADRENO_PREEMPT_TIMEOUT) | |
| FIELD_PREP(GENMASK(3, 0), 0xf)); |
| |
| } |
| |
| static int enable_gmu_stats(struct adreno_device *adreno_dev) |
| { |
| struct gen8_gmu_device *gmu = to_gen8_gmu(adreno_dev); |
| u32 data; |
| |
| if (!gmu->stats_enable) |
| return 0; |
| |
| /* |
| * Bits [23:0] contains the countables mask |
| * Bits [31:24] is the sampling interval |
| */ |
| data = FIELD_PREP(GENMASK(23, 0), gmu->stats_mask) | |
| FIELD_PREP(GENMASK(31, 24), gmu->stats_interval); |
| |
| return gen8_hfi_send_feature_ctrl(adreno_dev, HFI_FEATURE_GMU_STATS, 1, data); |
| } |
| |
| static int gen8_hfi_send_perfcounter_feature_ctrl(struct adreno_device *adreno_dev) |
| { |
| /* |
| * Perfcounter retention is disabled by default in GMU firmware. |
| * In case perfcounter retention behaviour is overwritten by sysfs |
| * setting dynmaically, send this HFI feature with 'enable = 0' to |
| * disable this feature in GMU firmware. |
| */ |
| if (adreno_dev->perfcounter) |
| return gen8_hfi_send_feature_ctrl(adreno_dev, |
| HFI_FEATURE_PERF_NORETAIN, 0, 0); |
| |
| return 0; |
| } |
| |
| u32 gen8_hwsched_hfi_get_value(struct adreno_device *adreno_dev, u32 prop) |
| { |
| struct hfi_get_value_cmd cmd; |
| struct gen8_gmu_device *gmu = to_gen8_gmu(adreno_dev); |
| struct gen8_hwsched_hfi *hfi = to_gen8_hwsched_hfi(adreno_dev); |
| struct pending_cmd pending_ack; |
| u32 seqnum; |
| int rc; |
| |
| rc = CMD_MSG_HDR(cmd, H2F_MSG_GET_VALUE); |
| if (rc) |
| return 0; |
| |
| seqnum = atomic_inc_return(&gmu->hfi.seqnum); |
| cmd.hdr = MSG_HDR_SET_SEQNUM_SIZE(cmd.hdr, seqnum, sizeof(cmd) >> 2); |
| cmd.type = prop; |
| cmd.subtype = 0; |
| |
| add_waiter(hfi, cmd.hdr, &pending_ack); |
| |
| rc = gen8_hfi_cmdq_write(adreno_dev, (u32 *)&cmd, sizeof(cmd)); |
| if (rc) |
| goto done; |
| |
| rc = adreno_hwsched_wait_ack_completion(adreno_dev, &gmu->pdev->dev, &pending_ack, |
| gen8_hwsched_process_msgq); |
| |
| done: |
| del_waiter(hfi, &pending_ack); |
| |
| if (rc || (pending_ack.results[2] == UINT_MAX)) |
| return 0; |
| |
| return pending_ack.results[2]; |
| } |
| |
| static void _context_queue_enable(struct adreno_device *adreno_dev) |
| { |
| struct gen8_gmu_device *gmu = to_gen8_gmu(adreno_dev); |
| |
| if (GMU_VER_MINOR(gmu->ver.hfi) >= 3) { |
| if (gen8_hfi_send_get_value(adreno_dev, HFI_VALUE_CONTEXT_QUEUE, 0) == 1) |
| set_bit(ADRENO_HWSCHED_CONTEXT_QUEUE, &adreno_dev->hwsched.flags); |
| } |
| } |
| |
| static int gen8_hfi_send_hw_fence_feature_ctrl(struct adreno_device *adreno_dev) |
| { |
| struct gen8_gmu_device *gmu = to_gen8_gmu(adreno_dev); |
| struct adreno_hwsched *hwsched = &adreno_dev->hwsched; |
| int ret; |
| |
| if (!test_bit(ADRENO_HWSCHED_HW_FENCE, &hwsched->flags)) |
| return 0; |
| |
| ret = gen8_hfi_send_feature_ctrl(adreno_dev, HFI_FEATURE_HW_FENCE, 1, 0); |
| if (ret && (ret == -ENOENT)) { |
| dev_err(&gmu->pdev->dev, "GMU doesn't support HW_FENCE feature\n"); |
| adreno_hwsched_deregister_hw_fence(hwsched->hw_fence.handle); |
| return 0; |
| } |
| |
| return ret; |
| } |
| |
| static void gen8_spin_idle_debug_lpac(struct adreno_device *adreno_dev, |
| const char *str) |
| { |
| struct kgsl_device *device = &adreno_dev->dev; |
| u32 rptr, wptr, status, intstatus, global_status; |
| bool val = adreno_is_preemption_enabled(adreno_dev); |
| |
| dev_err(device->dev, str); |
| |
| kgsl_regread(device, GEN8_CP_RB_RPTR_LPAC, &rptr); |
| kgsl_regread(device, GEN8_CP_RB_WPTR_LPAC, &wptr); |
| |
| kgsl_regread(device, GEN8_RBBM_STATUS, &status); |
| kgsl_regread(device, GEN8_RBBM_INT_0_STATUS, &intstatus); |
| kgsl_regread(device, GEN8_CP_INTERRUPT_STATUS_GLOBAL, &global_status); |
| |
| dev_err(device->dev, |
| "LPAC rb=%d pos=%X/%X rbbm_status=%8.8X int_0_status=%8.8X global_status=%8.8X\n", |
| val ? KGSL_LPAC_RB_ID : 1, rptr, wptr, |
| status, intstatus, global_status); |
| |
| kgsl_device_snapshot(device, NULL, NULL, false); |
| } |
| |
| static bool gen8_hwsched_warmboot_possible(struct adreno_device *adreno_dev) |
| { |
| struct gen8_gmu_device *gmu = to_gen8_gmu(adreno_dev); |
| |
| if (adreno_dev->warmboot_enabled && test_bit(GMU_PRIV_WARMBOOT_GMU_INIT_DONE, &gmu->flags) |
| && test_bit(GMU_PRIV_WARMBOOT_GPU_BOOT_DONE, &gmu->flags) && |
| !test_bit(ADRENO_DEVICE_FORCE_COLDBOOT, &adreno_dev->priv)) |
| return true; |
| |
| return false; |
| } |
| |
| static int gen8_hwsched_hfi_send_warmboot_cmd(struct adreno_device *adreno_dev, |
| struct kgsl_memdesc *desc, u32 flag, bool async, struct pending_cmd *ack) |
| { |
| struct hfi_warmboot_scratch_cmd cmd = {0}; |
| int ret; |
| |
| if (!adreno_dev->warmboot_enabled) |
| return 0; |
| |
| cmd.scratch_addr = desc->gmuaddr; |
| cmd.scratch_size = desc->size; |
| cmd.flags = flag; |
| |
| ret = CMD_MSG_HDR(cmd, H2F_MSG_WARMBOOT_CMD); |
| if (ret) |
| return ret; |
| |
| if (async) |
| return gen8_hfi_send_cmd_async(adreno_dev, &cmd, sizeof(cmd)); |
| |
| return gen8_hfi_send_generic_req_v5(adreno_dev, &cmd, ack, sizeof(cmd)); |
| } |
| |
| static int gen8_hwsched_hfi_warmboot_gpu_cmd(struct adreno_device *adreno_dev, |
| struct pending_cmd *ret_cmd) |
| { |
| struct gen8_gmu_device *gmu = to_gen8_gmu(adreno_dev); |
| struct gen8_hwsched_hfi *hfi = to_gen8_hwsched_hfi(adreno_dev); |
| struct hfi_warmboot_scratch_cmd cmd = { |
| .scratch_addr = gmu->gpu_boot_scratch->gmuaddr, |
| .scratch_size = gmu->gpu_boot_scratch->size, |
| .flags = HFI_WARMBOOT_EXEC_SCRATCH, |
| }; |
| int ret = 0; |
| u32 seqnum; |
| |
| if (!adreno_dev->warmboot_enabled) |
| return 0; |
| |
| ret = CMD_MSG_HDR(cmd, H2F_MSG_WARMBOOT_CMD); |
| if (ret) |
| return ret; |
| |
| seqnum = atomic_inc_return(&gmu->hfi.seqnum); |
| cmd.hdr = MSG_HDR_SET_SEQNUM_SIZE(cmd.hdr, seqnum, sizeof(cmd) >> 2); |
| add_waiter(hfi, cmd.hdr, ret_cmd); |
| |
| ret = gen8_hfi_cmdq_write(adreno_dev, (u32 *)&cmd, sizeof(cmd)); |
| if (ret) |
| goto err; |
| |
| ret = adreno_hwsched_wait_ack_completion(adreno_dev, &gmu->pdev->dev, ret_cmd, |
| gen8_hwsched_process_msgq); |
| err: |
| del_waiter(hfi, ret_cmd); |
| |
| return ret; |
| } |
| |
| static int gen8_hwsched_warmboot_gpu(struct adreno_device *adreno_dev) |
| { |
| struct gen8_gmu_device *gmu = to_gen8_gmu(adreno_dev); |
| struct pending_cmd ret_cmd = {0}; |
| int ret = 0; |
| |
| ret = gen8_hwsched_hfi_warmboot_gpu_cmd(adreno_dev, &ret_cmd); |
| if (!ret) |
| return ret; |
| |
| if (MSG_HDR_GET_TYPE(ret_cmd.results[1]) != H2F_MSG_WARMBOOT_CMD) |
| goto err; |
| |
| switch (MSG_HDR_GET_TYPE(ret_cmd.results[2])) { |
| case H2F_MSG_ISSUE_CMD_RAW: { |
| if (ret_cmd.results[2] == gmu->cp_init_hdr) |
| gen8_spin_idle_debug(adreno_dev, |
| "CP initialization failed to idle\n"); |
| else if (ret_cmd.results[2] == gmu->switch_to_unsec_hdr) |
| gen8_spin_idle_debug(adreno_dev, |
| "Switch to unsecure failed to idle\n"); |
| } |
| break; |
| case H2F_MSG_ISSUE_LPAC_CMD_RAW: |
| gen8_spin_idle_debug_lpac(adreno_dev, |
| "LPAC CP initialization failed to idle\n"); |
| break; |
| } |
| err: |
| /* Clear the bit on error so that in the next slumber exit we coldboot */ |
| clear_bit(GMU_PRIV_WARMBOOT_GPU_BOOT_DONE, &gmu->flags); |
| gen8_disable_gpu_irq(adreno_dev); |
| return ret; |
| } |
| |
| static int gen8_hwsched_coldboot_gpu(struct adreno_device *adreno_dev) |
| { |
| struct gen8_gmu_device *gmu = to_gen8_gmu(adreno_dev); |
| struct gen8_hfi *hfi = to_gen8_hfi(adreno_dev); |
| struct pending_cmd ack = {0}; |
| int ret = 0; |
| |
| ret = gen8_hwsched_hfi_send_warmboot_cmd(adreno_dev, gmu->gpu_boot_scratch, |
| HFI_WARMBOOT_SET_SCRATCH, true, &ack); |
| if (ret) |
| goto done; |
| |
| ret = gen8_hwsched_cp_init(adreno_dev); |
| if (ret) |
| goto done; |
| |
| ret = gen8_hwsched_lpac_cp_init(adreno_dev); |
| if (ret) |
| goto done; |
| |
| ret = gen8_hwsched_hfi_send_warmboot_cmd(adreno_dev, gmu->gpu_boot_scratch, |
| HFI_WARMBOOT_QUERY_SCRATCH, true, &ack); |
| if (ret) |
| goto done; |
| |
| if (adreno_dev->warmboot_enabled) |
| set_bit(GMU_PRIV_WARMBOOT_GPU_BOOT_DONE, &gmu->flags); |
| |
| done: |
| /* Clear the bitmask so that we don't send record bit with future HFI messages */ |
| memset(hfi->wb_set_record_bitmask, 0x0, sizeof(hfi->wb_set_record_bitmask)); |
| |
| if (ret) |
| gen8_disable_gpu_irq(adreno_dev); |
| |
| return ret; |
| } |
| |
| int gen8_hwsched_boot_gpu(struct adreno_device *adreno_dev) |
| { |
| /* If warmboot is possible just send the warmboot command else coldboot */ |
| if (gen8_hwsched_warmboot_possible(adreno_dev)) |
| return gen8_hwsched_warmboot_gpu(adreno_dev); |
| else |
| return gen8_hwsched_coldboot_gpu(adreno_dev); |
| } |
| |
| static int gen8_hwsched_setup_default_votes(struct adreno_device *adreno_dev) |
| { |
| struct kgsl_device *device = KGSL_DEVICE(adreno_dev); |
| int ret = 0; |
| |
| /* Request default DCVS level */ |
| ret = kgsl_pwrctrl_set_default_gpu_pwrlevel(device); |
| if (ret) |
| return ret; |
| |
| /* Request default BW vote */ |
| return kgsl_pwrctrl_axi(device, true); |
| } |
| |
| int gen8_hwsched_warmboot_init_gmu(struct adreno_device *adreno_dev) |
| { |
| struct gen8_gmu_device *gmu = to_gen8_gmu(adreno_dev); |
| struct pending_cmd ack = {0}; |
| int ret = 0; |
| |
| ret = gen8_hwsched_hfi_send_warmboot_cmd(adreno_dev, gmu->gmu_init_scratch, |
| HFI_WARMBOOT_EXEC_SCRATCH, false, &ack); |
| if (ret) |
| goto err; |
| |
| gen8_hwsched_enable_async_hfi(adreno_dev); |
| |
| set_bit(GMU_PRIV_HFI_STARTED, &gmu->flags); |
| |
| ret = gen8_hwsched_setup_default_votes(adreno_dev); |
| |
| err: |
| if (ret) { |
| /* Clear the bit in case of an error so next boot will be coldboot */ |
| clear_bit(GMU_PRIV_WARMBOOT_GMU_INIT_DONE, &gmu->flags); |
| clear_bit(GMU_PRIV_WARMBOOT_GPU_BOOT_DONE, &gmu->flags); |
| gen8_hwsched_hfi_stop(adreno_dev); |
| } |
| |
| return ret; |
| } |
| |
| static void warmboot_init_message_record_bitmask(struct adreno_device *adreno_dev) |
| { |
| struct gen8_hfi *hfi = to_gen8_hfi(adreno_dev); |
| |
| if (!adreno_dev->warmboot_enabled) |
| return; |
| |
| /* Set the record bit for all the messages */ |
| memset(hfi->wb_set_record_bitmask, 0xFF, sizeof(hfi->wb_set_record_bitmask)); |
| |
| /* These messages should not be recorded */ |
| clear_bit(H2F_MSG_WARMBOOT_CMD, hfi->wb_set_record_bitmask); |
| clear_bit(H2F_MSG_START, hfi->wb_set_record_bitmask); |
| clear_bit(H2F_MSG_GET_VALUE, hfi->wb_set_record_bitmask); |
| clear_bit(H2F_MSG_GX_BW_PERF_VOTE, hfi->wb_set_record_bitmask); |
| } |
| |
| int gen8_hwsched_hfi_start(struct adreno_device *adreno_dev) |
| { |
| struct gen8_gmu_device *gmu = to_gen8_gmu(adreno_dev); |
| struct pending_cmd ack = {0}; |
| int ret; |
| |
| reset_hfi_queues(adreno_dev); |
| |
| ret = gen8_gmu_hfi_start(adreno_dev); |
| if (ret) |
| goto err; |
| |
| if (gen8_hwsched_warmboot_possible(adreno_dev)) |
| return gen8_hwsched_warmboot_init_gmu(adreno_dev); |
| |
| if (ADRENO_FEATURE(adreno_dev, ADRENO_GMU_WARMBOOT) && |
| (!test_bit(GMU_PRIV_FIRST_BOOT_DONE, &gmu->flags))) { |
| if (gen8_hfi_send_get_value(adreno_dev, HFI_VALUE_GMU_WARMBOOT, 0) == 1) |
| adreno_dev->warmboot_enabled = true; |
| } |
| |
| warmboot_init_message_record_bitmask(adreno_dev); |
| |
| /* Reset the variable here and set it when we successfully record the scratch */ |
| clear_bit(GMU_PRIV_WARMBOOT_GMU_INIT_DONE, &gmu->flags); |
| clear_bit(GMU_PRIV_WARMBOOT_GPU_BOOT_DONE, &gmu->flags); |
| |
| ret = gen8_hwsched_hfi_send_warmboot_cmd(adreno_dev, gmu->gmu_init_scratch, |
| HFI_WARMBOOT_SET_SCRATCH, false, &ack); |
| if (ret) |
| goto err; |
| |
| ret = gen8_hfi_send_gpu_perf_table(adreno_dev); |
| if (ret) |
| goto err; |
| |
| ret = gen8_hfi_send_generic_req(adreno_dev, &gmu->hfi.bw_table, sizeof(gmu->hfi.bw_table)); |
| if (ret) |
| goto err; |
| |
| ret = gen8_hfi_send_acd_feature_ctrl(adreno_dev); |
| if (ret) |
| goto err; |
| |
| ret = gen8_hfi_send_bcl_feature_ctrl(adreno_dev); |
| if (ret) |
| goto err; |
| |
| ret = gen8_hfi_send_clx_feature_ctrl(adreno_dev); |
| if (ret) |
| goto err; |
| |
| ret = gen8_hfi_send_ifpc_feature_ctrl(adreno_dev); |
| if (ret) |
| goto err; |
| |
| ret = gen8_hfi_send_feature_ctrl(adreno_dev, HFI_FEATURE_HWSCHED, 1, 0); |
| if (ret) |
| goto err; |
| |
| ret = gen8_hfi_send_feature_ctrl(adreno_dev, HFI_FEATURE_KPROF, 1, 0); |
| if (ret) |
| goto err; |
| |
| if (ADRENO_FEATURE(adreno_dev, ADRENO_LSR)) { |
| ret = gen8_hfi_send_feature_ctrl(adreno_dev, HFI_FEATURE_LSR, |
| 1, 0); |
| if (ret) |
| goto err; |
| } |
| |
| ret = gen8_hfi_send_perfcounter_feature_ctrl(adreno_dev); |
| if (ret) |
| goto err; |
| |
| /* Enable the long ib timeout detection */ |
| if (adreno_long_ib_detect(adreno_dev)) { |
| ret = gen8_hfi_send_feature_ctrl(adreno_dev, |
| HFI_FEATURE_BAIL_OUT_TIMER, 1, 0); |
| if (ret) |
| goto err; |
| } |
| |
| enable_gmu_stats(adreno_dev); |
| |
| if (gmu->log_stream_enable) |
| gen8_hfi_send_set_value(adreno_dev, |
| HFI_VALUE_LOG_STREAM_ENABLE, 0, 1); |
| |
| if (gmu->log_group_mask) |
| gen8_hfi_send_set_value(adreno_dev, |
| HFI_VALUE_LOG_GROUP, 0, gmu->log_group_mask); |
| |
| ret = gen8_hfi_send_core_fw_start(adreno_dev); |
| if (ret) |
| goto err; |
| |
| /* |
| * HFI_VALUE_CONTEXT_QUEUE can only be queried after GMU has initialized some of the |
| * required resources as part of handling gen8_hfi_send_core_fw_start() |
| */ |
| if (!test_bit(GMU_PRIV_FIRST_BOOT_DONE, &gmu->flags)) { |
| _context_queue_enable(adreno_dev); |
| adreno_hwsched_register_hw_fence(adreno_dev); |
| } |
| |
| ret = gen8_hfi_send_hw_fence_feature_ctrl(adreno_dev); |
| if (ret) |
| goto err; |
| |
| ret = enable_preemption(adreno_dev); |
| if (ret) |
| goto err; |
| |
| ret = gen8_hfi_send_lpac_feature_ctrl(adreno_dev); |
| if (ret) |
| goto err; |
| |
| if (ADRENO_FEATURE(adreno_dev, ADRENO_AQE)) { |
| ret = gen8_hfi_send_feature_ctrl(adreno_dev, HFI_FEATURE_AQE, 1, 0); |
| if (ret) |
| goto err; |
| } |
| |
| ret = send_start_msg(adreno_dev); |
| if (ret) |
| goto err; |
| |
| /* |
| * Send this additional start message on cold boot if warmboot is enabled. |
| * This message will be recorded and on a warmboot this will trigger the |
| * sequence to replay memory allocation requests and ECP task setup |
| */ |
| ret = send_warmboot_start_msg(adreno_dev); |
| if (ret) |
| goto err; |
| |
| gen8_hwsched_enable_async_hfi(adreno_dev); |
| |
| set_bit(GMU_PRIV_HFI_STARTED, &gmu->flags); |
| |
| /* Send this message only on cold boot */ |
| ret = gen8_hwsched_hfi_send_warmboot_cmd(adreno_dev, gmu->gmu_init_scratch, |
| HFI_WARMBOOT_QUERY_SCRATCH, true, &ack); |
| if (ret) |
| goto err; |
| |
| if (adreno_dev->warmboot_enabled) |
| set_bit(GMU_PRIV_WARMBOOT_GMU_INIT_DONE, &gmu->flags); |
| |
| ret = gen8_hwsched_setup_default_votes(adreno_dev); |
| |
| err: |
| if (ret) |
| gen8_hwsched_hfi_stop(adreno_dev); |
| |
| return ret; |
| } |
| |
| static int submit_raw_cmds(struct adreno_device *adreno_dev, void *cmds, u32 size_bytes, |
| const char *str) |
| { |
| int ret; |
| |
| ret = gen8_hfi_send_cmd_async(adreno_dev, cmds, size_bytes); |
| if (ret) |
| return ret; |
| |
| ret = gmu_core_timed_poll_check(KGSL_DEVICE(adreno_dev), |
| GEN8_GMUAO_GPU_CX_BUSY_STATUS, 0, 200, BIT(23)); |
| if (ret) |
| gen8_spin_idle_debug(adreno_dev, str); |
| |
| return ret; |
| } |
| |
| static int submit_lpac_raw_cmds(struct adreno_device *adreno_dev, void *cmds, u32 size_bytes, |
| const char *str) |
| { |
| int ret; |
| |
| ret = gen8_hfi_send_cmd_async(adreno_dev, cmds, size_bytes); |
| if (ret) |
| return ret; |
| |
| ret = gmu_core_timed_poll_check(KGSL_DEVICE(adreno_dev), |
| GEN8_GMUAO_LPAC_BUSY_STATUS, 0, 200, BIT(23)); |
| if (ret) |
| gen8_spin_idle_debug_lpac(adreno_dev, str); |
| |
| return ret; |
| } |
| |
| static int cp_init(struct adreno_device *adreno_dev) |
| { |
| struct gen8_gmu_device *gmu = to_gen8_gmu(adreno_dev); |
| u32 cmds[GEN8_CP_INIT_DWORDS + 1]; |
| int ret = 0; |
| |
| cmds[0] = CREATE_MSG_HDR(H2F_MSG_ISSUE_CMD_RAW, HFI_MSG_CMD); |
| |
| gen8_cp_init_cmds(adreno_dev, &cmds[1]); |
| |
| ret = submit_raw_cmds(adreno_dev, cmds, sizeof(cmds), |
| "CP initialization failed to idle\n"); |
| |
| /* Save the header incase we need a warmboot debug */ |
| gmu->cp_init_hdr = cmds[0]; |
| |
| return ret; |
| } |
| |
| static int send_switch_to_unsecure(struct adreno_device *adreno_dev) |
| { |
| struct gen8_gmu_device *gmu = to_gen8_gmu(adreno_dev); |
| u32 cmds[3]; |
| int ret = 0; |
| |
| cmds[0] = CREATE_MSG_HDR(H2F_MSG_ISSUE_CMD_RAW, HFI_MSG_CMD); |
| |
| cmds[1] = cp_type7_packet(CP_SET_SECURE_MODE, 1); |
| cmds[2] = 0; |
| |
| ret = submit_raw_cmds(adreno_dev, cmds, sizeof(cmds), |
| "Switch to unsecure failed to idle\n"); |
| |
| /* Save the header incase we need a warmboot debug */ |
| gmu->switch_to_unsec_hdr = cmds[0]; |
| |
| return ret; |
| } |
| |
| int gen8_hwsched_cp_init(struct adreno_device *adreno_dev) |
| { |
| const struct adreno_gen8_core *gen8_core = to_gen8_core(adreno_dev); |
| int ret; |
| |
| ret = cp_init(adreno_dev); |
| if (ret) |
| return ret; |
| |
| ret = adreno_zap_shader_load(adreno_dev, gen8_core->zap_name); |
| if (ret) |
| return ret; |
| |
| if (!adreno_dev->zap_loaded) |
| kgsl_regwrite(KGSL_DEVICE(adreno_dev), |
| GEN8_RBBM_SECVID_TRUST_CNTL, 0x0); |
| else |
| ret = send_switch_to_unsecure(adreno_dev); |
| |
| return ret; |
| } |
| |
| int gen8_hwsched_lpac_cp_init(struct adreno_device *adreno_dev) |
| { |
| u32 cmds[GEN8_CP_INIT_DWORDS + 1]; |
| |
| if (!adreno_dev->lpac_enabled) |
| return 0; |
| |
| cmds[0] = CREATE_MSG_HDR(H2F_MSG_ISSUE_LPAC_CMD_RAW, HFI_MSG_CMD); |
| |
| gen8_cp_init_cmds(adreno_dev, &cmds[1]); |
| |
| return submit_lpac_raw_cmds(adreno_dev, cmds, sizeof(cmds), |
| "LPAC CP initialization failed to idle\n"); |
| } |
| |
| static bool is_queue_empty(struct adreno_device *adreno_dev, u32 queue_idx) |
| { |
| struct gen8_gmu_device *gmu = to_gen8_gmu(adreno_dev); |
| struct kgsl_memdesc *mem_addr = gmu->hfi.hfi_mem; |
| struct hfi_queue_table *tbl = mem_addr->hostptr; |
| struct hfi_queue_header *hdr = &tbl->qhdr[queue_idx]; |
| |
| if (hdr->status == HFI_QUEUE_STATUS_DISABLED) |
| return true; |
| |
| if (hdr->read_index == hdr->write_index) |
| return true; |
| |
| return false; |
| } |
| |
| static int hfi_f2h_main(void *arg) |
| { |
| struct adreno_device *adreno_dev = arg; |
| struct gen8_hwsched_hfi *hfi = to_gen8_hwsched_hfi(adreno_dev); |
| struct gen8_gmu_device *gmu = to_gen8_gmu(adreno_dev); |
| |
| while (!kthread_should_stop()) { |
| wait_event_interruptible(hfi->f2h_wq, kthread_should_stop() || |
| /* If msgq irq is enabled and msgq has messages to process */ |
| (((hfi->irq_mask & HFI_IRQ_MSGQ_MASK) && |
| !is_queue_empty(adreno_dev, HFI_MSG_ID)) || |
| /* Trace buffer has messages to process */ |
| !gmu_core_is_trace_empty(gmu->trace.md->hostptr) || |
| /* Dbgq has messages to process */ |
| !is_queue_empty(adreno_dev, HFI_DBG_ID))); |
| |
| if (kthread_should_stop()) |
| break; |
| |
| gen8_hwsched_process_msgq(adreno_dev); |
| gmu_core_process_trace_data(KGSL_DEVICE(adreno_dev), |
| &gmu->pdev->dev, &gmu->trace); |
| gen8_hwsched_process_dbgq(adreno_dev, true); |
| } |
| |
| return 0; |
| } |
| |
| static void gen8_hwsched_hw_fence_timeout(struct work_struct *work) |
| { |
| struct gen8_hwsched_hfi *hfi = container_of(work, struct gen8_hwsched_hfi, hw_fence_ws); |
| struct gen8_hwsched_device *gen8_hw_dev = container_of(hfi, struct gen8_hwsched_device, |
| hwsched_hfi); |
| struct adreno_device *adreno_dev = &gen8_hw_dev->gen8_dev.adreno_dev; |
| struct gen8_gmu_device *gmu = to_gen8_gmu(adreno_dev); |
| u32 unack_count, ts; |
| struct adreno_context *drawctxt = NULL; |
| bool fault; |
| |
| /* Check msgq one last time before recording a fault */ |
| gen8_hwsched_process_msgq(adreno_dev); |
| |
| spin_lock(&hfi->hw_fence.lock); |
| |
| unack_count = hfi->hw_fence.unack_count; |
| |
| fault = test_bit(GEN8_HWSCHED_HW_FENCE_SLEEP_BIT, &hfi->hw_fence.flags) && |
| test_bit(GEN8_HWSCHED_HW_FENCE_MAX_BIT, &hfi->hw_fence.flags) && |
| (unack_count > MIN_HW_FENCE_UNACK_COUNT); |
| |
| drawctxt = hfi->hw_fence.defer_drawctxt; |
| ts = hfi->hw_fence.defer_ts; |
| |
| spin_unlock(&hfi->hw_fence.lock); |
| |
| if (!fault) |
| return; |
| |
| dev_err(&gmu->pdev->dev, "Hardware fence unack(%d) timeout\n", unack_count); |
| |
| if (drawctxt) { |
| struct kgsl_process_private *proc_priv = drawctxt->base.proc_priv; |
| |
| dev_err(&gmu->pdev->dev, |
| "Hardware fence got deferred for ctx:%d ts:%d pid:%d proc:%s\n", |
| drawctxt->base.id, ts, pid_nr(proc_priv->pid), proc_priv->comm); |
| } |
| gen8_hwsched_fault(adreno_dev, ADRENO_GMU_FAULT); |
| } |
| |
| static void gen8_hwsched_hw_fence_timer(struct timer_list *t) |
| { |
| struct gen8_hwsched_hfi *hfi = from_timer(hfi, t, hw_fence_timer); |
| |
| kgsl_schedule_work(&hfi->hw_fence_ws); |
| } |
| |
| int gen8_hwsched_hfi_probe(struct adreno_device *adreno_dev) |
| { |
| struct gen8_gmu_device *gmu = to_gen8_gmu(adreno_dev); |
| struct gen8_hwsched_hfi *hw_hfi = to_gen8_hwsched_hfi(adreno_dev); |
| |
| gmu->hfi.irq = kgsl_request_irq(gmu->pdev, "hfi", |
| gen8_hwsched_hfi_handler, adreno_dev); |
| |
| if (gmu->hfi.irq < 0) |
| return gmu->hfi.irq; |
| |
| hw_hfi->irq_mask = HFI_IRQ_MASK; |
| |
| rwlock_init(&hw_hfi->msglock); |
| |
| INIT_LIST_HEAD(&hw_hfi->msglist); |
| INIT_LIST_HEAD(&hw_hfi->detached_hw_fence_list); |
| |
| init_waitqueue_head(&hw_hfi->f2h_wq); |
| init_waitqueue_head(&hw_hfi->hw_fence.unack_wq); |
| |
| spin_lock_init(&hw_hfi->hw_fence.lock); |
| |
| mutex_init(&hw_hfi->msgq_mutex); |
| |
| INIT_WORK(&hw_hfi->hw_fence_ws, gen8_hwsched_hw_fence_timeout); |
| |
| timer_setup(&hw_hfi->hw_fence_timer, gen8_hwsched_hw_fence_timer, 0); |
| |
| return 0; |
| } |
| |
| void gen8_hwsched_hfi_remove(struct adreno_device *adreno_dev) |
| { |
| struct gen8_hwsched_hfi *hw_hfi = to_gen8_hwsched_hfi(adreno_dev); |
| |
| if (hw_hfi->f2h_task) |
| kthread_stop(hw_hfi->f2h_task); |
| } |
| |
| static void gen8_add_profile_events(struct adreno_device *adreno_dev, |
| struct kgsl_drawobj_cmd *cmdobj, struct adreno_submit_time *time) |
| { |
| unsigned long flags; |
| u64 time_in_s; |
| unsigned long time_in_ns; |
| struct kgsl_drawobj *drawobj = DRAWOBJ(cmdobj); |
| struct kgsl_context *context = drawobj->context; |
| struct submission_info info = {0}; |
| struct adreno_hwsched *hwsched = &adreno_dev->hwsched; |
| const struct adreno_gpudev *gpudev = ADRENO_GPU_DEVICE(adreno_dev); |
| |
| if (!time) |
| return; |
| |
| /* |
| * Here we are attempting to create a mapping between the |
| * GPU time domain (alwayson counter) and the CPU time domain |
| * (local_clock) by sampling both values as close together as |
| * possible. This is useful for many types of debugging and |
| * profiling. In order to make this mapping as accurate as |
| * possible, we must turn off interrupts to avoid running |
| * interrupt handlers between the two samples. |
| */ |
| |
| local_irq_save(flags); |
| |
| /* Read always on registers */ |
| time->ticks = gpudev->read_alwayson(adreno_dev); |
| |
| /* Trace the GPU time to create a mapping to ftrace time */ |
| trace_adreno_cmdbatch_sync(context->id, context->priority, |
| drawobj->timestamp, time->ticks); |
| |
| /* Get the kernel clock for time since boot */ |
| time->ktime = local_clock(); |
| |
| /* Get the timeofday for the wall time (for the user) */ |
| ktime_get_real_ts64(&time->utime); |
| |
| local_irq_restore(flags); |
| |
| /* Return kernel clock time to the client if requested */ |
| time_in_s = time->ktime; |
| time_in_ns = do_div(time_in_s, 1000000000); |
| |
| info.inflight = hwsched->inflight; |
| info.rb_id = adreno_get_level(context); |
| info.gmu_dispatch_queue = context->gmu_dispatch_queue; |
| |
| cmdobj->submit_ticks = time->ticks; |
| |
| msm_perf_events_update(MSM_PERF_GFX, MSM_PERF_SUBMIT, |
| pid_nr(context->proc_priv->pid), |
| context->id, drawobj->timestamp, |
| !!(drawobj->flags & KGSL_DRAWOBJ_END_OF_FRAME)); |
| trace_adreno_cmdbatch_submitted(drawobj, &info, time->ticks, |
| (unsigned long) time_in_s, time_in_ns / 1000, 0); |
| |
| log_kgsl_cmdbatch_submitted_event(context->id, drawobj->timestamp, |
| context->priority, drawobj->flags); |
| } |
| |
| static void init_gmu_context_queue(struct adreno_context *drawctxt) |
| { |
| struct kgsl_memdesc *md = &drawctxt->gmu_context_queue; |
| struct gmu_context_queue_header *hdr = md->hostptr; |
| |
| hdr->start_addr = md->gmuaddr + sizeof(*hdr); |
| hdr->queue_size = (md->size - sizeof(*hdr)) >> 2; |
| hdr->hw_fence_buffer_va = drawctxt->gmu_hw_fence_queue.gmuaddr; |
| hdr->hw_fence_buffer_size = drawctxt->gmu_hw_fence_queue.size; |
| } |
| |
| static u32 get_dq_id(struct adreno_device *adreno_dev, struct kgsl_context *context) |
| { |
| struct dq_info *info; |
| u32 next; |
| u32 priority = adreno_get_level(context); |
| |
| if (adreno_dev->lpac_enabled) |
| info = &gen8_hfi_dqs_lpac[priority]; |
| else |
| info = &gen8_hfi_dqs[priority]; |
| |
| next = info->base_dq_id + info->offset; |
| |
| info->offset = (info->offset + 1) % info->max_dq; |
| |
| return next; |
| } |
| |
| static int allocate_context_queues(struct adreno_device *adreno_dev, |
| struct adreno_context *drawctxt) |
| { |
| int ret = 0; |
| |
| if (!adreno_hwsched_context_queue_enabled(adreno_dev)) |
| return 0; |
| |
| if (test_bit(ADRENO_HWSCHED_HW_FENCE, &adreno_dev->hwsched.flags) && |
| !drawctxt->gmu_hw_fence_queue.gmuaddr) { |
| ret = gen8_alloc_gmu_kernel_block( |
| to_gen8_gmu(adreno_dev), &drawctxt->gmu_hw_fence_queue, |
| HW_FENCE_QUEUE_SIZE, GMU_NONCACHED_KERNEL, |
| IOMMU_READ | IOMMU_WRITE | IOMMU_PRIV); |
| if (ret) { |
| memset(&drawctxt->gmu_hw_fence_queue, 0x0, |
| sizeof(drawctxt->gmu_hw_fence_queue)); |
| return ret; |
| } |
| } |
| |
| if (!drawctxt->gmu_context_queue.gmuaddr) { |
| ret = gen8_alloc_gmu_kernel_block( |
| to_gen8_gmu(adreno_dev), &drawctxt->gmu_context_queue, |
| SZ_4K, GMU_NONCACHED_KERNEL, |
| IOMMU_READ | IOMMU_WRITE | IOMMU_PRIV); |
| if (ret) { |
| memset(&drawctxt->gmu_context_queue, 0x0, |
| sizeof(drawctxt->gmu_context_queue)); |
| return ret; |
| } |
| init_gmu_context_queue(drawctxt); |
| } |
| |
| return 0; |
| } |
| |
| static int send_context_register(struct adreno_device *adreno_dev, |
| struct kgsl_context *context) |
| { |
| struct adreno_context *drawctxt = ADRENO_CONTEXT(context); |
| struct hfi_register_ctxt_cmd cmd; |
| struct kgsl_pagetable *pt = context->proc_priv->pagetable; |
| int ret, asid = kgsl_mmu_pagetable_get_asid(pt, context); |
| |
| if (asid < 0) |
| return asid; |
| |
| ret = CMD_MSG_HDR(cmd, H2F_MSG_REGISTER_CONTEXT); |
| if (ret) |
| return ret; |
| |
| ret = allocate_context_queues(adreno_dev, drawctxt); |
| if (ret) |
| return ret; |
| |
| cmd.ctxt_id = context->id; |
| cmd.flags = HFI_CTXT_FLAG_NOTIFY | context->flags; |
| /* |
| * HLOS SMMU driver programs context bank to look up ASID from TTBR0 during a page |
| * table walk. So the TLB entries are tagged with the ASID from TTBR0. TLBIASID |
| * invalidates TLB entries whose ASID matches the value that was written to the |
| * CBn_TLBIASID register. Set ASID along with PT address. |
| */ |
| cmd.pt_addr = kgsl_mmu_pagetable_get_ttbr0(pt) | |
| FIELD_PREP(GENMASK_ULL(63, KGSL_IOMMU_ASID_START_BIT), asid); |
| cmd.ctxt_idr = context->id; |
| cmd.ctxt_bank = kgsl_mmu_pagetable_get_context_bank(pt, context); |
| |
| return gen8_hfi_send_cmd_async(adreno_dev, &cmd, sizeof(cmd)); |
| } |
| |
| static int send_context_pointers(struct adreno_device *adreno_dev, |
| struct kgsl_context *context) |
| { |
| struct kgsl_device *device = KGSL_DEVICE(adreno_dev); |
| struct hfi_context_pointers_cmd cmd = {0}; |
| struct adreno_context *drawctxt = ADRENO_CONTEXT(context); |
| int ret; |
| |
| ret = CMD_MSG_HDR(cmd, H2F_MSG_CONTEXT_POINTERS); |
| if (ret) |
| return ret; |
| |
| cmd.ctxt_id = context->id; |
| cmd.sop_addr = MEMSTORE_ID_GPU_ADDR(device, context->id, soptimestamp); |
| cmd.eop_addr = MEMSTORE_ID_GPU_ADDR(device, context->id, eoptimestamp); |
| if (context->user_ctxt_record) |
| cmd.user_ctxt_record_addr = |
| context->user_ctxt_record->memdesc.gpuaddr; |
| |
| if (adreno_hwsched_context_queue_enabled(adreno_dev)) |
| cmd.gmu_context_queue_addr = drawctxt->gmu_context_queue.gmuaddr; |
| |
| return gen8_hfi_send_cmd_async(adreno_dev, &cmd, sizeof(cmd)); |
| } |
| |
| static int hfi_context_register(struct adreno_device *adreno_dev, |
| struct kgsl_context *context) |
| { |
| struct gen8_gmu_device *gmu = to_gen8_gmu(adreno_dev); |
| struct kgsl_device *device = KGSL_DEVICE(adreno_dev); |
| int ret; |
| |
| if (context->gmu_registered) |
| return 0; |
| |
| ret = send_context_register(adreno_dev, context); |
| if (ret) { |
| dev_err(&gmu->pdev->dev, |
| "Unable to register context %u: %d\n", |
| context->id, ret); |
| |
| if (device->gmu_fault) |
| gen8_hwsched_fault(adreno_dev, ADRENO_GMU_FAULT); |
| |
| return ret; |
| } |
| |
| ret = send_context_pointers(adreno_dev, context); |
| if (ret) { |
| dev_err(&gmu->pdev->dev, |
| "Unable to register context %u pointers: %d\n", |
| context->id, ret); |
| |
| if (device->gmu_fault) |
| gen8_hwsched_fault(adreno_dev, ADRENO_GMU_FAULT); |
| |
| return ret; |
| } |
| |
| context->gmu_registered = true; |
| if (adreno_hwsched_context_queue_enabled(adreno_dev)) |
| context->gmu_dispatch_queue = UINT_MAX; |
| else |
| context->gmu_dispatch_queue = get_dq_id(adreno_dev, context); |
| |
| return 0; |
| } |
| |
| static void populate_ibs(struct adreno_device *adreno_dev, |
| struct hfi_submit_cmd *cmd, struct kgsl_drawobj_cmd *cmdobj) |
| { |
| struct hfi_issue_ib *issue_ib; |
| struct kgsl_memobj_node *ib; |
| |
| if (cmdobj->numibs > HWSCHED_MAX_DISPATCH_NUMIBS) { |
| struct gen8_hwsched_hfi *hfi = to_gen8_hwsched_hfi(adreno_dev); |
| struct kgsl_memdesc *big_ib; |
| |
| if (test_bit(CMDOBJ_RECURRING_START, &cmdobj->priv)) |
| big_ib = hfi->big_ib_recurring; |
| else |
| big_ib = hfi->big_ib; |
| /* |
| * The dispatcher ensures that there is only one big IB inflight |
| */ |
| cmd->big_ib_gmu_va = big_ib->gmuaddr; |
| cmd->flags |= CMDBATCH_INDIRECT; |
| issue_ib = big_ib->hostptr; |
| } else { |
| issue_ib = (struct hfi_issue_ib *)&cmd[1]; |
| } |
| |
| list_for_each_entry(ib, &cmdobj->cmdlist, node) { |
| issue_ib->addr = ib->gpuaddr; |
| issue_ib->size = ib->size; |
| issue_ib++; |
| } |
| |
| cmd->numibs = cmdobj->numibs; |
| } |
| |
| #define HFI_DSP_IRQ_BASE 2 |
| |
| #define DISPQ_IRQ_BIT(_idx) BIT((_idx) + HFI_DSP_IRQ_BASE) |
| |
| int gen8_gmu_context_queue_write(struct adreno_device *adreno_dev, |
| struct adreno_context *drawctxt, u32 *msg, u32 size_bytes, |
| struct kgsl_drawobj *drawobj, struct adreno_submit_time *time) |
| { |
| struct gmu_context_queue_header *hdr = drawctxt->gmu_context_queue.hostptr; |
| const struct adreno_gpudev *gpudev = ADRENO_GPU_DEVICE(adreno_dev); |
| u32 *queue = drawctxt->gmu_context_queue.hostptr + sizeof(*hdr); |
| u32 i, empty_space, write_idx = hdr->write_index, read_idx = hdr->read_index; |
| u32 size_dwords = size_bytes >> 2; |
| u32 align_size = ALIGN(size_dwords, SZ_4); |
| u32 id = MSG_HDR_GET_ID(*msg); |
| struct kgsl_drawobj_cmd *cmdobj = NULL; |
| |
| empty_space = (write_idx >= read_idx) ? |
| (hdr->queue_size - (write_idx - read_idx)) |
| : (read_idx - write_idx); |
| |
| if (empty_space <= align_size) |
| return -ENOSPC; |
| |
| if (!IS_ALIGNED(size_bytes, sizeof(u32))) |
| return -EINVAL; |
| |
| for (i = 0; i < size_dwords; i++) { |
| queue[write_idx] = msg[i]; |
| write_idx = (write_idx + 1) % hdr->queue_size; |
| } |
| |
| /* Cookify any non used data at the end of the write buffer */ |
| for (; i < align_size; i++) { |
| queue[write_idx] = 0xfafafafa; |
| write_idx = (write_idx + 1) % hdr->queue_size; |
| } |
| |
| /* Ensure packet is written out before proceeding */ |
| wmb(); |
| |
| if (drawobj->type & SYNCOBJ_TYPE) { |
| struct kgsl_drawobj_sync *syncobj = SYNCOBJ(drawobj); |
| |
| trace_adreno_syncobj_submitted(drawobj->context->id, drawobj->timestamp, |
| syncobj->numsyncs, gpudev->read_alwayson(adreno_dev)); |
| goto done; |
| } |
| |
| cmdobj = CMDOBJ(drawobj); |
| |
| gen8_add_profile_events(adreno_dev, cmdobj, time); |
| |
| /* |
| * Put the profiling information in the user profiling buffer. |
| * The hfi_update_write_idx below has a wmb() before the actual |
| * write index update to ensure that the GMU does not see the |
| * packet before the profile data is written out. |
| */ |
| adreno_profile_submit_time(time); |
| |
| done: |
| trace_kgsl_hfi_send(id, size_dwords, MSG_HDR_GET_SEQNUM(*msg)); |
| |
| hfi_update_write_idx(&hdr->write_index, write_idx); |
| |
| return 0; |
| } |
| |
| static u32 get_irq_bit(struct adreno_device *adreno_dev, struct kgsl_drawobj *drawobj) |
| { |
| if (!adreno_hwsched_context_queue_enabled(adreno_dev)) |
| return drawobj->context->gmu_dispatch_queue; |
| |
| if (adreno_is_preemption_enabled(adreno_dev)) |
| return adreno_get_level(drawobj->context); |
| |
| if (kgsl_context_is_lpac(drawobj->context)) |
| return 1; |
| |
| return 0; |
| } |
| |
| static int add_gmu_waiter(struct adreno_device *adreno_dev, |
| struct dma_fence *fence) |
| { |
| struct kgsl_device *device = KGSL_DEVICE(adreno_dev); |
| int ret = msm_hw_fence_wait_update(adreno_dev->hwsched.hw_fence.handle, |
| &fence, 1, true); |
| |
| if (ret) |
| dev_err_ratelimited(device->dev, |
| "Failed to add GMU as waiter ret:%d fence ctx:%llu ts:%llu\n", |
| ret, fence->context, fence->seqno); |
| |
| return ret; |
| } |
| |
| static void populate_kgsl_fence(struct hfi_syncobj *obj, |
| struct dma_fence *fence) |
| { |
| struct kgsl_sync_fence *kfence = (struct kgsl_sync_fence *)fence; |
| struct kgsl_sync_timeline *ktimeline = kfence->parent; |
| unsigned long flags; |
| |
| obj->flags |= BIT(GMU_SYNCOBJ_FLAG_KGSL_FENCE_BIT); |
| |
| spin_lock_irqsave(&ktimeline->lock, flags); |
| /* If the context is going away or the dma fence is signaled, mark the fence as triggered */ |
| if (!ktimeline->context || dma_fence_is_signaled_locked(fence)) { |
| obj->flags |= BIT(GMU_SYNCOBJ_FLAG_SIGNALED_BIT); |
| spin_unlock_irqrestore(&ktimeline->lock, flags); |
| return; |
| } |
| obj->ctxt_id = ktimeline->context->id; |
| spin_unlock_irqrestore(&ktimeline->lock, flags); |
| |
| obj->seq_no = kfence->timestamp; |
| } |
| |
| static int _submit_hw_fence(struct adreno_device *adreno_dev, |
| struct kgsl_drawobj *drawobj, void *cmdbuf) |
| { |
| struct adreno_context *drawctxt = ADRENO_CONTEXT(drawobj->context); |
| int i, j; |
| u32 cmd_sizebytes, seqnum; |
| struct kgsl_drawobj_sync *syncobj = SYNCOBJ(drawobj); |
| struct hfi_submit_syncobj *cmd; |
| struct hfi_syncobj *obj = NULL; |
| |
| /* Add hfi_syncobj struct for sync object */ |
| cmd_sizebytes = sizeof(*cmd) + |
| (sizeof(struct hfi_syncobj) * |
| syncobj->num_hw_fence); |
| |
| if (WARN_ON(cmd_sizebytes > HFI_MAX_MSG_SIZE)) |
| return -EMSGSIZE; |
| |
| memset(cmdbuf, 0x0, cmd_sizebytes); |
| cmd = cmdbuf; |
| cmd->num_syncobj = syncobj->num_hw_fence; |
| obj = (struct hfi_syncobj *)&cmd[1]; |
| |
| for (i = 0; i < syncobj->numsyncs; i++) { |
| struct kgsl_drawobj_sync_event *event = &syncobj->synclist[i]; |
| struct kgsl_sync_fence_cb *kcb = event->handle; |
| struct dma_fence **fences; |
| struct dma_fence_array *array; |
| u32 num_fences; |
| |
| if (!kcb) |
| return -EINVAL; |
| |
| array = to_dma_fence_array(kcb->fence); |
| if (array != NULL) { |
| num_fences = array->num_fences; |
| fences = array->fences; |
| } else { |
| num_fences = 1; |
| fences = &kcb->fence; |
| } |
| |
| for (j = 0; j < num_fences; j++) { |
| |
| /* |
| * If this sync object has a software only fence, make sure that it is |
| * already signaled so that we can skip sending this fence to the GMU. |
| */ |
| if (!test_bit(MSM_HW_FENCE_FLAG_ENABLED_BIT, &fences[j]->flags)) { |
| if (WARN(!dma_fence_is_signaled(fences[j]), |
| "sync object has unsignaled software fence")) |
| return -EINVAL; |
| continue; |
| } |
| |
| if (is_kgsl_fence(fences[j])) { |
| populate_kgsl_fence(obj, fences[j]); |
| } else { |
| int ret = add_gmu_waiter(adreno_dev, fences[j]); |
| |
| if (ret) { |
| syncobj->flags &= ~KGSL_SYNCOBJ_HW; |
| return ret; |
| } |
| |
| if (test_bit(MSM_HW_FENCE_FLAG_SIGNALED_BIT, &fences[j]->flags) || |
| test_bit(DMA_FENCE_FLAG_SIGNALED_BIT, &fences[j]->flags)) |
| obj->flags |= BIT(GMU_SYNCOBJ_FLAG_SIGNALED_BIT); |
| |
| obj->ctxt_id = fences[j]->context; |
| obj->seq_no = fences[j]->seqno; |
| } |
| trace_adreno_input_hw_fence(drawobj->context->id, obj->ctxt_id, |
| obj->seq_no, obj->flags, fences[j]->ops->get_timeline_name ? |
| fences[j]->ops->get_timeline_name(fences[j]) : "unknown"); |
| |
| obj++; |
| } |
| } |
| |
| /* |
| * Attach a timestamp to this SYNCOBJ to keep track whether GMU has deemed it signaled |
| * or not. |
| */ |
| drawobj->timestamp = ++drawctxt->syncobj_timestamp; |
| cmd->timestamp = drawobj->timestamp; |
| |
| cmd->hdr = CREATE_MSG_HDR(H2F_MSG_ISSUE_SYNCOBJ, HFI_MSG_CMD); |
| seqnum = atomic_inc_return(&adreno_dev->hwsched.submission_seqnum); |
| cmd->hdr = MSG_HDR_SET_SEQNUM_SIZE(cmd->hdr, seqnum, cmd_sizebytes >> 2); |
| |
| return gen8_gmu_context_queue_write(adreno_dev, drawctxt, (u32 *)cmd, cmd_sizebytes, |
| drawobj, NULL); |
| } |
| |
| int gen8_hwsched_check_context_inflight_hw_fences(struct adreno_device *adreno_dev, |
| struct adreno_context *drawctxt) |
| { |
| struct adreno_hw_fence_entry *entry, *tmp; |
| struct gen8_gmu_device *gmu = to_gen8_gmu(adreno_dev); |
| int ret = 0; |
| |
| spin_lock(&drawctxt->lock); |
| list_for_each_entry_safe(entry, tmp, &drawctxt->hw_fence_inflight_list, node) { |
| struct gmu_context_queue_header *hdr = drawctxt->gmu_context_queue.hostptr; |
| |
| if (timestamp_cmp((u32)entry->cmd.ts, hdr->out_fence_ts) > 0) { |
| dev_err(&gmu->pdev->dev, |
| "detached ctx:%d has unsignaled fence ts:%d retired:%d\n", |
| drawctxt->base.id, (u32)entry->cmd.ts, hdr->out_fence_ts); |
| ret = -EINVAL; |
| break; |
| } |
| |
| gen8_remove_hw_fence_entry(adreno_dev, entry); |
| } |
| spin_unlock(&drawctxt->lock); |
| |
| return ret; |
| } |
| |
| /** |
| * move_detached_context_hardware_fences - Move all pending hardware fences belonging to this |
| * context to the detached hardware fence list so as to send them to TxQueue after fault recovery. |
| * This is needed because this context may get destroyed before fault recovery gets executed. |
| */ |
| static void move_detached_context_hardware_fences(struct adreno_device *adreno_dev, |
| struct adreno_context *drawctxt) |
| { |
| struct adreno_hw_fence_entry *entry, *tmp; |
| struct gen8_hwsched_hfi *hfi = to_gen8_hwsched_hfi(adreno_dev); |
| |
| /* We don't need the drawctxt lock here because this context has already been detached */ |
| list_for_each_entry_safe(entry, tmp, &drawctxt->hw_fence_inflight_list, node) { |
| struct gmu_context_queue_header *hdr = drawctxt->gmu_context_queue.hostptr; |
| |
| if ((timestamp_cmp((u32)entry->cmd.ts, hdr->out_fence_ts) > 0)) { |
| _kgsl_context_get(&drawctxt->base); |
| list_move_tail(&entry->node, &hfi->detached_hw_fence_list); |
| continue; |
| } |
| |
| gen8_remove_hw_fence_entry(adreno_dev, entry); |
| } |
| |
| /* Also grab all the hardware fences which were never sent to GMU */ |
| list_for_each_entry_safe(entry, tmp, &drawctxt->hw_fence_list, node) { |
| _kgsl_context_get(&drawctxt->base); |
| list_move_tail(&entry->node, &hfi->detached_hw_fence_list); |
| } |
| } |
| |
| /** |
| * check_detached_context_hardware_fences - When this context has been un-registered with the GMU, |
| * make sure all the hardware fences(that were sent to GMU) for this context have been sent to |
| * TxQueue. Also, send any hardware fences (to GMU) that were not yet dispatched to the GMU. In case |
| * of an error, move the pending hardware fences to detached hardware fence list, log the error, |
| * take a snapshot and trigger recovery. |
| */ |
| static int check_detached_context_hardware_fences(struct adreno_device *adreno_dev, |
| struct adreno_context *drawctxt) |
| { |
| struct kgsl_device *device = KGSL_DEVICE(adreno_dev); |
| struct adreno_hw_fence_entry *entry, *tmp; |
| struct gen8_gmu_device *gmu = to_gen8_gmu(adreno_dev); |
| int ret = 0; |
| |
| /* We don't need the drawctxt lock because this context has been detached */ |
| list_for_each_entry_safe(entry, tmp, &drawctxt->hw_fence_inflight_list, node) { |
| struct gmu_context_queue_header *hdr = drawctxt->gmu_context_queue.hostptr; |
| |
| if ((timestamp_cmp((u32)entry->cmd.ts, hdr->out_fence_ts) > 0)) { |
| dev_err(&gmu->pdev->dev, |
| "detached ctx:%d has unsignaled fence ts:%d retired:%d\n", |
| drawctxt->base.id, (u32)entry->cmd.ts, hdr->out_fence_ts); |
| ret = -EINVAL; |
| goto fault; |
| } |
| gen8_remove_hw_fence_entry(adreno_dev, entry); |
| } |
| |
| /* Send hardware fences (to TxQueue) that were not dispatched to GMU */ |
| list_for_each_entry_safe(entry, tmp, &drawctxt->hw_fence_list, node) { |
| |
| ret = gen8_send_hw_fence_hfi_wait_ack(adreno_dev, entry, |
| HW_FENCE_FLAG_SKIP_MEMSTORE); |
| if (ret) |
| goto fault; |
| |
| gen8_remove_hw_fence_entry(adreno_dev, entry); |
| } |
| |
| return 0; |
| |
| fault: |
| move_detached_context_hardware_fences(adreno_dev, drawctxt); |
| gmu_core_fault_snapshot(device); |
| gen8_hwsched_fault(adreno_dev, ADRENO_GMU_FAULT); |
| |
| return ret; |
| } |
| |
| static inline int setup_hw_fence_info_cmd(struct adreno_device *adreno_dev, |
| struct adreno_hw_fence_entry *entry) |
| { |
| struct kgsl_sync_fence *kfence = entry->kfence; |
| int ret; |
| |
| ret = CMD_MSG_HDR(entry->cmd, H2F_MSG_HW_FENCE_INFO); |
| if (ret) |
| return ret; |
| |
| entry->cmd.gmu_ctxt_id = entry->drawctxt->base.id; |
| entry->cmd.ctxt_id = kfence->fence.context; |
| entry->cmd.ts = kfence->fence.seqno; |
| |
| entry->cmd.hash_index = kfence->hw_fence_index; |
| |
| return 0; |
| } |
| |
| /* |
| * gen8_send_hw_fence_hfi_wait_ack - This function is used in cases where multiple hardware fences |
| * are to be sent to GMU. Hence, we must send them one by one to avoid overwhelming the GMU with |
| * mutliple fences in a short span of time. |
| */ |
| int gen8_send_hw_fence_hfi_wait_ack(struct adreno_device *adreno_dev, |
| struct adreno_hw_fence_entry *entry, u64 flags) |
| { |
| struct gen8_gmu_device *gmu = to_gen8_gmu(adreno_dev); |
| struct gen8_hwsched_hfi *hfi = to_gen8_hwsched_hfi(adreno_dev); |
| struct kgsl_device *device = KGSL_DEVICE(adreno_dev); |
| int ret = 0; |
| u32 seqnum; |
| |
| /* Device mutex is necessary to ensure only one hardware fence ack is being waited for */ |
| if (WARN_ON(!mutex_is_locked(&device->mutex))) |
| return -EINVAL; |
| |
| spin_lock(&hfi->hw_fence.lock); |
| |
| init_completion(&gen8_hw_fence_ack.complete); |
| |
| entry->cmd.flags |= flags; |
| seqnum = atomic_inc_return(&hfi->hw_fence.seqnum); |
| |
| gen8_hw_fence_ack.sent_hdr = entry->cmd.hdr; |
| |
| /* |
| * We don't need to increment the unack count here as we are waiting for the ack for |
| * this fence before sending another hardware fence. |
| */ |
| ret = gen8_hfi_cmdq_write(adreno_dev, (u32 *)&entry->cmd, sizeof(entry->cmd)); |
| |
| spin_unlock(&hfi->hw_fence.lock); |
| |
| if (!ret) |
| ret = adreno_hwsched_wait_ack_completion(adreno_dev, |
| &gmu->pdev->dev, &gen8_hw_fence_ack, |
| gen8_hwsched_process_msgq); |
| |
| memset(&gen8_hw_fence_ack, 0x0, sizeof(gen8_hw_fence_ack)); |
| return ret; |
| } |
| |
| /** |
| * drawctxt_queue_hw_fence - Add a hardware fence to draw context's hardware fence list and make |
| * sure the list remains sorted (with the fence with the largest timestamp at the end) |
| */ |
| static void drawctxt_queue_hw_fence(struct adreno_context *drawctxt, |
| struct adreno_hw_fence_entry *new) |
| { |
| struct adreno_hw_fence_entry *entry = NULL; |
| u32 ts = (u32)new->cmd.ts; |
| |
| /* Walk the list backwards to find the right spot for this fence */ |
| list_for_each_entry_reverse(entry, &drawctxt->hw_fence_list, node) { |
| if (timestamp_cmp(ts, (u32)entry->cmd.ts) > 0) |
| break; |
| } |
| |
| list_add(&new->node, &entry->node); |
| } |
| |
| #define DRAWCTXT_SLOT_AVAILABLE(count) \ |
| ((count + 1) < (HW_FENCE_QUEUE_SIZE / sizeof(struct hfi_hw_fence_info))) |
| |
| /** |
| * allocate_hw_fence_entry - Allocate an entry to keep track of a hardware fence. This is free'd |
| * when we know GMU has sent this fence to the TxQueue |
| */ |
| static struct adreno_hw_fence_entry *allocate_hw_fence_entry(struct adreno_device *adreno_dev, |
| struct adreno_context *drawctxt, struct kgsl_sync_fence *kfence) |
| { |
| struct adreno_hwsched *hwsched = &adreno_dev->hwsched; |
| struct adreno_hw_fence_entry *entry; |
| |
| if (!DRAWCTXT_SLOT_AVAILABLE(drawctxt->hw_fence_count)) |
| return NULL; |
| |
| entry = kmem_cache_zalloc(hwsched->hw_fence_cache, GFP_ATOMIC); |
| if (!entry) |
| return NULL; |
| |
| entry->kfence = kfence; |
| entry->drawctxt = drawctxt; |
| |
| if (setup_hw_fence_info_cmd(adreno_dev, entry)) { |
| kmem_cache_free(hwsched->hw_fence_cache, entry); |
| return NULL; |
| } |
| |
| dma_fence_get(&kfence->fence); |
| |
| drawctxt->hw_fence_count++; |
| atomic_inc(&hwsched->hw_fence_count); |
| |
| INIT_LIST_HEAD(&entry->node); |
| INIT_LIST_HEAD(&entry->reset_node); |
| return entry; |
| } |
| |
| static bool _hw_fence_end_sleep(struct adreno_device *adreno_dev) |
| { |
| struct gen8_hwsched_hfi *hfi = to_gen8_hwsched_hfi(adreno_dev); |
| bool ret; |
| |
| spin_lock(&hfi->hw_fence.lock); |
| ret = !test_bit(GEN8_HWSCHED_HW_FENCE_SLEEP_BIT, &hfi->hw_fence.flags); |
| spin_unlock(&hfi->hw_fence.lock); |
| |
| return ret; |
| } |
| |
| /** |
| * _hw_fence_sleep() - Check if the thread needs to sleep until the hardware fence unack count |
| * drops to a desired threshold. |
| * |
| * Return: negative error code if the thread was woken up by a signal, or the context became bad in |
| * the meanwhile, or the hardware fence unack count hasn't yet dropped to a desired threshold, or |
| * if fault recovery is imminent. |
| * Otherwise, return 0. |
| */ |
| static int _hw_fence_sleep(struct adreno_device *adreno_dev, struct adreno_context *drawctxt) |
| { |
| struct gen8_hwsched_hfi *hfi = to_gen8_hwsched_hfi(adreno_dev); |
| int ret = 0; |
| |
| if (!test_bit(GEN8_HWSCHED_HW_FENCE_SLEEP_BIT, &hfi->hw_fence.flags)) |
| return 0; |
| |
| spin_unlock(&hfi->hw_fence.lock); |
| spin_unlock(&drawctxt->lock); |
| |
| ret = wait_event_interruptible(hfi->hw_fence.unack_wq, |
| _hw_fence_end_sleep(adreno_dev)); |
| |
| spin_lock(&drawctxt->lock); |
| spin_lock(&hfi->hw_fence.lock); |
| |
| /* |
| * If the thread received a signal, or the context became bad in the meanwhile or the limit |
| * is still not settled, then return error to avoid creating this hardware fence |
| */ |
| if ((ret == -ERESTARTSYS) || kgsl_context_is_bad(&drawctxt->base) || |
| test_bit(GEN8_HWSCHED_HW_FENCE_MAX_BIT, &hfi->hw_fence.flags)) |
| return -EINVAL; |
| |
| /* |
| * If fault recovery is imminent then return error code to avoid creating new hardware |
| * fences until recovery is complete |
| */ |
| if (test_bit(GEN8_HWSCHED_HW_FENCE_ABORT_BIT, &hfi->hw_fence.flags)) |
| return -EBUSY; |
| |
| return ret; |
| } |
| |
| void gen8_hwsched_create_hw_fence(struct adreno_device *adreno_dev, |
| struct kgsl_sync_fence *kfence) |
| { |
| struct kgsl_device *device = KGSL_DEVICE(adreno_dev); |
| struct kgsl_sync_timeline *ktimeline = kfence->parent; |
| struct kgsl_context *context = ktimeline->context; |
| struct adreno_context *drawctxt = ADRENO_CONTEXT(context); |
| struct adreno_hw_fence_entry *entry = NULL; |
| struct msm_hw_fence_create_params params = {0}; |
| /* Only allow a single log in a second */ |
| static DEFINE_RATELIMIT_STATE(_rs, HZ, 1); |
| struct gen8_hwsched_hfi *hw_hfi = to_gen8_hwsched_hfi(adreno_dev); |
| struct gen8_gmu_device *gmu = to_gen8_gmu(adreno_dev); |
| u32 retired = 0; |
| int ret = 0; |
| bool destroy_hw_fence = true; |
| |
| params.fence = &kfence->fence; |
| params.handle = &kfence->hw_fence_index; |
| kfence->hw_fence_handle = adreno_dev->hwsched.hw_fence.handle; |
| |
| ret = msm_hw_fence_create(kfence->hw_fence_handle, ¶ms); |
| if ((ret || IS_ERR_OR_NULL(params.handle))) { |
| if (__ratelimit(&_rs)) |
| dev_err(device->dev, "Failed to create ctx:%d ts:%d hardware fence:%d\n", |
| kfence->context_id, kfence->timestamp, ret); |
| return; |
| } |
| |
| spin_lock(&drawctxt->lock); |
| spin_lock(&hw_hfi->hw_fence.lock); |
| |
| /* |
| * If we create a hardware fence and this context is going away, we may never dispatch |
| * this fence to the GMU. Hence, avoid creating a hardware fence if context is going away. |
| */ |
| if (kgsl_context_is_bad(context)) |
| goto done; |
| |
| entry = allocate_hw_fence_entry(adreno_dev, drawctxt, kfence); |
| if (!entry) |
| goto done; |
| |
| /* If recovery is imminent, then do not create a hardware fence */ |
| if (test_bit(GEN8_HWSCHED_HW_FENCE_ABORT_BIT, &hw_hfi->hw_fence.flags)) { |
| destroy_hw_fence = true; |
| goto done; |
| } |
| |
| ret = _hw_fence_sleep(adreno_dev, drawctxt); |
| if (ret) |
| goto done; |
| |
| /* |
| * If this ts hasn't been submitted yet, then store it in the drawctxt hardware fence |
| * list and return. This fence will be sent to GMU when this ts is dispatched to GMU. |
| */ |
| if (timestamp_cmp(kfence->timestamp, drawctxt->internal_timestamp) > 0) { |
| drawctxt_queue_hw_fence(drawctxt, entry); |
| destroy_hw_fence = false; |
| goto done; |
| } |
| |
| kgsl_readtimestamp(device, context, KGSL_TIMESTAMP_RETIRED, &retired); |
| |
| /* |
| * Check if timestamp is retired. If we are in SLUMBER at this point, the timestamp is |
| * guaranteed to be retired. This way, we don't need the device mutex to check the device |
| * state explicitly. |
| */ |
| if (timestamp_cmp(retired, kfence->timestamp) >= 0) { |
| kgsl_sync_timeline_signal(ktimeline, kfence->timestamp); |
| goto done; |
| } |
| |
| /* |
| * If timestamp is not retired then GMU must already be powered up. This is because SLUMBER |
| * thread has to wait for hardware fence spinlock to make sure the hardware fence unack |
| * count is zero. |
| */ |
| ret = _send_hw_fence_no_ack(adreno_dev, entry); |
| if (ret) { |
| if (__ratelimit(&_rs)) |
| dev_err(&gmu->pdev->dev, "Aborting hw fence for ctx:%d ts:%d ret:%d\n", |
| kfence->context_id, kfence->timestamp, ret); |
| goto done; |
| } |
| |
| list_add_tail(&entry->node, &drawctxt->hw_fence_inflight_list); |
| |
| destroy_hw_fence = false; |
| |
| done: |
| if (destroy_hw_fence) { |
| msm_hw_fence_destroy(kfence->hw_fence_handle, &kfence->fence); |
| if (entry) |
| gen8_remove_hw_fence_entry(adreno_dev, entry); |
| } |
| |
| spin_unlock(&hw_hfi->hw_fence.lock); |
| spin_unlock(&drawctxt->lock); |
| } |
| |
| /** |
| * setup_hw_fence_deferred_ctxt - The hardware fence(s) from this context couldn't be sent to the |
| * GMU because the hardware fence unack count reached a threshold. Hence, setup this context such |
| * that these hardware fences are sent to the GMU when the unack count drops to a desired threshold. |
| */ |
| static void setup_hw_fence_deferred_ctxt(struct adreno_device *adreno_dev, |
| struct adreno_context *drawctxt, u32 ts) |
| { |
| struct gen8_hwsched_hfi *hfi = to_gen8_hwsched_hfi(adreno_dev); |
| |
| if (!_kgsl_context_get(&drawctxt->base)) |
| return; |
| |
| hfi->hw_fence.defer_drawctxt = drawctxt; |
| hfi->hw_fence.defer_ts = ts; |
| /* |
| * Increment the active count so that device doesn't get powered off until this fence has |
| * been sent to GMU |
| */ |
| gen8_hwsched_active_count_get(adreno_dev); |
| } |
| |
| /** |
| * process_hw_fence_queue - This function walks the draw context's list of hardware fences |
| * and sends the ones which have a timestamp less than or equal to the timestamp that just |
| * got submitted to the GMU. |
| */ |
| static void process_hw_fence_queue(struct adreno_device *adreno_dev, |
| struct adreno_context *drawctxt, u32 ts) |
| { |
| struct adreno_hw_fence_entry *entry = NULL, *next; |
| struct gen8_hwsched_hfi *hfi = to_gen8_hwsched_hfi(adreno_dev); |
| int ret = 0; |
| |
| /* This list is sorted with smallest timestamp at head and highest timestamp at tail */ |
| list_for_each_entry_safe(entry, next, &drawctxt->hw_fence_list, node) { |
| |
| if (timestamp_cmp((u32)entry->cmd.ts, ts) > 0) |
| return; |
| |
| spin_lock(&hfi->hw_fence.lock); |
| |
| if (test_bit(GEN8_HWSCHED_HW_FENCE_MAX_BIT, &hfi->hw_fence.flags)) { |
| setup_hw_fence_deferred_ctxt(adreno_dev, drawctxt, ts); |
| spin_unlock(&hfi->hw_fence.lock); |
| return; |
| } |
| |
| ret = _send_hw_fence_no_ack(adreno_dev, entry); |
| |
| spin_unlock(&hfi->hw_fence.lock); |
| |
| if (ret) |
| return; |
| |
| /* |
| * A fence that is sent to GMU must be added to the drawctxt->hw_fence_inflight_list |
| * so that we can keep track of when GMU sends it to the TxQueue |
| */ |
| list_del_init(&entry->node); |
| list_add_tail(&entry->node, &drawctxt->hw_fence_inflight_list); |
| } |
| } |
| |
| /* Size in below functions are in unit of dwords */ |
| static int gen8_hfi_dispatch_queue_write(struct adreno_device *adreno_dev, u32 queue_idx, |
| u32 *msg, u32 size_bytes, struct kgsl_drawobj_cmd *cmdobj, struct adreno_submit_time *time) |
| { |
| struct gen8_gmu_device *gmu = to_gen8_gmu(adreno_dev); |
| struct hfi_queue_table *tbl = gmu->hfi.hfi_mem->hostptr; |
| struct hfi_queue_header *hdr = &tbl->qhdr[queue_idx]; |
| u32 *queue; |
| u32 i, write, empty_space; |
| u32 size_dwords = size_bytes >> 2; |
| u32 align_size = ALIGN(size_dwords, SZ_4); |
| u32 id = MSG_HDR_GET_ID(*msg); |
| |
| if (hdr->status == HFI_QUEUE_STATUS_DISABLED || !IS_ALIGNED(size_bytes, sizeof(u32))) |
| return -EINVAL; |
| |
| queue = HOST_QUEUE_START_ADDR(gmu->hfi.hfi_mem, queue_idx); |
| |
| empty_space = (hdr->write_index >= hdr->read_index) ? |
| (hdr->queue_size - (hdr->write_index - hdr->read_index)) |
| : (hdr->read_index - hdr->write_index); |
| |
| if (empty_space <= align_size) |
| return -ENOSPC; |
| |
| write = hdr->write_index; |
| |
| for (i = 0; i < size_dwords; i++) { |
| queue[write] = msg[i]; |
| write = (write + 1) % hdr->queue_size; |
| } |
| |
| /* Cookify any non used data at the end of the write buffer */ |
| for (; i < align_size; i++) { |
| queue[write] = 0xfafafafa; |
| write = (write + 1) % hdr->queue_size; |
| } |
| |
| /* Ensure packet is written out before proceeding */ |
| wmb(); |
| |
| gen8_add_profile_events(adreno_dev, cmdobj, time); |
| |
| /* |
| * Put the profiling information in the user profiling buffer. |
| * The hfi_update_write_idx below has a wmb() before the actual |
| * write index update to ensure that the GMU does not see the |
| * packet before the profile data is written out. |
| */ |
| adreno_profile_submit_time(time); |
| |
| trace_kgsl_hfi_send(id, size_dwords, MSG_HDR_GET_SEQNUM(*msg)); |
| |
| hfi_update_write_idx(&hdr->write_index, write); |
| |
| return 0; |
| } |
| |
| int gen8_hwsched_submit_drawobj(struct adreno_device *adreno_dev, struct kgsl_drawobj *drawobj) |
| { |
| int ret = 0; |
| u32 cmd_sizebytes, seqnum; |
| struct kgsl_drawobj_cmd *cmdobj = NULL; |
| struct hfi_submit_cmd *cmd; |
| struct adreno_submit_time time = {0}; |
| struct adreno_context *drawctxt = ADRENO_CONTEXT(drawobj->context); |
| static void *cmdbuf; |
| |
| if (cmdbuf == NULL) { |
| struct kgsl_device *device = KGSL_DEVICE(adreno_dev); |
| |
| cmdbuf = devm_kzalloc(&device->pdev->dev, HFI_MAX_MSG_SIZE, |
| GFP_KERNEL); |
| if (!cmdbuf) |
| return -ENOMEM; |
| } |
| |
| ret = hfi_context_register(adreno_dev, drawobj->context); |
| if (ret) |
| return ret; |
| |
| if ((drawobj->type & SYNCOBJ_TYPE) != 0) |
| return _submit_hw_fence(adreno_dev, drawobj, cmdbuf); |
| |
| cmdobj = CMDOBJ(drawobj); |
| |
| /* |
| * If the MARKER object is retired, it doesn't need to be dispatched to GMU. Simply trigger |
| * any pending fences that are less than/equal to this object's timestamp. |
| */ |
| if (test_bit(CMDOBJ_MARKER_EXPIRED, &cmdobj->priv)) { |
| spin_lock(&drawctxt->lock); |
| process_hw_fence_queue(adreno_dev, drawctxt, drawobj->timestamp); |
| spin_unlock(&drawctxt->lock); |
| return 0; |
| } |
| |
| /* Add a *issue_ib struct for each IB */ |
| if (cmdobj->numibs > HWSCHED_MAX_DISPATCH_NUMIBS || |
| test_bit(CMDOBJ_SKIP, &cmdobj->priv)) |
| cmd_sizebytes = sizeof(*cmd); |
| else |
| cmd_sizebytes = sizeof(*cmd) + |
| (sizeof(struct hfi_issue_ib) * cmdobj->numibs); |
| |
| if (WARN_ON(cmd_sizebytes > HFI_MAX_MSG_SIZE)) |
| return -EMSGSIZE; |
| |
| memset(cmdbuf, 0x0, cmd_sizebytes); |
| |
| cmd = cmdbuf; |
| |
| cmd->ctxt_id = drawobj->context->id; |
| cmd->flags = HFI_CTXT_FLAG_NOTIFY; |
| if (drawobj->flags & KGSL_DRAWOBJ_END_OF_FRAME) |
| cmd->flags |= CMDBATCH_EOF; |
| |
| cmd->ts = drawobj->timestamp; |
| |
| if (test_bit(CMDOBJ_SKIP, &cmdobj->priv)) |
| goto skipib; |
| |
| populate_ibs(adreno_dev, cmd, cmdobj); |
| |
| if ((drawobj->flags & KGSL_DRAWOBJ_PROFILING) && |
| cmdobj->profiling_buf_entry) { |
| |
| time.drawobj = drawobj; |
| |
| cmd->profile_gpuaddr_lo = |
| lower_32_bits(cmdobj->profiling_buffer_gpuaddr); |
| cmd->profile_gpuaddr_hi = |
| upper_32_bits(cmdobj->profiling_buffer_gpuaddr); |
| |
| /* Indicate to GMU to do user profiling for this submission */ |
| cmd->flags |= CMDBATCH_PROFILING; |
| } |
| |
| skipib: |
| adreno_drawobj_set_constraint(KGSL_DEVICE(adreno_dev), drawobj); |
| |
| cmd->hdr = CREATE_MSG_HDR(H2F_MSG_ISSUE_CMD, HFI_MSG_CMD); |
| seqnum = atomic_inc_return(&adreno_dev->hwsched.submission_seqnum); |
| cmd->hdr = MSG_HDR_SET_SEQNUM_SIZE(cmd->hdr, seqnum, cmd_sizebytes >> 2); |
| |
| if (adreno_hwsched_context_queue_enabled(adreno_dev)) |
| ret = gen8_gmu_context_queue_write(adreno_dev, |
| drawctxt, (u32 *)cmd, cmd_sizebytes, drawobj, &time); |
| else |
| ret = gen8_hfi_dispatch_queue_write(adreno_dev, |
| HFI_DSP_ID_0 + drawobj->context->gmu_dispatch_queue, |
| (u32 *)cmd, cmd_sizebytes, cmdobj, &time); |
| if (ret) |
| return ret; |
| |
| /* Send interrupt to GMU to receive the message */ |
| gmu_core_regwrite(KGSL_DEVICE(adreno_dev), GEN8_GMUCX_HOST2GMU_INTR_SET, |
| DISPQ_IRQ_BIT(get_irq_bit(adreno_dev, drawobj))); |
| |
| spin_lock(&drawctxt->lock); |
| process_hw_fence_queue(adreno_dev, drawctxt, drawobj->timestamp); |
| /* |
| * We need to update the internal timestamp while holding the drawctxt lock since we have to |
| * check it in the hardware fence creation path, where we are not taking the device mutex. |
| */ |
| drawctxt->internal_timestamp = drawobj->timestamp; |
| spin_unlock(&drawctxt->lock); |
| |
| return 0; |
| } |
| |
| int gen8_hwsched_send_recurring_cmdobj(struct adreno_device *adreno_dev, |
| struct kgsl_drawobj_cmd *cmdobj) |
| { |
| struct adreno_hwsched *hwsched = &adreno_dev->hwsched; |
| struct kgsl_drawobj *drawobj = DRAWOBJ(cmdobj); |
| struct hfi_submit_cmd *cmd; |
| struct kgsl_memobj_node *ib; |
| u32 cmd_sizebytes; |
| int ret; |
| static bool active; |
| |
| if (adreno_gpu_halt(adreno_dev) || adreno_hwsched_gpu_fault(adreno_dev)) |
| return -EBUSY; |
| |
| if (test_bit(CMDOBJ_RECURRING_STOP, &cmdobj->priv)) { |
| cmdobj->numibs = 0; |
| } else { |
| list_for_each_entry(ib, &cmdobj->cmdlist, node) |
| cmdobj->numibs++; |
| } |
| |
| if (cmdobj->numibs > HWSCHED_MAX_IBS) |
| return -EINVAL; |
| |
| if (cmdobj->numibs > HWSCHED_MAX_DISPATCH_NUMIBS) |
| cmd_sizebytes = sizeof(*cmd); |
| else |
| cmd_sizebytes = sizeof(*cmd) + |
| (sizeof(struct hfi_issue_ib) * cmdobj->numibs); |
| |
| if (WARN_ON(cmd_sizebytes > HFI_MAX_MSG_SIZE)) |
| return -EMSGSIZE; |
| |
| cmd = kzalloc(cmd_sizebytes, GFP_KERNEL); |
| if (cmd == NULL) |
| return -ENOMEM; |
| |
| if (test_bit(CMDOBJ_RECURRING_START, &cmdobj->priv)) { |
| if (!active) { |
| ret = adreno_active_count_get(adreno_dev); |
| if (ret) { |
| kfree(cmd); |
| return ret; |
| } |
| active = true; |
| } |
| cmd->flags |= CMDBATCH_RECURRING_START; |
| populate_ibs(adreno_dev, cmd, cmdobj); |
| } else |
| cmd->flags |= CMDBATCH_RECURRING_STOP; |
| |
| cmd->ctxt_id = drawobj->context->id; |
| |
| ret = hfi_context_register(adreno_dev, drawobj->context); |
| if (ret) { |
| adreno_active_count_put(adreno_dev); |
| active = false; |
| kfree(cmd); |
| return ret; |
| } |
| |
| cmd->hdr = CREATE_MSG_HDR(H2F_MSG_ISSUE_RECURRING_CMD, HFI_MSG_CMD); |
| |
| ret = gen8_hfi_send_cmd_async(adreno_dev, cmd, sizeof(*cmd)); |
| |
| kfree(cmd); |
| |
| if (ret) { |
| adreno_active_count_put(adreno_dev); |
| active = false; |
| return ret; |
| } |
| |
| if (test_bit(CMDOBJ_RECURRING_STOP, &cmdobj->priv)) { |
| adreno_hwsched_retire_cmdobj(hwsched, hwsched->recurring_cmdobj); |
| del_timer_sync(&hwsched->lsr_timer); |
| hwsched->recurring_cmdobj = NULL; |
| if (active) |
| adreno_active_count_put(adreno_dev); |
| active = false; |
| return ret; |
| } |
| |
| hwsched->recurring_cmdobj = cmdobj; |
| /* Star LSR timer for power stats collection */ |
| mod_timer(&hwsched->lsr_timer, jiffies + msecs_to_jiffies(10)); |
| return ret; |
| } |
| |
| void gen8_trigger_hw_fence_cpu(struct adreno_device *adreno_dev, |
| struct adreno_hw_fence_entry *entry) |
| { |
| int ret = msm_hw_fence_update_txq(adreno_dev->hwsched.hw_fence.handle, |
| entry->cmd.hash_index, 0, 0); |
| |
| if (ret) { |
| dev_err_ratelimited(adreno_dev->dev.dev, |
| "Failed to trigger hw fence via cpu: ctx:%d ts:%d ret:%d\n", |
| entry->drawctxt->base.id, (u32)entry->cmd.ts, ret); |
| return; |
| } |
| |
| msm_hw_fence_trigger_signal(adreno_dev->hwsched.hw_fence.handle, IPCC_CLIENT_GPU, |
| IPCC_CLIENT_APSS, 0); |
| } |
| |
| /* We don't want to unnecessarily wake the GMU to trigger hardware fences */ |
| static void drain_context_hw_fence_cpu(struct adreno_device *adreno_dev, |
| struct adreno_context *drawctxt) |
| { |
| struct adreno_hw_fence_entry *entry, *tmp; |
| |
| list_for_each_entry_safe(entry, tmp, &drawctxt->hw_fence_list, node) { |
| |
| gen8_trigger_hw_fence_cpu(adreno_dev, entry); |
| |
| gen8_remove_hw_fence_entry(adreno_dev, entry); |
| } |
| } |
| |
| int gen8_hwsched_drain_context_hw_fences(struct adreno_device *adreno_dev, |
| struct adreno_context *drawctxt) |
| { |
| struct adreno_hw_fence_entry *entry, *tmp; |
| int ret = 0; |
| |
| /* We don't need the drawctxt lock here as this context has already been invalidated */ |
| list_for_each_entry_safe(entry, tmp, &drawctxt->hw_fence_list, node) { |
| |
| /* Any error here is fatal */ |
| ret = gen8_send_hw_fence_hfi_wait_ack(adreno_dev, entry, |
| HW_FENCE_FLAG_SKIP_MEMSTORE); |
| if (ret) |
| break; |
| |
| gen8_remove_hw_fence_entry(adreno_dev, entry); |
| } |
| |
| return ret; |
| } |
| |
| static void trigger_context_unregister_fault(struct adreno_device *adreno_dev, |
| struct adreno_context *drawctxt) |
| { |
| gmu_core_fault_snapshot(KGSL_DEVICE(adreno_dev)); |
| |
| /* Make sure we send all fences from this context to the TxQueue after recovery */ |
| move_detached_context_hardware_fences(adreno_dev, drawctxt); |
| gen8_hwsched_fault(adreno_dev, ADRENO_GMU_FAULT); |
| } |
| |
| static int send_context_unregister_hfi(struct adreno_device *adreno_dev, |
| struct kgsl_context *context, u32 ts) |
| { |
| struct gen8_gmu_device *gmu = to_gen8_gmu(adreno_dev); |
| struct gen8_hwsched_hfi *hfi = to_gen8_hwsched_hfi(adreno_dev); |
| struct adreno_context *drawctxt = ADRENO_CONTEXT(context); |
| struct pending_cmd pending_ack; |
| struct hfi_unregister_ctxt_cmd cmd; |
| u32 seqnum; |
| int ret; |
| |
| /* Only send HFI if device is not in SLUMBER */ |
| if (!context->gmu_registered || |
| !test_bit(GMU_PRIV_GPU_STARTED, &gmu->flags)) { |
| drain_context_hw_fence_cpu(adreno_dev, drawctxt); |
| return 0; |
| } |
| |
| ret = CMD_MSG_HDR(cmd, H2F_MSG_UNREGISTER_CONTEXT); |
| if (ret) |
| return ret; |
| |
| cmd.ctxt_id = context->id, |
| cmd.ts = ts, |
| |
| /* |
| * Although we know device is powered on, we can still enter SLUMBER |
| * because the wait for ack below is done without holding the mutex. So |
| * take an active count before releasing the mutex so as to avoid a |
| * concurrent SLUMBER sequence while GMU is un-registering this context. |
| */ |
| ret = gen8_hwsched_active_count_get(adreno_dev); |
| if (ret) { |
| trigger_context_unregister_fault(adreno_dev, drawctxt); |
| return ret; |
| } |
| |
| seqnum = atomic_inc_return(&gmu->hfi.seqnum); |
| cmd.hdr = MSG_HDR_SET_SEQNUM_SIZE(cmd.hdr, seqnum, sizeof(cmd) >> 2); |
| add_waiter(hfi, cmd.hdr, &pending_ack); |
| |
| ret = gen8_hfi_cmdq_write(adreno_dev, (u32 *)&cmd, sizeof(cmd)); |
| if (ret) { |
| trigger_context_unregister_fault(adreno_dev, drawctxt); |
| goto done; |
| } |
| |
| ret = adreno_hwsched_ctxt_unregister_wait_completion(adreno_dev, |
| &gmu->pdev->dev, &pending_ack, gen8_hwsched_process_msgq, &cmd); |
| if (ret) { |
| trigger_context_unregister_fault(adreno_dev, drawctxt); |
| goto done; |
| } |
| |
| ret = check_detached_context_hardware_fences(adreno_dev, drawctxt); |
| if (!ret) |
| ret = check_ack_failure(adreno_dev, &pending_ack); |
| |
| done: |
| gen8_hwsched_active_count_put(adreno_dev); |
| del_waiter(hfi, &pending_ack); |
| |
| return ret; |
| } |
| |
| void gen8_hwsched_context_detach(struct adreno_context *drawctxt) |
| { |
| struct kgsl_context *context = &drawctxt->base; |
| struct kgsl_device *device = context->device; |
| struct adreno_device *adreno_dev = ADRENO_DEVICE(device); |
| int ret = 0; |
| |
| mutex_lock(&device->mutex); |
| |
| ret = send_context_unregister_hfi(adreno_dev, context, |
| drawctxt->internal_timestamp); |
| |
| if (!ret) { |
| kgsl_sharedmem_writel(device->memstore, |
| KGSL_MEMSTORE_OFFSET(context->id, soptimestamp), |
| drawctxt->timestamp); |
| |
| kgsl_sharedmem_writel(device->memstore, |
| KGSL_MEMSTORE_OFFSET(context->id, eoptimestamp), |
| drawctxt->timestamp); |
| |
| adreno_profile_process_results(adreno_dev); |
| } |
| |
| context->gmu_registered = false; |
| |
| mutex_unlock(&device->mutex); |
| } |
| |
| u32 gen8_hwsched_preempt_count_get(struct adreno_device *adreno_dev) |
| { |
| struct kgsl_device *device = KGSL_DEVICE(adreno_dev); |
| |
| if (device->state != KGSL_STATE_ACTIVE) |
| return 0; |
| |
| return gen8_hwsched_hfi_get_value(adreno_dev, HFI_VALUE_PREEMPT_COUNT); |
| } |
| |
| void gen8_hwsched_context_destroy(struct adreno_device *adreno_dev, |
| struct adreno_context *drawctxt) |
| { |
| if (!adreno_hwsched_context_queue_enabled(adreno_dev)) |
| return; |
| |
| if (drawctxt->gmu_context_queue.gmuaddr) |
| gen8_free_gmu_block(to_gen8_gmu(adreno_dev), &drawctxt->gmu_context_queue); |
| |
| if (drawctxt->gmu_hw_fence_queue.gmuaddr) |
| gen8_free_gmu_block(to_gen8_gmu(adreno_dev), &drawctxt->gmu_hw_fence_queue); |
| } |
| |
| int gen8_hwsched_disable_hw_fence_throttle(struct adreno_device *adreno_dev) |
| { |
| struct gen8_hwsched_hfi *hfi = to_gen8_hwsched_hfi(adreno_dev); |
| struct adreno_context *drawctxt = NULL; |
| u32 ts = 0; |
| int ret = 0; |
| |
| if (!test_bit(ADRENO_HWSCHED_HW_FENCE, &adreno_dev->hwsched.flags)) |
| return 0; |
| |
| spin_lock(&hfi->hw_fence.lock); |
| |
| drawctxt = hfi->hw_fence.defer_drawctxt; |
| ts = hfi->hw_fence.defer_ts; |
| |
| spin_unlock(&hfi->hw_fence.lock); |
| |
| if (!drawctxt) |
| goto done; |
| |
| ret = process_hw_fence_deferred_ctxt(adreno_dev, drawctxt, ts); |
| |
| kgsl_context_put(&drawctxt->base); |
| gen8_hwsched_active_count_put(adreno_dev); |
| |
| done: |
| _disable_hw_fence_throttle(adreno_dev, true); |
| |
| return ret; |
| } |