blob: 69ec8747cfecd200a6122ea2c0ccfdbe1b68ad9e [file] [log] [blame]
// 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-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);
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);
}