// SPDX-License-Identifier: GPL-2.0 /* * GXP MCU telemetry support * * Copyright (C) 2022 Google LLC */ #include #include #include #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; vma_data->tel = tel; refcount_set(&vma_data->ref_count, 1); vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); vma->vm_flags |= VM_DONTCOPY | VM_DONTEXPAND | VM_DONTDUMP; 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); }