summaryrefslogtreecommitdiff
path: root/gxp-telemetry.c
diff options
context:
space:
mode:
Diffstat (limited to 'gxp-telemetry.c')
-rw-r--r--gxp-telemetry.c384
1 files changed, 384 insertions, 0 deletions
diff --git a/gxp-telemetry.c b/gxp-telemetry.c
new file mode 100644
index 0000000..4e97ec1
--- /dev/null
+++ b/gxp-telemetry.c
@@ -0,0 +1,384 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * GXP telemetry support
+ *
+ * Copyright (C) 2021 Google LLC
+ */
+
+#include <linux/slab.h>
+
+#include "gxp-config.h"
+#include "gxp-dma.h"
+#include "gxp-firmware-data.h"
+#include "gxp-telemetry.h"
+
+int gxp_telemetry_init(struct gxp_dev *gxp)
+{
+ struct gxp_telemetry_manager *mgr;
+
+ mgr = devm_kzalloc(gxp->dev, sizeof(*mgr), GFP_KERNEL);
+ if (!mgr)
+ return -ENOMEM;
+
+ mutex_init(&mgr->lock);
+
+ gxp->telemetry_mgr = mgr;
+
+ return 0;
+}
+
+/* Wrapper struct to be used by the telemetry vma_ops. */
+struct telemetry_vma_data {
+ struct gxp_dev *gxp;
+ struct buffer_data *data;
+ u8 type;
+};
+
+static void gxp_telemetry_vma_open(struct vm_area_struct *vma)
+{
+ struct gxp_dev *gxp;
+ struct buffer_data *data;
+
+ gxp = ((struct telemetry_vma_data *)vma->vm_private_data)->gxp;
+ data = ((struct telemetry_vma_data *)vma->vm_private_data)->data;
+
+ mutex_lock(&gxp->telemetry_mgr->lock);
+
+ refcount_inc(&data->ref_count);
+
+ mutex_unlock(&gxp->telemetry_mgr->lock);
+}
+
+static void gxp_telemetry_vma_close(struct vm_area_struct *vma)
+{
+ struct gxp_dev *gxp;
+ struct buffer_data *data;
+ u8 type;
+ int i;
+
+ gxp = ((struct telemetry_vma_data *)vma->vm_private_data)->gxp;
+ data = ((struct telemetry_vma_data *)vma->vm_private_data)->data;
+ type = ((struct telemetry_vma_data *)vma->vm_private_data)->type;
+
+ mutex_lock(&gxp->telemetry_mgr->lock);
+
+ if (refcount_dec_and_test(&data->ref_count)) {
+ if (data->enabled)
+ gxp_telemetry_disable(gxp, type);
+
+ for (i = 0; i < GXP_NUM_CORES; i++)
+ gxp_dma_free_coherent(gxp, BIT(i), data->size,
+ data->buffers[i],
+ data->buffer_daddrs[i]);
+ switch (type) {
+ case GXP_TELEMETRY_TYPE_LOGGING:
+ gxp->telemetry_mgr->logging_buff_data = NULL;
+ break;
+ case GXP_TELEMETRY_TYPE_TRACING:
+ gxp->telemetry_mgr->tracing_buff_data = NULL;
+ break;
+ default:
+ dev_warn(gxp->dev, "%s called with invalid type %u\n",
+ __func__, type);
+ }
+ kfree(data);
+ kfree(vma->vm_private_data);
+ }
+
+ mutex_unlock(&gxp->telemetry_mgr->lock);
+}
+
+static const struct vm_operations_struct gxp_telemetry_vma_ops = {
+ .open = gxp_telemetry_vma_open,
+ .close = gxp_telemetry_vma_close,
+};
+
+/**
+ * check_telemetry_type_availability() - Checks if @type is valid and whether
+ * buffers of that type already exists.
+ * @gxp: The GXP device to check availability for
+ * @type: Either `GXP_TELEMETRY_TYPE_LOGGING` or `GXP_TELEMETRY_TYPE_TRACING`
+ *
+ * Caller must hold the telemetry_manager's lock.
+ *
+ * Return:
+ * * 0 - @type is valid and can have new buffers created
+ * * -EBUSY - Buffers already exist for @type
+ * * -EINVAL - @type is not a valid telemetry type
+ */
+static int check_telemetry_type_availability(struct gxp_dev *gxp, u8 type)
+{
+ switch (type) {
+ case GXP_TELEMETRY_TYPE_LOGGING:
+ if (gxp->telemetry_mgr->logging_buff_data)
+ return -EBUSY;
+ break;
+ case GXP_TELEMETRY_TYPE_TRACING:
+ if (gxp->telemetry_mgr->tracing_buff_data)
+ return -EBUSY;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/**
+ * allocate_telemetry_buffers() - Allocate and populate a `struct buffer_data`,
+ * including allocating and mapping one coherent
+ * buffer of @size bytes per core.
+ * @gxp: The GXP device to allocate the buffers for
+ * @size: The size of buffer to allocate for each core
+ *
+ * Caller must hold the telemetry_manager's lock.
+ *
+ * Return: A pointer to the `struct buffer_data` if successful, NULL otherwise
+ */
+static struct buffer_data *allocate_telemetry_buffers(struct gxp_dev *gxp,
+ size_t size)
+{
+ struct buffer_data *data;
+ int i;
+
+ data = kzalloc(sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return NULL;
+
+ /* Allocate cache-coherent buffers for logging/tracing to */
+ for (i = 0; i < GXP_NUM_CORES; i++) {
+ data->buffers[i] =
+ gxp_dma_alloc_coherent(gxp, BIT(i), size,
+ &data->buffer_daddrs[i],
+ GFP_KERNEL, 0);
+ if (!data->buffers[i])
+ goto err_alloc;
+ }
+ data->size = size;
+ refcount_set(&data->ref_count, 1);
+ data->enabled = false;
+
+ return data;
+
+err_alloc:
+ for (; i > 0; i--) {
+ gxp_dma_free_coherent(gxp, BIT(i - 1), size,
+ data->buffers[i - 1],
+ data->buffer_daddrs[i - 1]);
+ }
+ kfree(data);
+
+ return NULL;
+}
+
+/**
+ * remap_telemetry_buffers() - Remaps a set of telemetry buffers into a
+ * user-space vm_area.
+ * @gxp: The GXP device the buffers were allocated for
+ * @vma: A vm area to remap the buffers into
+ * @data: The data describing a set of telemetry buffers to remap
+ *
+ * Caller must hold the telemetry_manager's lock.
+ *
+ * Return:
+ * * 0 - Success
+ * * otherwise - Error returned by `remap_pfn_range()`
+ */
+static int remap_telemetry_buffers(struct gxp_dev *gxp,
+ struct vm_area_struct *vma,
+ struct buffer_data *data)
+{
+ unsigned long orig_pgoff = vma->vm_pgoff;
+ int i;
+ unsigned long offset;
+ phys_addr_t phys;
+ int ret = 0;
+
+ /* mmap the buffers */
+ vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
+ vma->vm_flags |= VM_DONTCOPY | VM_DONTEXPAND | VM_DONTDUMP;
+ vma->vm_pgoff = 0;
+
+ for (i = 0; i < GXP_NUM_CORES; i++) {
+ /*
+ * Remap each core's buffer a page at a time, in case it is not
+ * physically contiguous.
+ */
+ for (offset = 0; offset < data->size; offset += PAGE_SIZE) {
+ /*
+ * `virt_to_phys()` does not work on memory allocated
+ * by `dma_alloc_coherent()`, so we have to use
+ * `iommu_iova_to_phys()` instead. Since all buffers
+ * are mapped to the default domain as well as any per-
+ * core domains, we can use it here to get the physical
+ * address of any valid IOVA, regardless of its core.
+ */
+ phys = iommu_iova_to_phys(
+ iommu_get_domain_for_dev(gxp->dev),
+ data->buffer_daddrs[i] + offset);
+ ret = remap_pfn_range(
+ vma, vma->vm_start + data->size * i + offset,
+ phys >> PAGE_SHIFT, PAGE_SIZE,
+ vma->vm_page_prot);
+ if (ret)
+ goto out;
+ }
+ }
+
+out:
+ vma->vm_pgoff = orig_pgoff;
+ vma->vm_ops = &gxp_telemetry_vma_ops;
+
+ return ret;
+}
+
+int gxp_telemetry_mmap_buffers(struct gxp_dev *gxp, u8 type,
+ struct vm_area_struct *vma)
+{
+ int ret = 0;
+ struct telemetry_vma_data *vma_data;
+ size_t total_size = vma->vm_end - vma->vm_start;
+ size_t size = total_size / GXP_NUM_CORES;
+ struct buffer_data *data;
+ int i;
+
+ if (!gxp->telemetry_mgr)
+ return -ENODEV;
+
+ /* Total size must divide evenly into 1 page-aligned buffer per core */
+ if (!total_size || !IS_ALIGNED(total_size, PAGE_SIZE * GXP_NUM_CORES))
+ return -EINVAL;
+
+ mutex_lock(&gxp->telemetry_mgr->lock);
+
+ ret = check_telemetry_type_availability(gxp, type);
+ if (ret)
+ goto err;
+
+ vma_data = kmalloc(sizeof(*vma_data), GFP_KERNEL);
+ if (!vma_data) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ data = allocate_telemetry_buffers(gxp, size);
+ if (!data) {
+ ret = -ENOMEM;
+ goto err_free_vma_data;
+ }
+
+ ret = remap_telemetry_buffers(gxp, vma, data);
+ if (ret)
+ goto err_free_buffers;
+
+ vma_data->gxp = gxp;
+ vma_data->data = data;
+ vma_data->type = type;
+ vma->vm_private_data = vma_data;
+
+ /* Save book-keeping on the buffers in the telemetry manager */
+ if (type == GXP_TELEMETRY_TYPE_LOGGING)
+ gxp->telemetry_mgr->logging_buff_data = data;
+ else /* type == GXP_TELEMETRY_TYPE_TRACING */
+ gxp->telemetry_mgr->tracing_buff_data = data;
+
+ mutex_unlock(&gxp->telemetry_mgr->lock);
+
+ return 0;
+
+err_free_buffers:
+ for (i = 0; i < GXP_NUM_CORES; i++)
+ gxp_dma_free_coherent(gxp, BIT(i), data->size, data->buffers[i],
+ data->buffer_daddrs[i]);
+ kfree(data);
+
+err_free_vma_data:
+ kfree(vma_data);
+
+err:
+ mutex_unlock(&gxp->telemetry_mgr->lock);
+ return ret;
+}
+
+int gxp_telemetry_enable(struct gxp_dev *gxp, u8 type)
+{
+ struct buffer_data *data;
+ int ret = 0;
+
+ mutex_lock(&gxp->telemetry_mgr->lock);
+
+ switch (type) {
+ case GXP_TELEMETRY_TYPE_LOGGING:
+ data = gxp->telemetry_mgr->logging_buff_data;
+ break;
+ case GXP_TELEMETRY_TYPE_TRACING:
+ data = gxp->telemetry_mgr->tracing_buff_data;
+ break;
+ default:
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (!data) {
+ ret = -ENXIO;
+ goto out;
+ }
+
+ /* Populate the buffer fields in firmware-data */
+ gxp_fw_data_set_telemetry_descriptors(
+ gxp, type, (u32 *)data->buffer_daddrs, data->size);
+
+ /* TODO(b/202937192) To be done in a future CL */
+ /* Notify any running cores that firmware-data was updated */
+
+ data->enabled = true;
+
+out:
+ mutex_unlock(&gxp->telemetry_mgr->lock);
+
+ return ret;
+}
+
+int gxp_telemetry_disable(struct gxp_dev *gxp, u8 type)
+{
+ struct buffer_data *data;
+ int ret = 0;
+ u32 null_daddrs[GXP_NUM_CORES] = {0};
+
+ mutex_lock(&gxp->telemetry_mgr->lock);
+
+ /* Cleanup telemetry manager's book-keeping */
+ switch (type) {
+ case GXP_TELEMETRY_TYPE_LOGGING:
+ data = gxp->telemetry_mgr->logging_buff_data;
+ break;
+ case GXP_TELEMETRY_TYPE_TRACING:
+ data = gxp->telemetry_mgr->tracing_buff_data;
+ break;
+ default:
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (!data) {
+ ret = -ENXIO;
+ goto out;
+ }
+
+ if (!data->enabled)
+ goto out;
+
+ /* Clear the log buffer fields in firmware-data */
+ gxp_fw_data_set_telemetry_descriptors(gxp, type, null_daddrs, 0);
+
+ /* TODO(b/202937192) To be done in a future CL */
+ /* Notify any running cores that firmware-data was updated */
+ /* Wait for ACK from firmware */
+
+ data->enabled = false;
+
+out:
+ mutex_unlock(&gxp->telemetry_mgr->lock);
+
+ return ret;
+}