| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * GXP MCU telemetry support |
| * |
| * Copyright (C) 2022 Google LLC |
| */ |
| |
| #include <linux/slab.h> |
| #include <linux/workqueue.h> |
| |
| #include <gcip/gcip-config.h> |
| #include <gcip/gcip-telemetry.h> |
| |
| #include "gxp-internal.h" |
| #include "gxp-kci.h" |
| #include "gxp-mcu-telemetry.h" |
| #include "gxp-mcu.h" |
| #include "gxp-notification.h" |
| |
| static struct gcip_telemetry * |
| select_telemetry(struct gxp_mcu_telemetry_ctx *ctx, |
| enum gcip_telemetry_type type) |
| { |
| switch (type) { |
| case GCIP_TELEMETRY_TRACE: |
| return &ctx->trace; |
| case GCIP_TELEMETRY_LOG: |
| return &ctx->log; |
| default: |
| WARN_ONCE(1, "Unrecognized GCIP telemetry type: %d", type); |
| /* return a valid object, don't crash the kernel */ |
| return &ctx->log; |
| } |
| } |
| |
| static struct gxp_mapped_resource * |
| select_telemetry_mem(struct gxp_mcu_telemetry_ctx *ctx, |
| enum gcip_telemetry_type type) |
| { |
| switch (type) { |
| case GCIP_TELEMETRY_TRACE: |
| return &ctx->trace_mem; |
| case GCIP_TELEMETRY_LOG: |
| return &ctx->log_mem; |
| default: |
| WARN_ONCE(1, "Unrecognized GCIP telemetry type: %d", type); |
| /* return a valid object, don't crash the kernel */ |
| return &ctx->log_mem; |
| } |
| } |
| |
| int gxp_mcu_telemetry_init(struct gxp_mcu *mcu) |
| { |
| struct gxp_mcu_telemetry_ctx *tel = &mcu->telemetry; |
| int ret; |
| |
| ret = gxp_mcu_mem_alloc_data(mcu, &tel->log_mem, |
| GXP_MCU_TELEMETRY_LOG_BUFFER_SIZE); |
| if (ret) |
| return ret; |
| |
| ret = gcip_telemetry_init(mcu->gxp->dev, &tel->log, "telemetry_log", |
| tel->log_mem.vaddr, |
| GXP_MCU_TELEMETRY_LOG_BUFFER_SIZE, |
| gcip_telemetry_fw_log); |
| if (ret) |
| goto free_log_mem; |
| |
| ret = gxp_mcu_mem_alloc_data(mcu, &tel->trace_mem, |
| GXP_MCU_TELEMETRY_TRACE_BUFFER_SIZE); |
| if (ret) |
| goto uninit_log; |
| |
| ret = gcip_telemetry_init(mcu->gxp->dev, &tel->trace, "telemetry_trace", |
| tel->trace_mem.vaddr, |
| GXP_MCU_TELEMETRY_TRACE_BUFFER_SIZE, |
| gcip_telemetry_fw_trace); |
| if (ret) |
| goto free_trace_mem; |
| |
| return 0; |
| |
| free_trace_mem: |
| gxp_mcu_mem_free_data(mcu, &tel->trace_mem); |
| |
| uninit_log: |
| gcip_telemetry_exit(&tel->log); |
| |
| free_log_mem: |
| gxp_mcu_mem_free_data(mcu, &tel->log_mem); |
| |
| return ret; |
| } |
| |
| void gxp_mcu_telemetry_exit(struct gxp_mcu *mcu) |
| { |
| gxp_mcu_mem_free_data(mcu, &mcu->telemetry.trace_mem); |
| gcip_telemetry_exit(&mcu->telemetry.trace); |
| gxp_mcu_mem_free_data(mcu, &mcu->telemetry.log_mem); |
| gcip_telemetry_exit(&mcu->telemetry.log); |
| } |
| |
| void gxp_mcu_telemetry_irq_handler(struct gxp_mcu *mcu) |
| { |
| struct gxp_mcu_telemetry_ctx *tel = &mcu->telemetry; |
| |
| gcip_telemetry_irq_handler(&tel->log); |
| gcip_telemetry_irq_handler(&tel->trace); |
| } |
| |
| int gxp_mcu_telemetry_kci(struct gxp_mcu *mcu) |
| { |
| struct gcip_telemetry_kci_args args = { |
| .kci = mcu->kci.mbx->mbx_impl.gcip_kci, |
| .addr = mcu->telemetry.log_mem.daddr, |
| .size = mcu->telemetry.log_mem.size, |
| }; |
| int ret; |
| |
| ret = gcip_telemetry_kci(&mcu->telemetry.log, |
| gxp_kci_map_mcu_log_buffer, &args); |
| if (ret) |
| return ret; |
| |
| args.addr = mcu->telemetry.trace_mem.daddr, |
| args.size = mcu->telemetry.trace_mem.size, |
| ret = gcip_telemetry_kci(&mcu->telemetry.trace, |
| gxp_kci_map_mcu_trace_buffer, &args); |
| |
| return ret; |
| } |
| |
| int gxp_mcu_telemetry_register_eventfd(struct gxp_mcu *mcu, |
| enum gcip_telemetry_type type, |
| u32 eventfd) |
| { |
| int ret; |
| |
| ret = gcip_telemetry_set_event(select_telemetry(&mcu->telemetry, type), |
| eventfd); |
| if (ret) |
| gxp_mcu_telemetry_unregister_eventfd(mcu, type); |
| |
| return ret; |
| } |
| |
| int gxp_mcu_telemetry_unregister_eventfd(struct gxp_mcu *mcu, |
| enum gcip_telemetry_type type) |
| { |
| gcip_telemetry_unset_event(select_telemetry(&mcu->telemetry, type)); |
| |
| return 0; |
| } |
| |
| struct telemetry_vma_data { |
| struct gcip_telemetry *tel; |
| refcount_t ref_count; |
| }; |
| |
| static void telemetry_vma_open(struct vm_area_struct *vma) |
| { |
| struct telemetry_vma_data *vma_data = |
| (struct telemetry_vma_data *)vma->vm_private_data; |
| struct gcip_telemetry *tel = vma_data->tel; |
| |
| WARN_ON_ONCE(!refcount_inc_not_zero(&vma_data->ref_count)); |
| gcip_telemetry_inc_mmap_count(tel, 1); |
| } |
| |
| static void telemetry_vma_close(struct vm_area_struct *vma) |
| { |
| struct telemetry_vma_data *vma_data = |
| (struct telemetry_vma_data *)vma->vm_private_data; |
| struct gcip_telemetry *tel = vma_data->tel; |
| |
| gcip_telemetry_inc_mmap_count(tel, -1); |
| if (refcount_dec_and_test(&vma_data->ref_count)) |
| kfree(vma_data); |
| } |
| |
| static const struct vm_operations_struct telemetry_vma_ops = { |
| .open = telemetry_vma_open, |
| .close = telemetry_vma_close, |
| }; |
| |
| struct gxp_mcu_telemetry_mmap_args { |
| struct gcip_telemetry *tel; |
| struct gxp_mapped_resource *mem; |
| struct vm_area_struct *vma; |
| }; |
| |
| static int telemetry_mmap_buffer(void *args) |
| { |
| struct gxp_mcu_telemetry_mmap_args *data = args; |
| struct gcip_telemetry *tel = data->tel; |
| struct gxp_mapped_resource *mem = data->mem; |
| struct vm_area_struct *vma = data->vma; |
| struct telemetry_vma_data *vma_data = |
| kmalloc(sizeof(*vma_data), GFP_KERNEL); |
| unsigned long orig_pgoff = vma->vm_pgoff; |
| int ret; |
| |
| if (!vma_data) |
| return -ENOMEM; |
| vma_data->tel = tel; |
| refcount_set(&vma_data->ref_count, 1); |
| |
| vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); |
| #if GCIP_HAS_VMA_FLAGS_API |
| vm_flags_set(vma, VM_DONTCOPY | VM_DONTEXPAND | VM_DONTDUMP); |
| #else |
| vma->vm_flags |= VM_DONTCOPY | VM_DONTEXPAND | VM_DONTDUMP; |
| #endif |
| vma->vm_pgoff = 0; |
| |
| if (mem->size > vma->vm_end - vma->vm_start) { |
| ret = -EINVAL; |
| goto out; |
| } |
| |
| ret = remap_pfn_range(vma, vma->vm_start, mem->paddr >> PAGE_SHIFT, |
| mem->size, vma->vm_page_prot); |
| vma->vm_pgoff = orig_pgoff; |
| |
| out: |
| if (ret) { |
| kfree(vma_data); |
| } else { |
| vma->vm_ops = &telemetry_vma_ops; |
| vma->vm_private_data = vma_data; |
| } |
| |
| return ret; |
| } |
| |
| int gxp_mcu_telemetry_mmap_buffer(struct gxp_mcu *mcu, |
| enum gcip_telemetry_type type, |
| struct vm_area_struct *vma) |
| { |
| struct gxp_mcu_telemetry_mmap_args args = { |
| .mem = select_telemetry_mem(&mcu->telemetry, type), |
| .tel = select_telemetry(&mcu->telemetry, type), |
| .vma = vma, |
| }; |
| |
| return gcip_telemetry_mmap_buffer(select_telemetry(&mcu->telemetry, |
| type), |
| telemetry_mmap_buffer, &args); |
| } |