diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 10:05:51 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 10:05:51 +0000 |
commit | 5d1646d90e1f2cceb9f0828f4b28318cd0ec7744 (patch) | |
tree | a94efe259b9009378be6d90eb30d2b019d95c194 /drivers/hwtracing/intel_th | |
parent | Initial commit. (diff) | |
download | linux-5d1646d90e1f2cceb9f0828f4b28318cd0ec7744.tar.xz linux-5d1646d90e1f2cceb9f0828f4b28318cd0ec7744.zip |
Adding upstream version 5.10.209.upstream/5.10.209upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/hwtracing/intel_th')
-rw-r--r-- | drivers/hwtracing/intel_th/Kconfig | 86 | ||||
-rw-r--r-- | drivers/hwtracing/intel_th/Makefile | 25 | ||||
-rw-r--r-- | drivers/hwtracing/intel_th/acpi.c | 85 | ||||
-rw-r--r-- | drivers/hwtracing/intel_th/core.c | 1086 | ||||
-rw-r--r-- | drivers/hwtracing/intel_th/debug.c | 28 | ||||
-rw-r--r-- | drivers/hwtracing/intel_th/debug.h | 26 | ||||
-rw-r--r-- | drivers/hwtracing/intel_th/gth.c | 850 | ||||
-rw-r--r-- | drivers/hwtracing/intel_th/gth.h | 79 | ||||
-rw-r--r-- | drivers/hwtracing/intel_th/intel_th.h | 384 | ||||
-rw-r--r-- | drivers/hwtracing/intel_th/msu-sink.c | 119 | ||||
-rw-r--r-- | drivers/hwtracing/intel_th/msu.c | 2192 | ||||
-rw-r--r-- | drivers/hwtracing/intel_th/msu.h | 118 | ||||
-rw-r--r-- | drivers/hwtracing/intel_th/pci.c | 328 | ||||
-rw-r--r-- | drivers/hwtracing/intel_th/pti.c | 343 | ||||
-rw-r--r-- | drivers/hwtracing/intel_th/pti.h | 29 | ||||
-rw-r--r-- | drivers/hwtracing/intel_th/sth.c | 261 | ||||
-rw-r--r-- | drivers/hwtracing/intel_th/sth.h | 34 |
17 files changed, 6073 insertions, 0 deletions
diff --git a/drivers/hwtracing/intel_th/Kconfig b/drivers/hwtracing/intel_th/Kconfig new file mode 100644 index 000000000..4b6359326 --- /dev/null +++ b/drivers/hwtracing/intel_th/Kconfig @@ -0,0 +1,86 @@ +# SPDX-License-Identifier: GPL-2.0-only +config INTEL_TH + tristate "Intel(R) Trace Hub controller" + depends on HAS_DMA && HAS_IOMEM + help + Intel(R) Trace Hub (TH) is a set of hardware blocks (subdevices) that + produce, switch and output trace data from multiple hardware and + software sources over several types of trace output ports encoded + in System Trace Protocol (MIPI STPv2) and is intended to perform + full system debugging. + + This option enables intel_th bus and common code used by TH + subdevices to interact with each other and hardware and for + platform glue layers to drive Intel TH devices. + + Say Y here to enable Intel(R) Trace Hub controller support. + +if INTEL_TH + +config INTEL_TH_PCI + tristate "Intel(R) Trace Hub PCI controller" + depends on PCI + help + Intel(R) Trace Hub may exist as a PCI device. This option enables + support glue layer for PCI-based Intel TH. + + Say Y here to enable PCI Intel TH support. + +config INTEL_TH_ACPI + tristate "Intel(R) Trace Hub ACPI controller" + depends on ACPI + help + Intel(R) Trace Hub may exist as an ACPI device. This option enables + support glue layer for ACPI-based Intel TH. This typically implies + 'host debugger' mode, that is, the trace configuration and capture + is handled by an external debug host and corresponding controls will + not be available on the target. + + Say Y here to enable ACPI Intel TH support. + +config INTEL_TH_GTH + tristate "Intel(R) Trace Hub Global Trace Hub" + help + Global Trace Hub (GTH) is the central component of the + Intel TH infrastructure and acts as a switch for source + and output devices. This driver is required for other + Intel TH subdevices to initialize. + + Say Y here to enable GTH subdevice of Intel(R) Trace Hub. + +config INTEL_TH_STH + tristate "Intel(R) Trace Hub Software Trace Hub support" + depends on STM + help + Software Trace Hub (STH) enables trace data from software + trace sources to be sent out via Intel(R) Trace Hub. It + uses stm class device to interface with its sources. + + Say Y here to enable STH subdevice of Intel(R) Trace Hub. + +config INTEL_TH_MSU + tristate "Intel(R) Trace Hub Memory Storage Unit" + help + Memory Storage Unit (MSU) trace output device enables + storing STP traces to system memory. It supports single + and multiblock modes of operation and provides read() + and mmap() access to the collected data. + + Say Y here to enable MSU output device for Intel TH. + +config INTEL_TH_PTI + tristate "Intel(R) Trace Hub PTI output" + help + Parallel Trace Interface unit (PTI) is a trace output device + of Intel TH architecture that facilitates STP trace output via + a PTI port. + + Say Y to enable PTI output of Intel TH data. + +config INTEL_TH_DEBUG + bool "Intel(R) Trace Hub debugging" + depends on DEBUG_FS + help + Say Y here to enable debugging. + +endif diff --git a/drivers/hwtracing/intel_th/Makefile b/drivers/hwtracing/intel_th/Makefile new file mode 100644 index 000000000..b63eb8f30 --- /dev/null +++ b/drivers/hwtracing/intel_th/Makefile @@ -0,0 +1,25 @@ +# SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_INTEL_TH) += intel_th.o +intel_th-y := core.o +intel_th-$(CONFIG_INTEL_TH_DEBUG) += debug.o + +obj-$(CONFIG_INTEL_TH_PCI) += intel_th_pci.o +intel_th_pci-y := pci.o + +obj-$(CONFIG_INTEL_TH_ACPI) += intel_th_acpi.o +intel_th_acpi-y := acpi.o + +obj-$(CONFIG_INTEL_TH_GTH) += intel_th_gth.o +intel_th_gth-y := gth.o + +obj-$(CONFIG_INTEL_TH_STH) += intel_th_sth.o +intel_th_sth-y := sth.o + +obj-$(CONFIG_INTEL_TH_MSU) += intel_th_msu.o +intel_th_msu-y := msu.o + +obj-$(CONFIG_INTEL_TH_PTI) += intel_th_pti.o +intel_th_pti-y := pti.o + +obj-$(CONFIG_INTEL_TH_MSU) += intel_th_msu_sink.o +intel_th_msu_sink-y := msu-sink.o diff --git a/drivers/hwtracing/intel_th/acpi.c b/drivers/hwtracing/intel_th/acpi.c new file mode 100644 index 000000000..87f9024e4 --- /dev/null +++ b/drivers/hwtracing/intel_th/acpi.c @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel(R) Trace Hub ACPI driver + * + * Copyright (C) 2017 Intel Corporation. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/types.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/sysfs.h> +#include <linux/platform_device.h> +#include <linux/acpi.h> + +#include "intel_th.h" + +#define DRIVER_NAME "intel_th_acpi" + +static const struct intel_th_drvdata intel_th_acpi_pch = { + .host_mode_only = 1, +}; + +static const struct intel_th_drvdata intel_th_acpi_uncore = { + .host_mode_only = 1, +}; + +static const struct acpi_device_id intel_th_acpi_ids[] = { + { "INTC1000", (kernel_ulong_t)&intel_th_acpi_uncore }, + { "INTC1001", (kernel_ulong_t)&intel_th_acpi_pch }, + { "", 0 }, +}; + +MODULE_DEVICE_TABLE(acpi, intel_th_acpi_ids); + +static int intel_th_acpi_probe(struct platform_device *pdev) +{ + struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); + struct resource resource[TH_MMIO_END]; + const struct acpi_device_id *id; + struct intel_th *th; + int i, r; + + id = acpi_match_device(intel_th_acpi_ids, &pdev->dev); + if (!id) + return -ENODEV; + + for (i = 0, r = 0; i < pdev->num_resources && r < TH_MMIO_END; i++) + if (pdev->resource[i].flags & + (IORESOURCE_IRQ | IORESOURCE_MEM)) + resource[r++] = pdev->resource[i]; + + th = intel_th_alloc(&pdev->dev, (void *)id->driver_data, resource, r); + if (IS_ERR(th)) + return PTR_ERR(th); + + adev->driver_data = th; + + return 0; +} + +static int intel_th_acpi_remove(struct platform_device *pdev) +{ + struct intel_th *th = platform_get_drvdata(pdev); + + intel_th_free(th); + + return 0; +} + +static struct platform_driver intel_th_acpi_driver = { + .probe = intel_th_acpi_probe, + .remove = intel_th_acpi_remove, + .driver = { + .name = DRIVER_NAME, + .acpi_match_table = intel_th_acpi_ids, + }, +}; + +module_platform_driver(intel_th_acpi_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Intel(R) Trace Hub ACPI controller driver"); +MODULE_AUTHOR("Alexander Shishkin <alexander.shishkin@intel.com>"); diff --git a/drivers/hwtracing/intel_th/core.c b/drivers/hwtracing/intel_th/core.c new file mode 100644 index 000000000..9cb8c7d13 --- /dev/null +++ b/drivers/hwtracing/intel_th/core.c @@ -0,0 +1,1086 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel(R) Trace Hub driver core + * + * Copyright (C) 2014-2015 Intel Corporation. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/types.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/sysfs.h> +#include <linux/kdev_t.h> +#include <linux/debugfs.h> +#include <linux/idr.h> +#include <linux/pci.h> +#include <linux/pm_runtime.h> +#include <linux/dma-mapping.h> + +#include "intel_th.h" +#include "debug.h" + +static bool host_mode __read_mostly; +module_param(host_mode, bool, 0444); + +static DEFINE_IDA(intel_th_ida); + +static int intel_th_match(struct device *dev, struct device_driver *driver) +{ + struct intel_th_driver *thdrv = to_intel_th_driver(driver); + struct intel_th_device *thdev = to_intel_th_device(dev); + + if (thdev->type == INTEL_TH_SWITCH && + (!thdrv->enable || !thdrv->disable)) + return 0; + + return !strcmp(thdev->name, driver->name); +} + +static int intel_th_child_remove(struct device *dev, void *data) +{ + device_release_driver(dev); + + return 0; +} + +static int intel_th_probe(struct device *dev) +{ + struct intel_th_driver *thdrv = to_intel_th_driver(dev->driver); + struct intel_th_device *thdev = to_intel_th_device(dev); + struct intel_th_driver *hubdrv; + struct intel_th_device *hub = NULL; + int ret; + + if (thdev->type == INTEL_TH_SWITCH) + hub = thdev; + else if (dev->parent) + hub = to_intel_th_device(dev->parent); + + if (!hub || !hub->dev.driver) + return -EPROBE_DEFER; + + hubdrv = to_intel_th_driver(hub->dev.driver); + + pm_runtime_set_active(dev); + pm_runtime_no_callbacks(dev); + pm_runtime_enable(dev); + + ret = thdrv->probe(to_intel_th_device(dev)); + if (ret) + goto out_pm; + + if (thdrv->attr_group) { + ret = sysfs_create_group(&thdev->dev.kobj, thdrv->attr_group); + if (ret) + goto out; + } + + if (thdev->type == INTEL_TH_OUTPUT && + !intel_th_output_assigned(thdev)) + /* does not talk to hardware */ + ret = hubdrv->assign(hub, thdev); + +out: + if (ret) + thdrv->remove(thdev); + +out_pm: + if (ret) + pm_runtime_disable(dev); + + return ret; +} + +static void intel_th_device_remove(struct intel_th_device *thdev); + +static int intel_th_remove(struct device *dev) +{ + struct intel_th_driver *thdrv = to_intel_th_driver(dev->driver); + struct intel_th_device *thdev = to_intel_th_device(dev); + struct intel_th_device *hub = to_intel_th_hub(thdev); + int err; + + if (thdev->type == INTEL_TH_SWITCH) { + struct intel_th *th = to_intel_th(hub); + int i, lowest; + + /* disconnect outputs */ + err = device_for_each_child(dev, thdev, intel_th_child_remove); + if (err) + return err; + + /* + * Remove outputs, that is, hub's children: they are created + * at hub's probe time by having the hub call + * intel_th_output_enable() for each of them. + */ + for (i = 0, lowest = -1; i < th->num_thdevs; i++) { + /* + * Move the non-output devices from higher up the + * th->thdev[] array to lower positions to maintain + * a contiguous array. + */ + if (th->thdev[i]->type != INTEL_TH_OUTPUT) { + if (lowest >= 0) { + th->thdev[lowest] = th->thdev[i]; + th->thdev[i] = NULL; + ++lowest; + } + + continue; + } + + if (lowest == -1) + lowest = i; + + intel_th_device_remove(th->thdev[i]); + th->thdev[i] = NULL; + } + + if (lowest >= 0) + th->num_thdevs = lowest; + } + + if (thdrv->attr_group) + sysfs_remove_group(&thdev->dev.kobj, thdrv->attr_group); + + pm_runtime_get_sync(dev); + + thdrv->remove(thdev); + + if (intel_th_output_assigned(thdev)) { + struct intel_th_driver *hubdrv = + to_intel_th_driver(dev->parent->driver); + + if (hub->dev.driver) + /* does not talk to hardware */ + hubdrv->unassign(hub, thdev); + } + + pm_runtime_disable(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + + return 0; +} + +static struct bus_type intel_th_bus = { + .name = "intel_th", + .match = intel_th_match, + .probe = intel_th_probe, + .remove = intel_th_remove, +}; + +static void intel_th_device_free(struct intel_th_device *thdev); + +static void intel_th_device_release(struct device *dev) +{ + intel_th_device_free(to_intel_th_device(dev)); +} + +static struct device_type intel_th_source_device_type = { + .name = "intel_th_source_device", + .release = intel_th_device_release, +}; + +static char *intel_th_output_devnode(struct device *dev, umode_t *mode, + kuid_t *uid, kgid_t *gid) +{ + struct intel_th_device *thdev = to_intel_th_device(dev); + struct intel_th *th = to_intel_th(thdev); + char *node; + + if (thdev->id >= 0) + node = kasprintf(GFP_KERNEL, "intel_th%d/%s%d", th->id, + thdev->name, thdev->id); + else + node = kasprintf(GFP_KERNEL, "intel_th%d/%s", th->id, + thdev->name); + + return node; +} + +static ssize_t port_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct intel_th_device *thdev = to_intel_th_device(dev); + + if (thdev->output.port >= 0) + return scnprintf(buf, PAGE_SIZE, "%u\n", thdev->output.port); + + return scnprintf(buf, PAGE_SIZE, "unassigned\n"); +} + +static DEVICE_ATTR_RO(port); + +static void intel_th_trace_prepare(struct intel_th_device *thdev) +{ + struct intel_th_device *hub = to_intel_th_hub(thdev); + struct intel_th_driver *hubdrv = to_intel_th_driver(hub->dev.driver); + + if (hub->type != INTEL_TH_SWITCH) + return; + + if (thdev->type != INTEL_TH_OUTPUT) + return; + + pm_runtime_get_sync(&thdev->dev); + hubdrv->prepare(hub, &thdev->output); + pm_runtime_put(&thdev->dev); +} + +static int intel_th_output_activate(struct intel_th_device *thdev) +{ + struct intel_th_driver *thdrv = + to_intel_th_driver_or_null(thdev->dev.driver); + struct intel_th *th = to_intel_th(thdev); + int ret = 0; + + if (!thdrv) + return -ENODEV; + + if (!try_module_get(thdrv->driver.owner)) + return -ENODEV; + + pm_runtime_get_sync(&thdev->dev); + + if (th->activate) + ret = th->activate(th); + if (ret) + goto fail_put; + + intel_th_trace_prepare(thdev); + if (thdrv->activate) + ret = thdrv->activate(thdev); + else + intel_th_trace_enable(thdev); + + if (ret) + goto fail_deactivate; + + return 0; + +fail_deactivate: + if (th->deactivate) + th->deactivate(th); + +fail_put: + pm_runtime_put(&thdev->dev); + module_put(thdrv->driver.owner); + + return ret; +} + +static void intel_th_output_deactivate(struct intel_th_device *thdev) +{ + struct intel_th_driver *thdrv = + to_intel_th_driver_or_null(thdev->dev.driver); + struct intel_th *th = to_intel_th(thdev); + + if (!thdrv) + return; + + if (thdrv->deactivate) + thdrv->deactivate(thdev); + else + intel_th_trace_disable(thdev); + + if (th->deactivate) + th->deactivate(th); + + pm_runtime_put(&thdev->dev); + module_put(thdrv->driver.owner); +} + +static ssize_t active_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct intel_th_device *thdev = to_intel_th_device(dev); + + return scnprintf(buf, PAGE_SIZE, "%d\n", thdev->output.active); +} + +static ssize_t active_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct intel_th_device *thdev = to_intel_th_device(dev); + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + + if (!!val != thdev->output.active) { + if (val) + ret = intel_th_output_activate(thdev); + else + intel_th_output_deactivate(thdev); + } + + return ret ? ret : size; +} + +static DEVICE_ATTR_RW(active); + +static struct attribute *intel_th_output_attrs[] = { + &dev_attr_port.attr, + &dev_attr_active.attr, + NULL, +}; + +ATTRIBUTE_GROUPS(intel_th_output); + +static struct device_type intel_th_output_device_type = { + .name = "intel_th_output_device", + .groups = intel_th_output_groups, + .release = intel_th_device_release, + .devnode = intel_th_output_devnode, +}; + +static struct device_type intel_th_switch_device_type = { + .name = "intel_th_switch_device", + .release = intel_th_device_release, +}; + +static struct device_type *intel_th_device_type[] = { + [INTEL_TH_SOURCE] = &intel_th_source_device_type, + [INTEL_TH_OUTPUT] = &intel_th_output_device_type, + [INTEL_TH_SWITCH] = &intel_th_switch_device_type, +}; + +int intel_th_driver_register(struct intel_th_driver *thdrv) +{ + if (!thdrv->probe || !thdrv->remove) + return -EINVAL; + + thdrv->driver.bus = &intel_th_bus; + + return driver_register(&thdrv->driver); +} +EXPORT_SYMBOL_GPL(intel_th_driver_register); + +void intel_th_driver_unregister(struct intel_th_driver *thdrv) +{ + driver_unregister(&thdrv->driver); +} +EXPORT_SYMBOL_GPL(intel_th_driver_unregister); + +static struct intel_th_device * +intel_th_device_alloc(struct intel_th *th, unsigned int type, const char *name, + int id) +{ + struct device *parent; + struct intel_th_device *thdev; + + if (type == INTEL_TH_OUTPUT) + parent = &th->hub->dev; + else + parent = th->dev; + + thdev = kzalloc(sizeof(*thdev) + strlen(name) + 1, GFP_KERNEL); + if (!thdev) + return NULL; + + thdev->id = id; + thdev->type = type; + + strcpy(thdev->name, name); + device_initialize(&thdev->dev); + thdev->dev.bus = &intel_th_bus; + thdev->dev.type = intel_th_device_type[type]; + thdev->dev.parent = parent; + thdev->dev.dma_mask = parent->dma_mask; + thdev->dev.dma_parms = parent->dma_parms; + dma_set_coherent_mask(&thdev->dev, parent->coherent_dma_mask); + if (id >= 0) + dev_set_name(&thdev->dev, "%d-%s%d", th->id, name, id); + else + dev_set_name(&thdev->dev, "%d-%s", th->id, name); + + return thdev; +} + +static int intel_th_device_add_resources(struct intel_th_device *thdev, + struct resource *res, int nres) +{ + struct resource *r; + + r = kmemdup(res, sizeof(*res) * nres, GFP_KERNEL); + if (!r) + return -ENOMEM; + + thdev->resource = r; + thdev->num_resources = nres; + + return 0; +} + +static void intel_th_device_remove(struct intel_th_device *thdev) +{ + device_del(&thdev->dev); + put_device(&thdev->dev); +} + +static void intel_th_device_free(struct intel_th_device *thdev) +{ + kfree(thdev->resource); + kfree(thdev); +} + +/* + * Intel(R) Trace Hub subdevices + */ +static const struct intel_th_subdevice { + const char *name; + struct resource res[3]; + unsigned nres; + unsigned type; + unsigned otype; + bool mknode; + unsigned scrpd; + int id; +} intel_th_subdevices[] = { + { + .nres = 1, + .res = { + { + /* Handle TSCU and CTS from GTH driver */ + .start = REG_GTH_OFFSET, + .end = REG_CTS_OFFSET + REG_CTS_LENGTH - 1, + .flags = IORESOURCE_MEM, + }, + }, + .name = "gth", + .type = INTEL_TH_SWITCH, + .id = -1, + }, + { + .nres = 2, + .res = { + { + .start = REG_MSU_OFFSET, + .end = REG_MSU_OFFSET + REG_MSU_LENGTH - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = BUF_MSU_OFFSET, + .end = BUF_MSU_OFFSET + BUF_MSU_LENGTH - 1, + .flags = IORESOURCE_MEM, + }, + }, + .name = "msc", + .id = 0, + .type = INTEL_TH_OUTPUT, + .mknode = true, + .otype = GTH_MSU, + .scrpd = SCRPD_MEM_IS_PRIM_DEST | SCRPD_MSC0_IS_ENABLED, + }, + { + .nres = 2, + .res = { + { + .start = REG_MSU_OFFSET, + .end = REG_MSU_OFFSET + REG_MSU_LENGTH - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = BUF_MSU_OFFSET, + .end = BUF_MSU_OFFSET + BUF_MSU_LENGTH - 1, + .flags = IORESOURCE_MEM, + }, + }, + .name = "msc", + .id = 1, + .type = INTEL_TH_OUTPUT, + .mknode = true, + .otype = GTH_MSU, + .scrpd = SCRPD_MEM_IS_PRIM_DEST | SCRPD_MSC1_IS_ENABLED, + }, + { + .nres = 2, + .res = { + { + .start = REG_STH_OFFSET, + .end = REG_STH_OFFSET + REG_STH_LENGTH - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = TH_MMIO_SW, + .end = 0, + .flags = IORESOURCE_MEM, + }, + }, + .id = -1, + .name = "sth", + .type = INTEL_TH_SOURCE, + }, + { + .nres = 2, + .res = { + { + .start = REG_STH_OFFSET, + .end = REG_STH_OFFSET + REG_STH_LENGTH - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = TH_MMIO_RTIT, + .end = 0, + .flags = IORESOURCE_MEM, + }, + }, + .id = -1, + .name = "rtit", + .type = INTEL_TH_SOURCE, + }, + { + .nres = 1, + .res = { + { + .start = REG_PTI_OFFSET, + .end = REG_PTI_OFFSET + REG_PTI_LENGTH - 1, + .flags = IORESOURCE_MEM, + }, + }, + .id = -1, + .name = "pti", + .type = INTEL_TH_OUTPUT, + .otype = GTH_PTI, + .scrpd = SCRPD_PTI_IS_PRIM_DEST, + }, + { + .nres = 1, + .res = { + { + .start = REG_PTI_OFFSET, + .end = REG_PTI_OFFSET + REG_PTI_LENGTH - 1, + .flags = IORESOURCE_MEM, + }, + }, + .id = -1, + .name = "lpp", + .type = INTEL_TH_OUTPUT, + .otype = GTH_LPP, + .scrpd = SCRPD_PTI_IS_PRIM_DEST, + }, + { + .nres = 1, + .res = { + { + .start = REG_DCIH_OFFSET, + .end = REG_DCIH_OFFSET + REG_DCIH_LENGTH - 1, + .flags = IORESOURCE_MEM, + }, + }, + .id = -1, + .name = "dcih", + .type = INTEL_TH_OUTPUT, + }, +}; + +#ifdef CONFIG_MODULES +static void __intel_th_request_hub_module(struct work_struct *work) +{ + struct intel_th *th = container_of(work, struct intel_th, + request_module_work); + + request_module("intel_th_%s", th->hub->name); +} + +static int intel_th_request_hub_module(struct intel_th *th) +{ + INIT_WORK(&th->request_module_work, __intel_th_request_hub_module); + schedule_work(&th->request_module_work); + + return 0; +} + +static void intel_th_request_hub_module_flush(struct intel_th *th) +{ + flush_work(&th->request_module_work); +} +#else +static inline int intel_th_request_hub_module(struct intel_th *th) +{ + return -EINVAL; +} + +static inline void intel_th_request_hub_module_flush(struct intel_th *th) +{ +} +#endif /* CONFIG_MODULES */ + +static struct intel_th_device * +intel_th_subdevice_alloc(struct intel_th *th, + const struct intel_th_subdevice *subdev) +{ + struct intel_th_device *thdev; + struct resource res[3]; + unsigned int req = 0; + int r, err; + + thdev = intel_th_device_alloc(th, subdev->type, subdev->name, + subdev->id); + if (!thdev) + return ERR_PTR(-ENOMEM); + + thdev->drvdata = th->drvdata; + + memcpy(res, subdev->res, + sizeof(struct resource) * subdev->nres); + + for (r = 0; r < subdev->nres; r++) { + struct resource *devres = th->resource; + int bar = TH_MMIO_CONFIG; + + /* + * Take .end == 0 to mean 'take the whole bar', + * .start then tells us which bar it is. Default to + * TH_MMIO_CONFIG. + */ + if (!res[r].end && res[r].flags == IORESOURCE_MEM) { + bar = res[r].start; + err = -ENODEV; + if (bar >= th->num_resources) + goto fail_put_device; + res[r].start = 0; + res[r].end = resource_size(&devres[bar]) - 1; + } + + if (res[r].flags & IORESOURCE_MEM) { + res[r].start += devres[bar].start; + res[r].end += devres[bar].start; + + dev_dbg(th->dev, "%s:%d @ %pR\n", + subdev->name, r, &res[r]); + } else if (res[r].flags & IORESOURCE_IRQ) { + /* + * Only pass on the IRQ if we have useful interrupts: + * the ones that can be configured via MINTCTL. + */ + if (INTEL_TH_CAP(th, has_mintctl) && th->irq != -1) + res[r].start = th->irq; + } + } + + err = intel_th_device_add_resources(thdev, res, subdev->nres); + if (err) + goto fail_put_device; + + if (subdev->type == INTEL_TH_OUTPUT) { + if (subdev->mknode) + thdev->dev.devt = MKDEV(th->major, th->num_thdevs); + thdev->output.type = subdev->otype; + thdev->output.port = -1; + thdev->output.scratchpad = subdev->scrpd; + } else if (subdev->type == INTEL_TH_SWITCH) { + thdev->host_mode = + INTEL_TH_CAP(th, host_mode_only) ? true : host_mode; + th->hub = thdev; + } + + err = device_add(&thdev->dev); + if (err) + goto fail_free_res; + + /* need switch driver to be loaded to enumerate the rest */ + if (subdev->type == INTEL_TH_SWITCH && !req) { + err = intel_th_request_hub_module(th); + if (!err) + req++; + } + + return thdev; + +fail_free_res: + kfree(thdev->resource); + +fail_put_device: + put_device(&thdev->dev); + + return ERR_PTR(err); +} + +/** + * intel_th_output_enable() - find and enable a device for a given output type + * @th: Intel TH instance + * @otype: output type + * + * Go through the unallocated output devices, find the first one whos type + * matches @otype and instantiate it. These devices are removed when the hub + * device is removed, see intel_th_remove(). + */ +int intel_th_output_enable(struct intel_th *th, unsigned int otype) +{ + struct intel_th_device *thdev; + int src = 0, dst = 0; + + for (src = 0, dst = 0; dst <= th->num_thdevs; src++, dst++) { + for (; src < ARRAY_SIZE(intel_th_subdevices); src++) { + if (intel_th_subdevices[src].type != INTEL_TH_OUTPUT) + continue; + + if (intel_th_subdevices[src].otype != otype) + continue; + + break; + } + + /* no unallocated matching subdevices */ + if (src == ARRAY_SIZE(intel_th_subdevices)) + return -ENODEV; + + for (; dst < th->num_thdevs; dst++) { + if (th->thdev[dst]->type != INTEL_TH_OUTPUT) + continue; + + if (th->thdev[dst]->output.type != otype) + continue; + + break; + } + + /* + * intel_th_subdevices[src] matches our requirements and is + * not matched in th::thdev[] + */ + if (dst == th->num_thdevs) + goto found; + } + + return -ENODEV; + +found: + thdev = intel_th_subdevice_alloc(th, &intel_th_subdevices[src]); + if (IS_ERR(thdev)) + return PTR_ERR(thdev); + + th->thdev[th->num_thdevs++] = thdev; + + return 0; +} +EXPORT_SYMBOL_GPL(intel_th_output_enable); + +static int intel_th_populate(struct intel_th *th) +{ + int src; + + /* create devices for each intel_th_subdevice */ + for (src = 0; src < ARRAY_SIZE(intel_th_subdevices); src++) { + const struct intel_th_subdevice *subdev = + &intel_th_subdevices[src]; + struct intel_th_device *thdev; + + /* only allow SOURCE and SWITCH devices in host mode */ + if ((INTEL_TH_CAP(th, host_mode_only) || host_mode) && + subdev->type == INTEL_TH_OUTPUT) + continue; + + /* + * don't enable port OUTPUTs in this path; SWITCH enables them + * via intel_th_output_enable() + */ + if (subdev->type == INTEL_TH_OUTPUT && + subdev->otype != GTH_NONE) + continue; + + thdev = intel_th_subdevice_alloc(th, subdev); + /* note: caller should free subdevices from th::thdev[] */ + if (IS_ERR(thdev)) { + /* ENODEV for individual subdevices is allowed */ + if (PTR_ERR(thdev) == -ENODEV) + continue; + + return PTR_ERR(thdev); + } + + th->thdev[th->num_thdevs++] = thdev; + } + + return 0; +} + +static int intel_th_output_open(struct inode *inode, struct file *file) +{ + const struct file_operations *fops; + struct intel_th_driver *thdrv; + struct device *dev; + int err; + + dev = bus_find_device_by_devt(&intel_th_bus, inode->i_rdev); + if (!dev || !dev->driver) + return -ENODEV; + + thdrv = to_intel_th_driver(dev->driver); + fops = fops_get(thdrv->fops); + if (!fops) + return -ENODEV; + + replace_fops(file, fops); + + file->private_data = to_intel_th_device(dev); + + if (file->f_op->open) { + err = file->f_op->open(inode, file); + return err; + } + + return 0; +} + +static const struct file_operations intel_th_output_fops = { + .open = intel_th_output_open, + .llseek = noop_llseek, +}; + +static irqreturn_t intel_th_irq(int irq, void *data) +{ + struct intel_th *th = data; + irqreturn_t ret = IRQ_NONE; + struct intel_th_driver *d; + int i; + + for (i = 0; i < th->num_thdevs; i++) { + if (th->thdev[i]->type != INTEL_TH_OUTPUT) + continue; + + d = to_intel_th_driver(th->thdev[i]->dev.driver); + if (d && d->irq) + ret |= d->irq(th->thdev[i]); + } + + return ret; +} + +/** + * intel_th_alloc() - allocate a new Intel TH device and its subdevices + * @dev: parent device + * @devres: resources indexed by th_mmio_idx + * @irq: irq number + */ +struct intel_th * +intel_th_alloc(struct device *dev, struct intel_th_drvdata *drvdata, + struct resource *devres, unsigned int ndevres) +{ + int err, r, nr_mmios = 0; + struct intel_th *th; + + th = kzalloc(sizeof(*th), GFP_KERNEL); + if (!th) + return ERR_PTR(-ENOMEM); + + th->id = ida_simple_get(&intel_th_ida, 0, 0, GFP_KERNEL); + if (th->id < 0) { + err = th->id; + goto err_alloc; + } + + th->major = __register_chrdev(0, 0, TH_POSSIBLE_OUTPUTS, + "intel_th/output", &intel_th_output_fops); + if (th->major < 0) { + err = th->major; + goto err_ida; + } + th->irq = -1; + th->dev = dev; + th->drvdata = drvdata; + + for (r = 0; r < ndevres; r++) + switch (devres[r].flags & IORESOURCE_TYPE_BITS) { + case IORESOURCE_MEM: + th->resource[nr_mmios++] = devres[r]; + break; + case IORESOURCE_IRQ: + err = devm_request_irq(dev, devres[r].start, + intel_th_irq, IRQF_SHARED, + dev_name(dev), th); + if (err) + goto err_chrdev; + + if (th->irq == -1) + th->irq = devres[r].start; + th->num_irqs++; + break; + default: + dev_warn(dev, "Unknown resource type %lx\n", + devres[r].flags); + break; + } + + th->num_resources = nr_mmios; + + dev_set_drvdata(dev, th); + + pm_runtime_no_callbacks(dev); + pm_runtime_put(dev); + pm_runtime_allow(dev); + + err = intel_th_populate(th); + if (err) { + /* free the subdevices and undo everything */ + intel_th_free(th); + return ERR_PTR(err); + } + + return th; + +err_chrdev: + __unregister_chrdev(th->major, 0, TH_POSSIBLE_OUTPUTS, + "intel_th/output"); + +err_ida: + ida_simple_remove(&intel_th_ida, th->id); + +err_alloc: + kfree(th); + + return ERR_PTR(err); +} +EXPORT_SYMBOL_GPL(intel_th_alloc); + +void intel_th_free(struct intel_th *th) +{ + int i; + + intel_th_request_hub_module_flush(th); + + intel_th_device_remove(th->hub); + for (i = 0; i < th->num_thdevs; i++) { + if (th->thdev[i] != th->hub) + intel_th_device_remove(th->thdev[i]); + th->thdev[i] = NULL; + } + + th->num_thdevs = 0; + + for (i = 0; i < th->num_irqs; i++) + devm_free_irq(th->dev, th->irq + i, th); + + pm_runtime_get_sync(th->dev); + pm_runtime_forbid(th->dev); + + __unregister_chrdev(th->major, 0, TH_POSSIBLE_OUTPUTS, + "intel_th/output"); + + ida_simple_remove(&intel_th_ida, th->id); + + kfree(th); +} +EXPORT_SYMBOL_GPL(intel_th_free); + +/** + * intel_th_trace_enable() - enable tracing for an output device + * @thdev: output device that requests tracing be enabled + */ +int intel_th_trace_enable(struct intel_th_device *thdev) +{ + struct intel_th_device *hub = to_intel_th_device(thdev->dev.parent); + struct intel_th_driver *hubdrv = to_intel_th_driver(hub->dev.driver); + + if (WARN_ON_ONCE(hub->type != INTEL_TH_SWITCH)) + return -EINVAL; + + if (WARN_ON_ONCE(thdev->type != INTEL_TH_OUTPUT)) + return -EINVAL; + + pm_runtime_get_sync(&thdev->dev); + hubdrv->enable(hub, &thdev->output); + + return 0; +} +EXPORT_SYMBOL_GPL(intel_th_trace_enable); + +/** + * intel_th_trace_switch() - execute a switch sequence + * @thdev: output device that requests tracing switch + */ +int intel_th_trace_switch(struct intel_th_device *thdev) +{ + struct intel_th_device *hub = to_intel_th_device(thdev->dev.parent); + struct intel_th_driver *hubdrv = to_intel_th_driver(hub->dev.driver); + + if (WARN_ON_ONCE(hub->type != INTEL_TH_SWITCH)) + return -EINVAL; + + if (WARN_ON_ONCE(thdev->type != INTEL_TH_OUTPUT)) + return -EINVAL; + + hubdrv->trig_switch(hub, &thdev->output); + + return 0; +} +EXPORT_SYMBOL_GPL(intel_th_trace_switch); + +/** + * intel_th_trace_disable() - disable tracing for an output device + * @thdev: output device that requests tracing be disabled + */ +int intel_th_trace_disable(struct intel_th_device *thdev) +{ + struct intel_th_device *hub = to_intel_th_device(thdev->dev.parent); + struct intel_th_driver *hubdrv = to_intel_th_driver(hub->dev.driver); + + WARN_ON_ONCE(hub->type != INTEL_TH_SWITCH); + if (WARN_ON_ONCE(thdev->type != INTEL_TH_OUTPUT)) + return -EINVAL; + + hubdrv->disable(hub, &thdev->output); + pm_runtime_put(&thdev->dev); + + return 0; +} +EXPORT_SYMBOL_GPL(intel_th_trace_disable); + +int intel_th_set_output(struct intel_th_device *thdev, + unsigned int master) +{ + struct intel_th_device *hub = to_intel_th_hub(thdev); + struct intel_th_driver *hubdrv = to_intel_th_driver(hub->dev.driver); + int ret; + + /* In host mode, this is up to the external debugger, do nothing. */ + if (hub->host_mode) + return 0; + + /* + * hub is instantiated together with the source device that + * calls here, so guaranteed to be present. + */ + hubdrv = to_intel_th_driver(hub->dev.driver); + if (!hubdrv || !try_module_get(hubdrv->driver.owner)) + return -EINVAL; + + if (!hubdrv->set_output) { + ret = -ENOTSUPP; + goto out; + } + + ret = hubdrv->set_output(hub, master); + +out: + module_put(hubdrv->driver.owner); + return ret; +} +EXPORT_SYMBOL_GPL(intel_th_set_output); + +static int __init intel_th_init(void) +{ + intel_th_debug_init(); + + return bus_register(&intel_th_bus); +} +subsys_initcall(intel_th_init); + +static void __exit intel_th_exit(void) +{ + intel_th_debug_done(); + + bus_unregister(&intel_th_bus); +} +module_exit(intel_th_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Intel(R) Trace Hub controller driver"); +MODULE_AUTHOR("Alexander Shishkin <alexander.shishkin@linux.intel.com>"); diff --git a/drivers/hwtracing/intel_th/debug.c b/drivers/hwtracing/intel_th/debug.c new file mode 100644 index 000000000..ff7906311 --- /dev/null +++ b/drivers/hwtracing/intel_th/debug.c @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel(R) Trace Hub driver debugging + * + * Copyright (C) 2014-2015 Intel Corporation. + */ + +#include <linux/types.h> +#include <linux/device.h> +#include <linux/debugfs.h> + +#include "intel_th.h" +#include "debug.h" + +struct dentry *intel_th_dbg; + +void intel_th_debug_init(void) +{ + intel_th_dbg = debugfs_create_dir("intel_th", NULL); + if (IS_ERR(intel_th_dbg)) + intel_th_dbg = NULL; +} + +void intel_th_debug_done(void) +{ + debugfs_remove(intel_th_dbg); + intel_th_dbg = NULL; +} diff --git a/drivers/hwtracing/intel_th/debug.h b/drivers/hwtracing/intel_th/debug.h new file mode 100644 index 000000000..78bd7e4bf --- /dev/null +++ b/drivers/hwtracing/intel_th/debug.h @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Intel(R) Trace Hub driver debugging + * + * Copyright (C) 2014-2015 Intel Corporation. + */ + +#ifndef __INTEL_TH_DEBUG_H__ +#define __INTEL_TH_DEBUG_H__ + +#ifdef CONFIG_INTEL_TH_DEBUG +extern struct dentry *intel_th_dbg; + +void intel_th_debug_init(void); +void intel_th_debug_done(void); +#else +static inline void intel_th_debug_init(void) +{ +} + +static inline void intel_th_debug_done(void) +{ +} +#endif + +#endif /* __INTEL_TH_DEBUG_H__ */ diff --git a/drivers/hwtracing/intel_th/gth.c b/drivers/hwtracing/intel_th/gth.c new file mode 100644 index 000000000..b3308934a --- /dev/null +++ b/drivers/hwtracing/intel_th/gth.c @@ -0,0 +1,850 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel(R) Trace Hub Global Trace Hub + * + * Copyright (C) 2014-2015 Intel Corporation. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/types.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/mm.h> +#include <linux/slab.h> +#include <linux/bitmap.h> +#include <linux/pm_runtime.h> + +#include "intel_th.h" +#include "gth.h" + +struct gth_device; + +/** + * struct gth_output - GTH view on an output port + * @gth: backlink to the GTH device + * @output: link to output device's output descriptor + * @index: output port number + * @port_type: one of GTH_* port type values + * @master: bitmap of masters configured for this output + */ +struct gth_output { + struct gth_device *gth; + struct intel_th_output *output; + unsigned int index; + unsigned int port_type; + DECLARE_BITMAP(master, TH_CONFIGURABLE_MASTERS + 1); +}; + +/** + * struct gth_device - GTH device + * @dev: driver core's device + * @base: register window base address + * @output_group: attributes describing output ports + * @master_group: attributes describing master assignments + * @output: output ports + * @master: master/output port assignments + * @gth_lock: serializes accesses to GTH bits + */ +struct gth_device { + struct device *dev; + void __iomem *base; + + struct attribute_group output_group; + struct attribute_group master_group; + struct gth_output output[TH_POSSIBLE_OUTPUTS]; + signed char master[TH_CONFIGURABLE_MASTERS + 1]; + spinlock_t gth_lock; +}; + +static void gth_output_set(struct gth_device *gth, int port, + unsigned int config) +{ + unsigned long reg = port & 4 ? REG_GTH_GTHOPT1 : REG_GTH_GTHOPT0; + u32 val; + int shift = (port & 3) * 8; + + val = ioread32(gth->base + reg); + val &= ~(0xff << shift); + val |= config << shift; + iowrite32(val, gth->base + reg); +} + +static unsigned int gth_output_get(struct gth_device *gth, int port) +{ + unsigned long reg = port & 4 ? REG_GTH_GTHOPT1 : REG_GTH_GTHOPT0; + u32 val; + int shift = (port & 3) * 8; + + val = ioread32(gth->base + reg); + val &= 0xff << shift; + val >>= shift; + + return val; +} + +static void gth_smcfreq_set(struct gth_device *gth, int port, + unsigned int freq) +{ + unsigned long reg = REG_GTH_SMCR0 + ((port / 2) * 4); + int shift = (port & 1) * 16; + u32 val; + + val = ioread32(gth->base + reg); + val &= ~(0xffff << shift); + val |= freq << shift; + iowrite32(val, gth->base + reg); +} + +static unsigned int gth_smcfreq_get(struct gth_device *gth, int port) +{ + unsigned long reg = REG_GTH_SMCR0 + ((port / 2) * 4); + int shift = (port & 1) * 16; + u32 val; + + val = ioread32(gth->base + reg); + val &= 0xffff << shift; + val >>= shift; + + return val; +} + +/* + * "masters" attribute group + */ + +struct master_attribute { + struct device_attribute attr; + struct gth_device *gth; + unsigned int master; +}; + +static void +gth_master_set(struct gth_device *gth, unsigned int master, int port) +{ + unsigned int reg = REG_GTH_SWDEST0 + ((master >> 1) & ~3u); + unsigned int shift = (master & 0x7) * 4; + u32 val; + + if (master >= 256) { + reg = REG_GTH_GSWTDEST; + shift = 0; + } + + val = ioread32(gth->base + reg); + val &= ~(0xf << shift); + if (port >= 0) + val |= (0x8 | port) << shift; + iowrite32(val, gth->base + reg); +} + +static ssize_t master_attr_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct master_attribute *ma = + container_of(attr, struct master_attribute, attr); + struct gth_device *gth = ma->gth; + size_t count; + int port; + + spin_lock(>h->gth_lock); + port = gth->master[ma->master]; + spin_unlock(>h->gth_lock); + + if (port >= 0) + count = snprintf(buf, PAGE_SIZE, "%x\n", port); + else + count = snprintf(buf, PAGE_SIZE, "disabled\n"); + + return count; +} + +static ssize_t master_attr_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct master_attribute *ma = + container_of(attr, struct master_attribute, attr); + struct gth_device *gth = ma->gth; + int old_port, port; + + if (kstrtoint(buf, 10, &port) < 0) + return -EINVAL; + + if (port >= TH_POSSIBLE_OUTPUTS || port < -1) + return -EINVAL; + + spin_lock(>h->gth_lock); + + /* disconnect from the previous output port, if any */ + old_port = gth->master[ma->master]; + if (old_port >= 0) { + gth->master[ma->master] = -1; + clear_bit(ma->master, gth->output[old_port].master); + + /* + * if the port is active, program this setting, + * implies that runtime PM is on + */ + if (gth->output[old_port].output->active) + gth_master_set(gth, ma->master, -1); + } + + /* connect to the new output port, if any */ + if (port >= 0) { + /* check if there's a driver for this port */ + if (!gth->output[port].output) { + count = -ENODEV; + goto unlock; + } + + set_bit(ma->master, gth->output[port].master); + + /* if the port is active, program this setting, see above */ + if (gth->output[port].output->active) + gth_master_set(gth, ma->master, port); + } + + gth->master[ma->master] = port; + +unlock: + spin_unlock(>h->gth_lock); + + return count; +} + +struct output_attribute { + struct device_attribute attr; + struct gth_device *gth; + unsigned int port; + unsigned int parm; +}; + +#define OUTPUT_PARM(_name, _mask, _r, _w, _what) \ + [TH_OUTPUT_PARM(_name)] = { .name = __stringify(_name), \ + .get = gth_ ## _what ## _get, \ + .set = gth_ ## _what ## _set, \ + .mask = (_mask), \ + .readable = (_r), \ + .writable = (_w) } + +static const struct output_parm { + const char *name; + unsigned int (*get)(struct gth_device *gth, int port); + void (*set)(struct gth_device *gth, int port, + unsigned int val); + unsigned int mask; + unsigned int readable : 1, + writable : 1; +} output_parms[] = { + OUTPUT_PARM(port, 0x7, 1, 0, output), + OUTPUT_PARM(null, BIT(3), 1, 1, output), + OUTPUT_PARM(drop, BIT(4), 1, 1, output), + OUTPUT_PARM(reset, BIT(5), 1, 0, output), + OUTPUT_PARM(flush, BIT(7), 0, 1, output), + OUTPUT_PARM(smcfreq, 0xffff, 1, 1, smcfreq), +}; + +static void +gth_output_parm_set(struct gth_device *gth, int port, unsigned int parm, + unsigned int val) +{ + unsigned int config = output_parms[parm].get(gth, port); + unsigned int mask = output_parms[parm].mask; + unsigned int shift = __ffs(mask); + + config &= ~mask; + config |= (val << shift) & mask; + output_parms[parm].set(gth, port, config); +} + +static unsigned int +gth_output_parm_get(struct gth_device *gth, int port, unsigned int parm) +{ + unsigned int config = output_parms[parm].get(gth, port); + unsigned int mask = output_parms[parm].mask; + unsigned int shift = __ffs(mask); + + config &= mask; + config >>= shift; + return config; +} + +/* + * Reset outputs and sources + */ +static int intel_th_gth_reset(struct gth_device *gth) +{ + u32 reg; + int port, i; + + reg = ioread32(gth->base + REG_GTH_SCRPD0); + if (reg & SCRPD_DEBUGGER_IN_USE) + return -EBUSY; + + /* Always save/restore STH and TU registers in S0ix entry/exit */ + reg |= SCRPD_STH_IS_ENABLED | SCRPD_TRIGGER_IS_ENABLED; + iowrite32(reg, gth->base + REG_GTH_SCRPD0); + + /* output ports */ + for (port = 0; port < 8; port++) { + if (gth_output_parm_get(gth, port, TH_OUTPUT_PARM(port)) == + GTH_NONE) + continue; + + gth_output_set(gth, port, 0); + gth_smcfreq_set(gth, port, 16); + } + /* disable overrides */ + iowrite32(0, gth->base + REG_GTH_DESTOVR); + + /* masters swdest_0~31 and gswdest */ + for (i = 0; i < 33; i++) + iowrite32(0, gth->base + REG_GTH_SWDEST0 + i * 4); + + /* sources */ + iowrite32(0, gth->base + REG_GTH_SCR); + iowrite32(0xfc, gth->base + REG_GTH_SCR2); + + /* setup CTS for single trigger */ + iowrite32(CTS_EVENT_ENABLE_IF_ANYTHING, gth->base + REG_CTS_C0S0_EN); + iowrite32(CTS_ACTION_CONTROL_SET_STATE(CTS_STATE_IDLE) | + CTS_ACTION_CONTROL_TRIGGER, gth->base + REG_CTS_C0S0_ACT); + + return 0; +} + +/* + * "outputs" attribute group + */ + +static ssize_t output_attr_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct output_attribute *oa = + container_of(attr, struct output_attribute, attr); + struct gth_device *gth = oa->gth; + size_t count; + + pm_runtime_get_sync(dev); + + spin_lock(>h->gth_lock); + count = snprintf(buf, PAGE_SIZE, "%x\n", + gth_output_parm_get(gth, oa->port, oa->parm)); + spin_unlock(>h->gth_lock); + + pm_runtime_put(dev); + + return count; +} + +static ssize_t output_attr_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct output_attribute *oa = + container_of(attr, struct output_attribute, attr); + struct gth_device *gth = oa->gth; + unsigned int config; + + if (kstrtouint(buf, 16, &config) < 0) + return -EINVAL; + + pm_runtime_get_sync(dev); + + spin_lock(>h->gth_lock); + gth_output_parm_set(gth, oa->port, oa->parm, config); + spin_unlock(>h->gth_lock); + + pm_runtime_put(dev); + + return count; +} + +static int intel_th_master_attributes(struct gth_device *gth) +{ + struct master_attribute *master_attrs; + struct attribute **attrs; + int i, nattrs = TH_CONFIGURABLE_MASTERS + 2; + + attrs = devm_kcalloc(gth->dev, nattrs, sizeof(void *), GFP_KERNEL); + if (!attrs) + return -ENOMEM; + + master_attrs = devm_kcalloc(gth->dev, nattrs, + sizeof(struct master_attribute), + GFP_KERNEL); + if (!master_attrs) + return -ENOMEM; + + for (i = 0; i < TH_CONFIGURABLE_MASTERS + 1; i++) { + char *name; + + name = devm_kasprintf(gth->dev, GFP_KERNEL, "%d%s", i, + i == TH_CONFIGURABLE_MASTERS ? "+" : ""); + if (!name) + return -ENOMEM; + + master_attrs[i].attr.attr.name = name; + master_attrs[i].attr.attr.mode = S_IRUGO | S_IWUSR; + master_attrs[i].attr.show = master_attr_show; + master_attrs[i].attr.store = master_attr_store; + + sysfs_attr_init(&master_attrs[i].attr.attr); + attrs[i] = &master_attrs[i].attr.attr; + + master_attrs[i].gth = gth; + master_attrs[i].master = i; + } + + gth->master_group.name = "masters"; + gth->master_group.attrs = attrs; + + return sysfs_create_group(>h->dev->kobj, >h->master_group); +} + +static int intel_th_output_attributes(struct gth_device *gth) +{ + struct output_attribute *out_attrs; + struct attribute **attrs; + int i, j, nouts = TH_POSSIBLE_OUTPUTS; + int nparms = ARRAY_SIZE(output_parms); + int nattrs = nouts * nparms + 1; + + attrs = devm_kcalloc(gth->dev, nattrs, sizeof(void *), GFP_KERNEL); + if (!attrs) + return -ENOMEM; + + out_attrs = devm_kcalloc(gth->dev, nattrs, + sizeof(struct output_attribute), + GFP_KERNEL); + if (!out_attrs) + return -ENOMEM; + + for (i = 0; i < nouts; i++) { + for (j = 0; j < nparms; j++) { + unsigned int idx = i * nparms + j; + char *name; + + name = devm_kasprintf(gth->dev, GFP_KERNEL, "%d_%s", i, + output_parms[j].name); + if (!name) + return -ENOMEM; + + out_attrs[idx].attr.attr.name = name; + + if (output_parms[j].readable) { + out_attrs[idx].attr.attr.mode |= S_IRUGO; + out_attrs[idx].attr.show = output_attr_show; + } + + if (output_parms[j].writable) { + out_attrs[idx].attr.attr.mode |= S_IWUSR; + out_attrs[idx].attr.store = output_attr_store; + } + + sysfs_attr_init(&out_attrs[idx].attr.attr); + attrs[idx] = &out_attrs[idx].attr.attr; + + out_attrs[idx].gth = gth; + out_attrs[idx].port = i; + out_attrs[idx].parm = j; + } + } + + gth->output_group.name = "outputs"; + gth->output_group.attrs = attrs; + + return sysfs_create_group(>h->dev->kobj, >h->output_group); +} + +/** + * intel_th_gth_stop() - stop tracing to an output device + * @gth: GTH device + * @output: output device's descriptor + * @capture_done: set when no more traces will be captured + * + * This will stop tracing using force storeEn off signal and wait for the + * pipelines to be empty for the corresponding output port. + */ +static void intel_th_gth_stop(struct gth_device *gth, + struct intel_th_output *output, + bool capture_done) +{ + struct intel_th_device *outdev = + container_of(output, struct intel_th_device, output); + struct intel_th_driver *outdrv = + to_intel_th_driver(outdev->dev.driver); + unsigned long count; + u32 reg; + u32 scr2 = 0xfc | (capture_done ? 1 : 0); + + iowrite32(0, gth->base + REG_GTH_SCR); + iowrite32(scr2, gth->base + REG_GTH_SCR2); + + /* wait on pipeline empty for the given port */ + for (reg = 0, count = GTH_PLE_WAITLOOP_DEPTH; + count && !(reg & BIT(output->port)); count--) { + reg = ioread32(gth->base + REG_GTH_STAT); + cpu_relax(); + } + + if (!count) + dev_dbg(gth->dev, "timeout waiting for GTH[%d] PLE\n", + output->port); + + /* wait on output piepline empty */ + if (outdrv->wait_empty) + outdrv->wait_empty(outdev); + + /* clear force capture done for next captures */ + iowrite32(0xfc, gth->base + REG_GTH_SCR2); +} + +/** + * intel_th_gth_start() - start tracing to an output device + * @gth: GTH device + * @output: output device's descriptor + * + * This will start tracing using force storeEn signal. + */ +static void intel_th_gth_start(struct gth_device *gth, + struct intel_th_output *output) +{ + u32 scr = 0xfc0000; + + if (output->multiblock) + scr |= 0xff; + + iowrite32(scr, gth->base + REG_GTH_SCR); + iowrite32(0, gth->base + REG_GTH_SCR2); +} + +/** + * intel_th_gth_disable() - disable tracing to an output device + * @thdev: GTH device + * @output: output device's descriptor + * + * This will deconfigure all masters set to output to this device, + * disable tracing using force storeEn off signal and wait for the + * "pipeline empty" bit for corresponding output port. + */ +static void intel_th_gth_disable(struct intel_th_device *thdev, + struct intel_th_output *output) +{ + struct gth_device *gth = dev_get_drvdata(&thdev->dev); + int master; + u32 reg; + + spin_lock(>h->gth_lock); + output->active = false; + + for_each_set_bit(master, gth->output[output->port].master, + TH_CONFIGURABLE_MASTERS + 1) { + gth_master_set(gth, master, -1); + } + spin_unlock(>h->gth_lock); + + intel_th_gth_stop(gth, output, true); + + reg = ioread32(gth->base + REG_GTH_SCRPD0); + reg &= ~output->scratchpad; + iowrite32(reg, gth->base + REG_GTH_SCRPD0); +} + +static void gth_tscu_resync(struct gth_device *gth) +{ + u32 reg; + + reg = ioread32(gth->base + REG_TSCU_TSUCTRL); + reg &= ~TSUCTRL_CTCRESYNC; + iowrite32(reg, gth->base + REG_TSCU_TSUCTRL); +} + +static void intel_th_gth_prepare(struct intel_th_device *thdev, + struct intel_th_output *output) +{ + struct gth_device *gth = dev_get_drvdata(&thdev->dev); + int count; + + /* + * Wait until the output port is in reset before we start + * programming it. + */ + for (count = GTH_PLE_WAITLOOP_DEPTH; + count && !(gth_output_get(gth, output->port) & BIT(5)); count--) + cpu_relax(); +} + +/** + * intel_th_gth_enable() - enable tracing to an output device + * @thdev: GTH device + * @output: output device's descriptor + * + * This will configure all masters set to output to this device and + * enable tracing using force storeEn signal. + */ +static void intel_th_gth_enable(struct intel_th_device *thdev, + struct intel_th_output *output) +{ + struct gth_device *gth = dev_get_drvdata(&thdev->dev); + struct intel_th *th = to_intel_th(thdev); + int master; + u32 scrpd; + + spin_lock(>h->gth_lock); + for_each_set_bit(master, gth->output[output->port].master, + TH_CONFIGURABLE_MASTERS + 1) { + gth_master_set(gth, master, output->port); + } + + output->active = true; + spin_unlock(>h->gth_lock); + + if (INTEL_TH_CAP(th, tscu_enable)) + gth_tscu_resync(gth); + + scrpd = ioread32(gth->base + REG_GTH_SCRPD0); + scrpd |= output->scratchpad; + iowrite32(scrpd, gth->base + REG_GTH_SCRPD0); + + intel_th_gth_start(gth, output); +} + +/** + * intel_th_gth_switch() - execute a switch sequence + * @thdev: GTH device + * @output: output device's descriptor + * + * This will execute a switch sequence that will trigger a switch window + * when tracing to MSC in multi-block mode. + */ +static void intel_th_gth_switch(struct intel_th_device *thdev, + struct intel_th_output *output) +{ + struct gth_device *gth = dev_get_drvdata(&thdev->dev); + unsigned long count; + u32 reg; + + /* trigger */ + iowrite32(0, gth->base + REG_CTS_CTL); + iowrite32(CTS_CTL_SEQUENCER_ENABLE, gth->base + REG_CTS_CTL); + /* wait on trigger status */ + for (reg = 0, count = CTS_TRIG_WAITLOOP_DEPTH; + count && !(reg & BIT(4)); count--) { + reg = ioread32(gth->base + REG_CTS_STAT); + cpu_relax(); + } + if (!count) + dev_dbg(&thdev->dev, "timeout waiting for CTS Trigger\n"); + + /* De-assert the trigger */ + iowrite32(0, gth->base + REG_CTS_CTL); + + intel_th_gth_stop(gth, output, false); + intel_th_gth_start(gth, output); +} + +/** + * intel_th_gth_assign() - assign output device to a GTH output port + * @thdev: GTH device + * @othdev: output device + * + * This will match a given output device parameters against present + * output ports on the GTH and fill out relevant bits in output device's + * descriptor. + * + * Return: 0 on success, -errno on error. + */ +static int intel_th_gth_assign(struct intel_th_device *thdev, + struct intel_th_device *othdev) +{ + struct gth_device *gth = dev_get_drvdata(&thdev->dev); + int i, id; + + if (thdev->host_mode) + return -EBUSY; + + if (othdev->type != INTEL_TH_OUTPUT) + return -EINVAL; + + for (i = 0, id = 0; i < TH_POSSIBLE_OUTPUTS; i++) { + if (gth->output[i].port_type != othdev->output.type) + continue; + + if (othdev->id == -1 || othdev->id == id) + goto found; + + id++; + } + + return -ENOENT; + +found: + spin_lock(>h->gth_lock); + othdev->output.port = i; + othdev->output.active = false; + gth->output[i].output = &othdev->output; + spin_unlock(>h->gth_lock); + + return 0; +} + +/** + * intel_th_gth_unassign() - deassociate an output device from its output port + * @thdev: GTH device + * @othdev: output device + */ +static void intel_th_gth_unassign(struct intel_th_device *thdev, + struct intel_th_device *othdev) +{ + struct gth_device *gth = dev_get_drvdata(&thdev->dev); + int port = othdev->output.port; + int master; + + if (thdev->host_mode) + return; + + spin_lock(>h->gth_lock); + othdev->output.port = -1; + othdev->output.active = false; + gth->output[port].output = NULL; + for (master = 0; master < TH_CONFIGURABLE_MASTERS + 1; master++) + if (gth->master[master] == port) + gth->master[master] = -1; + spin_unlock(>h->gth_lock); +} + +static int +intel_th_gth_set_output(struct intel_th_device *thdev, unsigned int master) +{ + struct gth_device *gth = dev_get_drvdata(&thdev->dev); + int port = 0; /* FIXME: make default output configurable */ + + /* + * everything above TH_CONFIGURABLE_MASTERS is controlled by the + * same register + */ + if (master > TH_CONFIGURABLE_MASTERS) + master = TH_CONFIGURABLE_MASTERS; + + spin_lock(>h->gth_lock); + if (gth->master[master] == -1) { + set_bit(master, gth->output[port].master); + gth->master[master] = port; + } + spin_unlock(>h->gth_lock); + + return 0; +} + +static int intel_th_gth_probe(struct intel_th_device *thdev) +{ + struct device *dev = &thdev->dev; + struct intel_th *th = dev_get_drvdata(dev->parent); + struct gth_device *gth; + struct resource *res; + void __iomem *base; + int i, ret; + + res = intel_th_device_get_resource(thdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + + base = devm_ioremap(dev, res->start, resource_size(res)); + if (!base) + return -ENOMEM; + + gth = devm_kzalloc(dev, sizeof(*gth), GFP_KERNEL); + if (!gth) + return -ENOMEM; + + gth->dev = dev; + gth->base = base; + spin_lock_init(>h->gth_lock); + + dev_set_drvdata(dev, gth); + + /* + * Host mode can be signalled via SW means or via SCRPD_DEBUGGER_IN_USE + * bit. Either way, don't reset HW in this case, and don't export any + * capture configuration attributes. Also, refuse to assign output + * drivers to ports, see intel_th_gth_assign(). + */ + if (thdev->host_mode) + return 0; + + ret = intel_th_gth_reset(gth); + if (ret) { + if (ret != -EBUSY) + return ret; + + thdev->host_mode = true; + + return 0; + } + + for (i = 0; i < TH_CONFIGURABLE_MASTERS + 1; i++) + gth->master[i] = -1; + + for (i = 0; i < TH_POSSIBLE_OUTPUTS; i++) { + gth->output[i].gth = gth; + gth->output[i].index = i; + gth->output[i].port_type = + gth_output_parm_get(gth, i, TH_OUTPUT_PARM(port)); + if (gth->output[i].port_type == GTH_NONE) + continue; + + ret = intel_th_output_enable(th, gth->output[i].port_type); + /* -ENODEV is ok, we just won't have that device enumerated */ + if (ret && ret != -ENODEV) + return ret; + } + + if (intel_th_output_attributes(gth) || + intel_th_master_attributes(gth)) { + pr_warn("Can't initialize sysfs attributes\n"); + + if (gth->output_group.attrs) + sysfs_remove_group(>h->dev->kobj, >h->output_group); + return -ENOMEM; + } + + return 0; +} + +static void intel_th_gth_remove(struct intel_th_device *thdev) +{ + struct gth_device *gth = dev_get_drvdata(&thdev->dev); + + sysfs_remove_group(>h->dev->kobj, >h->output_group); + sysfs_remove_group(>h->dev->kobj, >h->master_group); +} + +static struct intel_th_driver intel_th_gth_driver = { + .probe = intel_th_gth_probe, + .remove = intel_th_gth_remove, + .assign = intel_th_gth_assign, + .unassign = intel_th_gth_unassign, + .set_output = intel_th_gth_set_output, + .prepare = intel_th_gth_prepare, + .enable = intel_th_gth_enable, + .trig_switch = intel_th_gth_switch, + .disable = intel_th_gth_disable, + .driver = { + .name = "gth", + .owner = THIS_MODULE, + }, +}; + +module_driver(intel_th_gth_driver, + intel_th_driver_register, + intel_th_driver_unregister); + +MODULE_ALIAS("intel_th_switch"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Intel(R) Trace Hub Global Trace Hub driver"); +MODULE_AUTHOR("Alexander Shishkin <alexander.shishkin@linux.intel.com>"); diff --git a/drivers/hwtracing/intel_th/gth.h b/drivers/hwtracing/intel_th/gth.h new file mode 100644 index 000000000..bfcc0fd01 --- /dev/null +++ b/drivers/hwtracing/intel_th/gth.h @@ -0,0 +1,79 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Intel(R) Trace Hub Global Trace Hub (GTH) data structures + * + * Copyright (C) 2014-2015 Intel Corporation. + */ + +#ifndef __INTEL_TH_GTH_H__ +#define __INTEL_TH_GTH_H__ + +/* Map output port parameter bits to symbolic names */ +#define TH_OUTPUT_PARM(name) \ + TH_OUTPUT_ ## name + +enum intel_th_output_parm { + /* output port type */ + TH_OUTPUT_PARM(port), + /* generate NULL packet */ + TH_OUTPUT_PARM(null), + /* packet drop */ + TH_OUTPUT_PARM(drop), + /* port in reset state */ + TH_OUTPUT_PARM(reset), + /* flush out data */ + TH_OUTPUT_PARM(flush), + /* mainenance packet frequency */ + TH_OUTPUT_PARM(smcfreq), +}; + +/* + * Register offsets + */ +enum { + REG_GTH_GTHOPT0 = 0x00, /* Output ports 0..3 config */ + REG_GTH_GTHOPT1 = 0x04, /* Output ports 4..7 config */ + REG_GTH_SWDEST0 = 0x08, /* Switching destination masters 0..7 */ + REG_GTH_GSWTDEST = 0x88, /* Global sw trace destination */ + REG_GTH_SMCR0 = 0x9c, /* STP mainenance for ports 0/1 */ + REG_GTH_SMCR1 = 0xa0, /* STP mainenance for ports 2/3 */ + REG_GTH_SMCR2 = 0xa4, /* STP mainenance for ports 4/5 */ + REG_GTH_SMCR3 = 0xa8, /* STP mainenance for ports 6/7 */ + REG_GTH_SCR = 0xc8, /* Source control (storeEn override) */ + REG_GTH_STAT = 0xd4, /* GTH status */ + REG_GTH_SCR2 = 0xd8, /* Source control (force storeEn off) */ + REG_GTH_DESTOVR = 0xdc, /* Destination override */ + REG_GTH_SCRPD0 = 0xe0, /* ScratchPad[0] */ + REG_GTH_SCRPD1 = 0xe4, /* ScratchPad[1] */ + REG_GTH_SCRPD2 = 0xe8, /* ScratchPad[2] */ + REG_GTH_SCRPD3 = 0xec, /* ScratchPad[3] */ + REG_TSCU_TSUCTRL = 0x2000, /* TSCU control register */ + REG_TSCU_TSCUSTAT = 0x2004, /* TSCU status register */ + + /* Common Capture Sequencer (CTS) registers */ + REG_CTS_C0S0_EN = 0x30c0, /* clause_event_enable_c0s0 */ + REG_CTS_C0S0_ACT = 0x3180, /* clause_action_control_c0s0 */ + REG_CTS_STAT = 0x32a0, /* cts_status */ + REG_CTS_CTL = 0x32a4, /* cts_control */ +}; + +/* waiting for Pipeline Empty bit(s) to assert for GTH */ +#define GTH_PLE_WAITLOOP_DEPTH 10000 + +#define TSUCTRL_CTCRESYNC BIT(0) +#define TSCUSTAT_CTCSYNCING BIT(1) + +/* waiting for Trigger status to assert for CTS */ +#define CTS_TRIG_WAITLOOP_DEPTH 10000 + +#define CTS_EVENT_ENABLE_IF_ANYTHING BIT(31) +#define CTS_ACTION_CONTROL_STATE_OFF 27 +#define CTS_ACTION_CONTROL_SET_STATE(x) \ + (((x) & 0x1f) << CTS_ACTION_CONTROL_STATE_OFF) +#define CTS_ACTION_CONTROL_TRIGGER BIT(4) + +#define CTS_STATE_IDLE 0x10u + +#define CTS_CTL_SEQUENCER_ENABLE BIT(0) + +#endif /* __INTEL_TH_GTH_H__ */ diff --git a/drivers/hwtracing/intel_th/intel_th.h b/drivers/hwtracing/intel_th/intel_th.h new file mode 100644 index 000000000..595615b79 --- /dev/null +++ b/drivers/hwtracing/intel_th/intel_th.h @@ -0,0 +1,384 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Intel(R) Trace Hub data structures + * + * Copyright (C) 2014-2015 Intel Corporation. + */ + +#ifndef __INTEL_TH_H__ +#define __INTEL_TH_H__ + +#include <linux/irqreturn.h> + +/* intel_th_device device types */ +enum { + /* Devices that generate trace data */ + INTEL_TH_SOURCE = 0, + /* Output ports (MSC, PTI) */ + INTEL_TH_OUTPUT, + /* Switch, the Global Trace Hub (GTH) */ + INTEL_TH_SWITCH, +}; + +struct intel_th_device; + +/** + * struct intel_th_output - descriptor INTEL_TH_OUTPUT type devices + * @port: output port number, assigned by the switch + * @type: GTH_{MSU,CTP,PTI} + * @scratchpad: scratchpad bits to flag when this output is enabled + * @multiblock: true for multiblock output configuration + * @active: true when this output is enabled + * @wait_empty: wait for device pipeline to be empty + * + * Output port descriptor, used by switch driver to tell which output + * port this output device corresponds to. Filled in at output device's + * probe time by switch::assign(). Passed from output device driver to + * switch related code to enable/disable its port. + */ +struct intel_th_output { + int port; + unsigned int type; + unsigned int scratchpad; + bool multiblock; + bool active; +}; + +/** + * struct intel_th_drvdata - describes hardware capabilities and quirks + * @tscu_enable: device needs SW to enable time stamping unit + * @multi_is_broken: device has multiblock mode is broken + * @has_mintctl: device has interrupt control (MINTCTL) register + * @host_mode_only: device can only operate in 'host debugger' mode + */ +struct intel_th_drvdata { + unsigned int tscu_enable : 1, + multi_is_broken : 1, + has_mintctl : 1, + host_mode_only : 1; +}; + +#define INTEL_TH_CAP(_th, _cap) ((_th)->drvdata ? (_th)->drvdata->_cap : 0) + +/** + * struct intel_th_device - device on the intel_th bus + * @dev: device + * @drvdata: hardware capabilities/quirks + * @resource: array of resources available to this device + * @num_resources: number of resources in @resource array + * @type: INTEL_TH_{SOURCE,OUTPUT,SWITCH} + * @id: device instance or -1 + * @host_mode: Intel TH is controlled by an external debug host + * @output: output descriptor for INTEL_TH_OUTPUT devices + * @name: device name to match the driver + */ +struct intel_th_device { + struct device dev; + struct intel_th_drvdata *drvdata; + struct resource *resource; + unsigned int num_resources; + unsigned int type; + int id; + + /* INTEL_TH_SWITCH specific */ + bool host_mode; + + /* INTEL_TH_OUTPUT specific */ + struct intel_th_output output; + + char name[]; +}; + +#define to_intel_th_device(_d) \ + container_of((_d), struct intel_th_device, dev) + +/** + * intel_th_device_get_resource() - obtain @num'th resource of type @type + * @thdev: the device to search the resource for + * @type: resource type + * @num: number of the resource + */ +static inline struct resource * +intel_th_device_get_resource(struct intel_th_device *thdev, unsigned int type, + unsigned int num) +{ + int i; + + for (i = 0; i < thdev->num_resources; i++) + if (resource_type(&thdev->resource[i]) == type && !num--) + return &thdev->resource[i]; + + return NULL; +} + +/* + * GTH, output ports configuration + */ +enum { + GTH_NONE = 0, + GTH_MSU, /* memory/usb */ + GTH_CTP, /* Common Trace Port */ + GTH_LPP, /* Low Power Path */ + GTH_PTI, /* MIPI-PTI */ +}; + +/** + * intel_th_output_assigned() - if an output device is assigned to a switch port + * @thdev: the output device + * + * Return: true if the device is INTEL_TH_OUTPUT *and* is assigned a port + */ +static inline bool +intel_th_output_assigned(struct intel_th_device *thdev) +{ + return thdev->type == INTEL_TH_OUTPUT && + (thdev->output.port >= 0 || + thdev->output.type == GTH_NONE); +} + +/** + * struct intel_th_driver - driver for an intel_th_device device + * @driver: generic driver + * @probe: probe method + * @remove: remove method + * @assign: match a given output type device against available outputs + * @unassign: deassociate an output type device from an output port + * @prepare: prepare output port for tracing + * @enable: enable tracing for a given output device + * @disable: disable tracing for a given output device + * @irq: interrupt callback + * @activate: enable tracing on the output's side + * @deactivate: disable tracing on the output's side + * @fops: file operations for device nodes + * @attr_group: attributes provided by the driver + * + * Callbacks @probe and @remove are required for all device types. + * Switch device driver needs to fill in @assign, @enable and @disable + * callbacks. + */ +struct intel_th_driver { + struct device_driver driver; + int (*probe)(struct intel_th_device *thdev); + void (*remove)(struct intel_th_device *thdev); + /* switch (GTH) ops */ + int (*assign)(struct intel_th_device *thdev, + struct intel_th_device *othdev); + void (*unassign)(struct intel_th_device *thdev, + struct intel_th_device *othdev); + void (*prepare)(struct intel_th_device *thdev, + struct intel_th_output *output); + void (*enable)(struct intel_th_device *thdev, + struct intel_th_output *output); + void (*trig_switch)(struct intel_th_device *thdev, + struct intel_th_output *output); + void (*disable)(struct intel_th_device *thdev, + struct intel_th_output *output); + /* output ops */ + irqreturn_t (*irq)(struct intel_th_device *thdev); + void (*wait_empty)(struct intel_th_device *thdev); + int (*activate)(struct intel_th_device *thdev); + void (*deactivate)(struct intel_th_device *thdev); + /* file_operations for those who want a device node */ + const struct file_operations *fops; + /* optional attributes */ + struct attribute_group *attr_group; + + /* source ops */ + int (*set_output)(struct intel_th_device *thdev, + unsigned int master); +}; + +#define to_intel_th_driver(_d) \ + container_of((_d), struct intel_th_driver, driver) + +#define to_intel_th_driver_or_null(_d) \ + ((_d) ? to_intel_th_driver(_d) : NULL) + +/* + * Subdevice tree structure is as follows: + * + struct intel_th device (pci; dev_{get,set}_drvdata() + * + struct intel_th_device INTEL_TH_SWITCH (GTH) + * + struct intel_th_device INTEL_TH_OUTPUT (MSU, PTI) + * + struct intel_th_device INTEL_TH_SOURCE (STH) + * + * In other words, INTEL_TH_OUTPUT devices are children of INTEL_TH_SWITCH; + * INTEL_TH_SWITCH and INTEL_TH_SOURCE are children of the intel_th device. + */ +static inline struct intel_th_device * +to_intel_th_parent(struct intel_th_device *thdev) +{ + struct device *parent = thdev->dev.parent; + + if (!parent) + return NULL; + + return to_intel_th_device(parent); +} + +static inline struct intel_th *to_intel_th(struct intel_th_device *thdev) +{ + if (thdev->type == INTEL_TH_OUTPUT) + thdev = to_intel_th_parent(thdev); + + if (WARN_ON_ONCE(!thdev || thdev->type == INTEL_TH_OUTPUT)) + return NULL; + + return dev_get_drvdata(thdev->dev.parent); +} + +struct intel_th * +intel_th_alloc(struct device *dev, struct intel_th_drvdata *drvdata, + struct resource *devres, unsigned int ndevres); +void intel_th_free(struct intel_th *th); + +int intel_th_driver_register(struct intel_th_driver *thdrv); +void intel_th_driver_unregister(struct intel_th_driver *thdrv); + +int intel_th_trace_enable(struct intel_th_device *thdev); +int intel_th_trace_switch(struct intel_th_device *thdev); +int intel_th_trace_disable(struct intel_th_device *thdev); +int intel_th_set_output(struct intel_th_device *thdev, + unsigned int master); +int intel_th_output_enable(struct intel_th *th, unsigned int otype); + +enum th_mmio_idx { + TH_MMIO_CONFIG = 0, + TH_MMIO_SW = 1, + TH_MMIO_RTIT = 2, + TH_MMIO_END, +}; + +#define TH_POSSIBLE_OUTPUTS 8 +/* Total number of possible subdevices: outputs + GTH + STH */ +#define TH_SUBDEVICE_MAX (TH_POSSIBLE_OUTPUTS + 2) +#define TH_CONFIGURABLE_MASTERS 256 +#define TH_MSC_MAX 2 + +/* Maximum IRQ vectors */ +#define TH_NVEC_MAX 8 + +/** + * struct intel_th - Intel TH controller + * @dev: driver core's device + * @thdev: subdevices + * @hub: "switch" subdevice (GTH) + * @resource: resources of the entire controller + * @num_thdevs: number of devices in the @thdev array + * @num_resources: number of resources in the @resource array + * @irq: irq number + * @num_irqs: number of IRQs is use + * @id: this Intel TH controller's device ID in the system + * @major: device node major for output devices + */ +struct intel_th { + struct device *dev; + + struct intel_th_device *thdev[TH_SUBDEVICE_MAX]; + struct intel_th_device *hub; + struct intel_th_drvdata *drvdata; + + struct resource resource[TH_MMIO_END]; + int (*activate)(struct intel_th *); + void (*deactivate)(struct intel_th *); + unsigned int num_thdevs; + unsigned int num_resources; + int irq; + int num_irqs; + + int id; + int major; +#ifdef CONFIG_MODULES + struct work_struct request_module_work; +#endif /* CONFIG_MODULES */ +#ifdef CONFIG_INTEL_TH_DEBUG + struct dentry *dbg; +#endif +}; + +static inline struct intel_th_device * +to_intel_th_hub(struct intel_th_device *thdev) +{ + if (thdev->type == INTEL_TH_SWITCH) + return thdev; + else if (thdev->type == INTEL_TH_OUTPUT) + return to_intel_th_parent(thdev); + + return to_intel_th(thdev)->hub; +} + +/* + * Register windows + */ +enum { + /* Global Trace Hub (GTH) */ + REG_GTH_OFFSET = 0x0000, + REG_GTH_LENGTH = 0x2000, + + /* Timestamp counter unit (TSCU) */ + REG_TSCU_OFFSET = 0x2000, + REG_TSCU_LENGTH = 0x1000, + + REG_CTS_OFFSET = 0x3000, + REG_CTS_LENGTH = 0x1000, + + /* Software Trace Hub (STH) [0x4000..0x4fff] */ + REG_STH_OFFSET = 0x4000, + REG_STH_LENGTH = 0x2000, + + /* Memory Storage Unit (MSU) [0xa0000..0xa1fff] */ + REG_MSU_OFFSET = 0xa0000, + REG_MSU_LENGTH = 0x02000, + + /* Internal MSU trace buffer [0x80000..0x9ffff] */ + BUF_MSU_OFFSET = 0x80000, + BUF_MSU_LENGTH = 0x20000, + + /* PTI output == same window as GTH */ + REG_PTI_OFFSET = REG_GTH_OFFSET, + REG_PTI_LENGTH = REG_GTH_LENGTH, + + /* DCI Handler (DCIH) == some window as MSU */ + REG_DCIH_OFFSET = REG_MSU_OFFSET, + REG_DCIH_LENGTH = REG_MSU_LENGTH, +}; + +/* + * Scratchpad bits: tell firmware and external debuggers + * what we are up to. + */ +enum { + /* Memory is the primary destination */ + SCRPD_MEM_IS_PRIM_DEST = BIT(0), + /* XHCI DbC is the primary destination */ + SCRPD_DBC_IS_PRIM_DEST = BIT(1), + /* PTI is the primary destination */ + SCRPD_PTI_IS_PRIM_DEST = BIT(2), + /* BSSB is the primary destination */ + SCRPD_BSSB_IS_PRIM_DEST = BIT(3), + /* PTI is the alternate destination */ + SCRPD_PTI_IS_ALT_DEST = BIT(4), + /* BSSB is the alternate destination */ + SCRPD_BSSB_IS_ALT_DEST = BIT(5), + /* DeepSx exit occurred */ + SCRPD_DEEPSX_EXIT = BIT(6), + /* S4 exit occurred */ + SCRPD_S4_EXIT = BIT(7), + /* S5 exit occurred */ + SCRPD_S5_EXIT = BIT(8), + /* MSU controller 0/1 is enabled */ + SCRPD_MSC0_IS_ENABLED = BIT(9), + SCRPD_MSC1_IS_ENABLED = BIT(10), + /* Sx exit occurred */ + SCRPD_SX_EXIT = BIT(11), + /* Trigger Unit is enabled */ + SCRPD_TRIGGER_IS_ENABLED = BIT(12), + SCRPD_ODLA_IS_ENABLED = BIT(13), + SCRPD_SOCHAP_IS_ENABLED = BIT(14), + SCRPD_STH_IS_ENABLED = BIT(15), + SCRPD_DCIH_IS_ENABLED = BIT(16), + SCRPD_VER_IS_ENABLED = BIT(17), + /* External debugger is using Intel TH */ + SCRPD_DEBUGGER_IN_USE = BIT(24), +}; + +#endif diff --git a/drivers/hwtracing/intel_th/msu-sink.c b/drivers/hwtracing/intel_th/msu-sink.c new file mode 100644 index 000000000..891b28ea2 --- /dev/null +++ b/drivers/hwtracing/intel_th/msu-sink.c @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * An example software sink buffer for Intel TH MSU. + * + * Copyright (C) 2019 Intel Corporation. + */ + +#include <linux/intel_th.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/device.h> +#include <linux/dma-mapping.h> + +#define MAX_SGTS 16 + +struct msu_sink_private { + struct device *dev; + struct sg_table **sgts; + unsigned int nr_sgts; +}; + +static void *msu_sink_assign(struct device *dev, int *mode) +{ + struct msu_sink_private *priv; + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + return NULL; + + priv->sgts = kcalloc(MAX_SGTS, sizeof(void *), GFP_KERNEL); + if (!priv->sgts) { + kfree(priv); + return NULL; + } + + priv->dev = dev; + *mode = MSC_MODE_MULTI; + + return priv; +} + +static void msu_sink_unassign(void *data) +{ + struct msu_sink_private *priv = data; + + kfree(priv->sgts); + kfree(priv); +} + +/* See also: msc.c: __msc_buffer_win_alloc() */ +static int msu_sink_alloc_window(void *data, struct sg_table **sgt, size_t size) +{ + struct msu_sink_private *priv = data; + unsigned int nents; + struct scatterlist *sg_ptr; + void *block; + int ret, i; + + if (priv->nr_sgts == MAX_SGTS) + return -ENOMEM; + + nents = DIV_ROUND_UP(size, PAGE_SIZE); + + ret = sg_alloc_table(*sgt, nents, GFP_KERNEL); + if (ret) + return -ENOMEM; + + priv->sgts[priv->nr_sgts++] = *sgt; + + for_each_sg((*sgt)->sgl, sg_ptr, nents, i) { + block = dma_alloc_coherent(priv->dev->parent->parent, + PAGE_SIZE, &sg_dma_address(sg_ptr), + GFP_KERNEL); + if (!block) + return -ENOMEM; + + sg_set_buf(sg_ptr, block, PAGE_SIZE); + } + + return nents; +} + +/* See also: msc.c: __msc_buffer_win_free() */ +static void msu_sink_free_window(void *data, struct sg_table *sgt) +{ + struct msu_sink_private *priv = data; + struct scatterlist *sg_ptr; + int i; + + for_each_sg(sgt->sgl, sg_ptr, sgt->nents, i) { + dma_free_coherent(priv->dev->parent->parent, PAGE_SIZE, + sg_virt(sg_ptr), sg_dma_address(sg_ptr)); + } + + sg_free_table(sgt); + priv->nr_sgts--; +} + +static int msu_sink_ready(void *data, struct sg_table *sgt, size_t bytes) +{ + struct msu_sink_private *priv = data; + + intel_th_msc_window_unlock(priv->dev, sgt); + + return 0; +} + +static const struct msu_buffer sink_mbuf = { + .name = "sink", + .assign = msu_sink_assign, + .unassign = msu_sink_unassign, + .alloc_window = msu_sink_alloc_window, + .free_window = msu_sink_free_window, + .ready = msu_sink_ready, +}; + +module_intel_th_msu_buffer(sink_mbuf); + +MODULE_LICENSE("GPL v2"); diff --git a/drivers/hwtracing/intel_th/msu.c b/drivers/hwtracing/intel_th/msu.c new file mode 100644 index 000000000..24f56a7c0 --- /dev/null +++ b/drivers/hwtracing/intel_th/msu.c @@ -0,0 +1,2192 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel(R) Trace Hub Memory Storage Unit + * + * Copyright (C) 2014-2015 Intel Corporation. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/types.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/uaccess.h> +#include <linux/sizes.h> +#include <linux/printk.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/fs.h> +#include <linux/io.h> +#include <linux/workqueue.h> +#include <linux/dma-mapping.h> + +#ifdef CONFIG_X86 +#include <asm/set_memory.h> +#endif + +#include <linux/intel_th.h> +#include "intel_th.h" +#include "msu.h" + +#define msc_dev(x) (&(x)->thdev->dev) + +/* + * Lockout state transitions: + * READY -> INUSE -+-> LOCKED -+-> READY -> etc. + * \-----------/ + * WIN_READY: window can be used by HW + * WIN_INUSE: window is in use + * WIN_LOCKED: window is filled up and is being processed by the buffer + * handling code + * + * All state transitions happen automatically, except for the LOCKED->READY, + * which needs to be signalled by the buffer code by calling + * intel_th_msc_window_unlock(). + * + * When the interrupt handler has to switch to the next window, it checks + * whether it's READY, and if it is, it performs the switch and tracing + * continues. If it's LOCKED, it stops the trace. + */ +enum lockout_state { + WIN_READY = 0, + WIN_INUSE, + WIN_LOCKED +}; + +/** + * struct msc_window - multiblock mode window descriptor + * @entry: window list linkage (msc::win_list) + * @pgoff: page offset into the buffer that this window starts at + * @lockout: lockout state, see comment below + * @lo_lock: lockout state serialization + * @nr_blocks: number of blocks (pages) in this window + * @nr_segs: number of segments in this window (<= @nr_blocks) + * @_sgt: array of block descriptors + * @sgt: array of block descriptors + */ +struct msc_window { + struct list_head entry; + unsigned long pgoff; + enum lockout_state lockout; + spinlock_t lo_lock; + unsigned int nr_blocks; + unsigned int nr_segs; + struct msc *msc; + struct sg_table _sgt; + struct sg_table *sgt; +}; + +/** + * struct msc_iter - iterator for msc buffer + * @entry: msc::iter_list linkage + * @msc: pointer to the MSC device + * @start_win: oldest window + * @win: current window + * @offset: current logical offset into the buffer + * @start_block: oldest block in the window + * @block: block number in the window + * @block_off: offset into current block + * @wrap_count: block wrapping handling + * @eof: end of buffer reached + */ +struct msc_iter { + struct list_head entry; + struct msc *msc; + struct msc_window *start_win; + struct msc_window *win; + unsigned long offset; + struct scatterlist *start_block; + struct scatterlist *block; + unsigned int block_off; + unsigned int wrap_count; + unsigned int eof; +}; + +/** + * struct msc - MSC device representation + * @reg_base: register window base address + * @thdev: intel_th_device pointer + * @mbuf: MSU buffer, if assigned + * @mbuf_priv MSU buffer's private data, if @mbuf + * @win_list: list of windows in multiblock mode + * @single_sgt: single mode buffer + * @cur_win: current window + * @nr_pages: total number of pages allocated for this buffer + * @single_sz: amount of data in single mode + * @single_wrap: single mode wrap occurred + * @base: buffer's base pointer + * @base_addr: buffer's base address + * @user_count: number of users of the buffer + * @mmap_count: number of mappings + * @buf_mutex: mutex to serialize access to buffer-related bits + + * @enabled: MSC is enabled + * @wrap: wrapping is enabled + * @mode: MSC operating mode + * @burst_len: write burst length + * @index: number of this MSC in the MSU + */ +struct msc { + void __iomem *reg_base; + void __iomem *msu_base; + struct intel_th_device *thdev; + + const struct msu_buffer *mbuf; + void *mbuf_priv; + + struct work_struct work; + struct list_head win_list; + struct sg_table single_sgt; + struct msc_window *cur_win; + struct msc_window *switch_on_unlock; + unsigned long nr_pages; + unsigned long single_sz; + unsigned int single_wrap : 1; + void *base; + dma_addr_t base_addr; + u32 orig_addr; + u32 orig_sz; + + /* <0: no buffer, 0: no users, >0: active users */ + atomic_t user_count; + + atomic_t mmap_count; + struct mutex buf_mutex; + + struct list_head iter_list; + + bool stop_on_full; + + /* config */ + unsigned int enabled : 1, + wrap : 1, + do_irq : 1, + multi_is_broken : 1; + unsigned int mode; + unsigned int burst_len; + unsigned int index; +}; + +static LIST_HEAD(msu_buffer_list); +static DEFINE_MUTEX(msu_buffer_mutex); + +/** + * struct msu_buffer_entry - internal MSU buffer bookkeeping + * @entry: link to msu_buffer_list + * @mbuf: MSU buffer object + * @owner: module that provides this MSU buffer + */ +struct msu_buffer_entry { + struct list_head entry; + const struct msu_buffer *mbuf; + struct module *owner; +}; + +static struct msu_buffer_entry *__msu_buffer_entry_find(const char *name) +{ + struct msu_buffer_entry *mbe; + + lockdep_assert_held(&msu_buffer_mutex); + + list_for_each_entry(mbe, &msu_buffer_list, entry) { + if (!strcmp(mbe->mbuf->name, name)) + return mbe; + } + + return NULL; +} + +static const struct msu_buffer * +msu_buffer_get(const char *name) +{ + struct msu_buffer_entry *mbe; + + mutex_lock(&msu_buffer_mutex); + mbe = __msu_buffer_entry_find(name); + if (mbe && !try_module_get(mbe->owner)) + mbe = NULL; + mutex_unlock(&msu_buffer_mutex); + + return mbe ? mbe->mbuf : NULL; +} + +static void msu_buffer_put(const struct msu_buffer *mbuf) +{ + struct msu_buffer_entry *mbe; + + mutex_lock(&msu_buffer_mutex); + mbe = __msu_buffer_entry_find(mbuf->name); + if (mbe) + module_put(mbe->owner); + mutex_unlock(&msu_buffer_mutex); +} + +int intel_th_msu_buffer_register(const struct msu_buffer *mbuf, + struct module *owner) +{ + struct msu_buffer_entry *mbe; + int ret = 0; + + mbe = kzalloc(sizeof(*mbe), GFP_KERNEL); + if (!mbe) + return -ENOMEM; + + mutex_lock(&msu_buffer_mutex); + if (__msu_buffer_entry_find(mbuf->name)) { + ret = -EEXIST; + kfree(mbe); + goto unlock; + } + + mbe->mbuf = mbuf; + mbe->owner = owner; + list_add_tail(&mbe->entry, &msu_buffer_list); +unlock: + mutex_unlock(&msu_buffer_mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(intel_th_msu_buffer_register); + +void intel_th_msu_buffer_unregister(const struct msu_buffer *mbuf) +{ + struct msu_buffer_entry *mbe; + + mutex_lock(&msu_buffer_mutex); + mbe = __msu_buffer_entry_find(mbuf->name); + if (mbe) { + list_del(&mbe->entry); + kfree(mbe); + } + mutex_unlock(&msu_buffer_mutex); +} +EXPORT_SYMBOL_GPL(intel_th_msu_buffer_unregister); + +static inline bool msc_block_is_empty(struct msc_block_desc *bdesc) +{ + /* header hasn't been written */ + if (!bdesc->valid_dw) + return true; + + /* valid_dw includes the header */ + if (!msc_data_sz(bdesc)) + return true; + + return false; +} + +static inline struct scatterlist *msc_win_base_sg(struct msc_window *win) +{ + return win->sgt->sgl; +} + +static inline struct msc_block_desc *msc_win_base(struct msc_window *win) +{ + return sg_virt(msc_win_base_sg(win)); +} + +static inline dma_addr_t msc_win_base_dma(struct msc_window *win) +{ + return sg_dma_address(msc_win_base_sg(win)); +} + +static inline unsigned long +msc_win_base_pfn(struct msc_window *win) +{ + return PFN_DOWN(msc_win_base_dma(win)); +} + +/** + * msc_is_last_win() - check if a window is the last one for a given MSC + * @win: window + * Return: true if @win is the last window in MSC's multiblock buffer + */ +static inline bool msc_is_last_win(struct msc_window *win) +{ + return win->entry.next == &win->msc->win_list; +} + +/** + * msc_next_window() - return next window in the multiblock buffer + * @win: current window + * + * Return: window following the current one + */ +static struct msc_window *msc_next_window(struct msc_window *win) +{ + if (msc_is_last_win(win)) + return list_first_entry(&win->msc->win_list, struct msc_window, + entry); + + return list_next_entry(win, entry); +} + +static size_t msc_win_total_sz(struct msc_window *win) +{ + struct scatterlist *sg; + unsigned int blk; + size_t size = 0; + + for_each_sg(win->sgt->sgl, sg, win->nr_segs, blk) { + struct msc_block_desc *bdesc = sg_virt(sg); + + if (msc_block_wrapped(bdesc)) + return (size_t)win->nr_blocks << PAGE_SHIFT; + + size += msc_total_sz(bdesc); + if (msc_block_last_written(bdesc)) + break; + } + + return size; +} + +/** + * msc_find_window() - find a window matching a given sg_table + * @msc: MSC device + * @sgt: SG table of the window + * @nonempty: skip over empty windows + * + * Return: MSC window structure pointer or NULL if the window + * could not be found. + */ +static struct msc_window * +msc_find_window(struct msc *msc, struct sg_table *sgt, bool nonempty) +{ + struct msc_window *win; + unsigned int found = 0; + + if (list_empty(&msc->win_list)) + return NULL; + + /* + * we might need a radix tree for this, depending on how + * many windows a typical user would allocate; ideally it's + * something like 2, in which case we're good + */ + list_for_each_entry(win, &msc->win_list, entry) { + if (win->sgt == sgt) + found++; + + /* skip the empty ones */ + if (nonempty && msc_block_is_empty(msc_win_base(win))) + continue; + + if (found) + return win; + } + + return NULL; +} + +/** + * msc_oldest_window() - locate the window with oldest data + * @msc: MSC device + * + * This should only be used in multiblock mode. Caller should hold the + * msc::user_count reference. + * + * Return: the oldest window with valid data + */ +static struct msc_window *msc_oldest_window(struct msc *msc) +{ + struct msc_window *win; + + if (list_empty(&msc->win_list)) + return NULL; + + win = msc_find_window(msc, msc_next_window(msc->cur_win)->sgt, true); + if (win) + return win; + + return list_first_entry(&msc->win_list, struct msc_window, entry); +} + +/** + * msc_win_oldest_sg() - locate the oldest block in a given window + * @win: window to look at + * + * Return: index of the block with the oldest data + */ +static struct scatterlist *msc_win_oldest_sg(struct msc_window *win) +{ + unsigned int blk; + struct scatterlist *sg; + struct msc_block_desc *bdesc = msc_win_base(win); + + /* without wrapping, first block is the oldest */ + if (!msc_block_wrapped(bdesc)) + return msc_win_base_sg(win); + + /* + * with wrapping, last written block contains both the newest and the + * oldest data for this window. + */ + for_each_sg(win->sgt->sgl, sg, win->nr_segs, blk) { + struct msc_block_desc *bdesc = sg_virt(sg); + + if (msc_block_last_written(bdesc)) + return sg; + } + + return msc_win_base_sg(win); +} + +static struct msc_block_desc *msc_iter_bdesc(struct msc_iter *iter) +{ + return sg_virt(iter->block); +} + +static struct msc_iter *msc_iter_install(struct msc *msc) +{ + struct msc_iter *iter; + + iter = kzalloc(sizeof(*iter), GFP_KERNEL); + if (!iter) + return ERR_PTR(-ENOMEM); + + mutex_lock(&msc->buf_mutex); + + /* + * Reading and tracing are mutually exclusive; if msc is + * enabled, open() will fail; otherwise existing readers + * will prevent enabling the msc and the rest of fops don't + * need to worry about it. + */ + if (msc->enabled) { + kfree(iter); + iter = ERR_PTR(-EBUSY); + goto unlock; + } + + iter->msc = msc; + + list_add_tail(&iter->entry, &msc->iter_list); +unlock: + mutex_unlock(&msc->buf_mutex); + + return iter; +} + +static void msc_iter_remove(struct msc_iter *iter, struct msc *msc) +{ + mutex_lock(&msc->buf_mutex); + list_del(&iter->entry); + mutex_unlock(&msc->buf_mutex); + + kfree(iter); +} + +static void msc_iter_block_start(struct msc_iter *iter) +{ + if (iter->start_block) + return; + + iter->start_block = msc_win_oldest_sg(iter->win); + iter->block = iter->start_block; + iter->wrap_count = 0; + + /* + * start with the block with oldest data; if data has wrapped + * in this window, it should be in this block + */ + if (msc_block_wrapped(msc_iter_bdesc(iter))) + iter->wrap_count = 2; + +} + +static int msc_iter_win_start(struct msc_iter *iter, struct msc *msc) +{ + /* already started, nothing to do */ + if (iter->start_win) + return 0; + + iter->start_win = msc_oldest_window(msc); + if (!iter->start_win) + return -EINVAL; + + iter->win = iter->start_win; + iter->start_block = NULL; + + msc_iter_block_start(iter); + + return 0; +} + +static int msc_iter_win_advance(struct msc_iter *iter) +{ + iter->win = msc_next_window(iter->win); + iter->start_block = NULL; + + if (iter->win == iter->start_win) { + iter->eof++; + return 1; + } + + msc_iter_block_start(iter); + + return 0; +} + +static int msc_iter_block_advance(struct msc_iter *iter) +{ + iter->block_off = 0; + + /* wrapping */ + if (iter->wrap_count && iter->block == iter->start_block) { + iter->wrap_count--; + if (!iter->wrap_count) + /* copied newest data from the wrapped block */ + return msc_iter_win_advance(iter); + } + + /* no wrapping, check for last written block */ + if (!iter->wrap_count && msc_block_last_written(msc_iter_bdesc(iter))) + /* copied newest data for the window */ + return msc_iter_win_advance(iter); + + /* block advance */ + if (sg_is_last(iter->block)) + iter->block = msc_win_base_sg(iter->win); + else + iter->block = sg_next(iter->block); + + /* no wrapping, sanity check in case there is no last written block */ + if (!iter->wrap_count && iter->block == iter->start_block) + return msc_iter_win_advance(iter); + + return 0; +} + +/** + * msc_buffer_iterate() - go through multiblock buffer's data + * @iter: iterator structure + * @size: amount of data to scan + * @data: callback's private data + * @fn: iterator callback + * + * This will start at the window which will be written to next (containing + * the oldest data) and work its way to the current window, calling @fn + * for each chunk of data as it goes. + * + * Caller should have msc::user_count reference to make sure the buffer + * doesn't disappear from under us. + * + * Return: amount of data actually scanned. + */ +static ssize_t +msc_buffer_iterate(struct msc_iter *iter, size_t size, void *data, + unsigned long (*fn)(void *, void *, size_t)) +{ + struct msc *msc = iter->msc; + size_t len = size; + unsigned int advance; + + if (iter->eof) + return 0; + + /* start with the oldest window */ + if (msc_iter_win_start(iter, msc)) + return 0; + + do { + unsigned long data_bytes = msc_data_sz(msc_iter_bdesc(iter)); + void *src = (void *)msc_iter_bdesc(iter) + MSC_BDESC; + size_t tocopy = data_bytes, copied = 0; + size_t remaining = 0; + + advance = 1; + + /* + * If block wrapping happened, we need to visit the last block + * twice, because it contains both the oldest and the newest + * data in this window. + * + * First time (wrap_count==2), in the very beginning, to collect + * the oldest data, which is in the range + * (data_bytes..DATA_IN_PAGE). + * + * Second time (wrap_count==1), it's just like any other block, + * containing data in the range of [MSC_BDESC..data_bytes]. + */ + if (iter->block == iter->start_block && iter->wrap_count == 2) { + tocopy = DATA_IN_PAGE - data_bytes; + src += data_bytes; + } + + if (!tocopy) + goto next_block; + + tocopy -= iter->block_off; + src += iter->block_off; + + if (len < tocopy) { + tocopy = len; + advance = 0; + } + + remaining = fn(data, src, tocopy); + + if (remaining) + advance = 0; + + copied = tocopy - remaining; + len -= copied; + iter->block_off += copied; + iter->offset += copied; + + if (!advance) + break; + +next_block: + if (msc_iter_block_advance(iter)) + break; + + } while (len); + + return size - len; +} + +/** + * msc_buffer_clear_hw_header() - clear hw header for multiblock + * @msc: MSC device + */ +static void msc_buffer_clear_hw_header(struct msc *msc) +{ + struct msc_window *win; + struct scatterlist *sg; + + list_for_each_entry(win, &msc->win_list, entry) { + unsigned int blk; + size_t hw_sz = sizeof(struct msc_block_desc) - + offsetof(struct msc_block_desc, hw_tag); + + for_each_sg(win->sgt->sgl, sg, win->nr_segs, blk) { + struct msc_block_desc *bdesc = sg_virt(sg); + + memset(&bdesc->hw_tag, 0, hw_sz); + } + } +} + +static int intel_th_msu_init(struct msc *msc) +{ + u32 mintctl, msusts; + + if (!msc->do_irq) + return 0; + + if (!msc->mbuf) + return 0; + + mintctl = ioread32(msc->msu_base + REG_MSU_MINTCTL); + mintctl |= msc->index ? M1BLIE : M0BLIE; + iowrite32(mintctl, msc->msu_base + REG_MSU_MINTCTL); + if (mintctl != ioread32(msc->msu_base + REG_MSU_MINTCTL)) { + dev_info(msc_dev(msc), "MINTCTL ignores writes: no usable interrupts\n"); + msc->do_irq = 0; + return 0; + } + + msusts = ioread32(msc->msu_base + REG_MSU_MSUSTS); + iowrite32(msusts, msc->msu_base + REG_MSU_MSUSTS); + + return 0; +} + +static void intel_th_msu_deinit(struct msc *msc) +{ + u32 mintctl; + + if (!msc->do_irq) + return; + + mintctl = ioread32(msc->msu_base + REG_MSU_MINTCTL); + mintctl &= msc->index ? ~M1BLIE : ~M0BLIE; + iowrite32(mintctl, msc->msu_base + REG_MSU_MINTCTL); +} + +static int msc_win_set_lockout(struct msc_window *win, + enum lockout_state expect, + enum lockout_state new) +{ + enum lockout_state old; + unsigned long flags; + int ret = 0; + + if (!win->msc->mbuf) + return 0; + + spin_lock_irqsave(&win->lo_lock, flags); + old = win->lockout; + + if (old != expect) { + ret = -EINVAL; + goto unlock; + } + + win->lockout = new; + + if (old == expect && new == WIN_LOCKED) + atomic_inc(&win->msc->user_count); + else if (old == expect && old == WIN_LOCKED) + atomic_dec(&win->msc->user_count); + +unlock: + spin_unlock_irqrestore(&win->lo_lock, flags); + + if (ret) { + if (expect == WIN_READY && old == WIN_LOCKED) + return -EBUSY; + + /* from intel_th_msc_window_unlock(), don't warn if not locked */ + if (expect == WIN_LOCKED && old == new) + return 0; + + dev_warn_ratelimited(msc_dev(win->msc), + "expected lockout state %d, got %d\n", + expect, old); + } + + return ret; +} +/** + * msc_configure() - set up MSC hardware + * @msc: the MSC device to configure + * + * Program storage mode, wrapping, burst length and trace buffer address + * into a given MSC. Then, enable tracing and set msc::enabled. + * The latter is serialized on msc::buf_mutex, so make sure to hold it. + */ +static int msc_configure(struct msc *msc) +{ + u32 reg; + + lockdep_assert_held(&msc->buf_mutex); + + if (msc->mode > MSC_MODE_MULTI) + return -EINVAL; + + if (msc->mode == MSC_MODE_MULTI) { + if (msc_win_set_lockout(msc->cur_win, WIN_READY, WIN_INUSE)) + return -EBUSY; + + msc_buffer_clear_hw_header(msc); + } + + msc->orig_addr = ioread32(msc->reg_base + REG_MSU_MSC0BAR); + msc->orig_sz = ioread32(msc->reg_base + REG_MSU_MSC0SIZE); + + reg = msc->base_addr >> PAGE_SHIFT; + iowrite32(reg, msc->reg_base + REG_MSU_MSC0BAR); + + if (msc->mode == MSC_MODE_SINGLE) { + reg = msc->nr_pages; + iowrite32(reg, msc->reg_base + REG_MSU_MSC0SIZE); + } + + reg = ioread32(msc->reg_base + REG_MSU_MSC0CTL); + reg &= ~(MSC_MODE | MSC_WRAPEN | MSC_EN | MSC_RD_HDR_OVRD); + + reg |= MSC_EN; + reg |= msc->mode << __ffs(MSC_MODE); + reg |= msc->burst_len << __ffs(MSC_LEN); + + if (msc->wrap) + reg |= MSC_WRAPEN; + + iowrite32(reg, msc->reg_base + REG_MSU_MSC0CTL); + + intel_th_msu_init(msc); + + msc->thdev->output.multiblock = msc->mode == MSC_MODE_MULTI; + intel_th_trace_enable(msc->thdev); + msc->enabled = 1; + + if (msc->mbuf && msc->mbuf->activate) + msc->mbuf->activate(msc->mbuf_priv); + + return 0; +} + +/** + * msc_disable() - disable MSC hardware + * @msc: MSC device to disable + * + * If @msc is enabled, disable tracing on the switch and then disable MSC + * storage. Caller must hold msc::buf_mutex. + */ +static void msc_disable(struct msc *msc) +{ + struct msc_window *win = msc->cur_win; + u32 reg; + + lockdep_assert_held(&msc->buf_mutex); + + if (msc->mode == MSC_MODE_MULTI) + msc_win_set_lockout(win, WIN_INUSE, WIN_LOCKED); + + if (msc->mbuf && msc->mbuf->deactivate) + msc->mbuf->deactivate(msc->mbuf_priv); + intel_th_msu_deinit(msc); + intel_th_trace_disable(msc->thdev); + + if (msc->mode == MSC_MODE_SINGLE) { + reg = ioread32(msc->reg_base + REG_MSU_MSC0STS); + msc->single_wrap = !!(reg & MSCSTS_WRAPSTAT); + + reg = ioread32(msc->reg_base + REG_MSU_MSC0MWP); + msc->single_sz = reg & ((msc->nr_pages << PAGE_SHIFT) - 1); + dev_dbg(msc_dev(msc), "MSCnMWP: %08x/%08lx, wrap: %d\n", + reg, msc->single_sz, msc->single_wrap); + } + + reg = ioread32(msc->reg_base + REG_MSU_MSC0CTL); + reg &= ~MSC_EN; + iowrite32(reg, msc->reg_base + REG_MSU_MSC0CTL); + + if (msc->mbuf && msc->mbuf->ready) + msc->mbuf->ready(msc->mbuf_priv, win->sgt, + msc_win_total_sz(win)); + + msc->enabled = 0; + + iowrite32(msc->orig_addr, msc->reg_base + REG_MSU_MSC0BAR); + iowrite32(msc->orig_sz, msc->reg_base + REG_MSU_MSC0SIZE); + + dev_dbg(msc_dev(msc), "MSCnNWSA: %08x\n", + ioread32(msc->reg_base + REG_MSU_MSC0NWSA)); + + reg = ioread32(msc->reg_base + REG_MSU_MSC0STS); + dev_dbg(msc_dev(msc), "MSCnSTS: %08x\n", reg); + + reg = ioread32(msc->reg_base + REG_MSU_MSUSTS); + reg &= msc->index ? MSUSTS_MSC1BLAST : MSUSTS_MSC0BLAST; + iowrite32(reg, msc->reg_base + REG_MSU_MSUSTS); +} + +static int intel_th_msc_activate(struct intel_th_device *thdev) +{ + struct msc *msc = dev_get_drvdata(&thdev->dev); + int ret = -EBUSY; + + if (!atomic_inc_unless_negative(&msc->user_count)) + return -ENODEV; + + mutex_lock(&msc->buf_mutex); + + /* if there are readers, refuse */ + if (list_empty(&msc->iter_list)) + ret = msc_configure(msc); + + mutex_unlock(&msc->buf_mutex); + + if (ret) + atomic_dec(&msc->user_count); + + return ret; +} + +static void intel_th_msc_deactivate(struct intel_th_device *thdev) +{ + struct msc *msc = dev_get_drvdata(&thdev->dev); + + mutex_lock(&msc->buf_mutex); + if (msc->enabled) { + msc_disable(msc); + atomic_dec(&msc->user_count); + } + mutex_unlock(&msc->buf_mutex); +} + +/** + * msc_buffer_contig_alloc() - allocate a contiguous buffer for SINGLE mode + * @msc: MSC device + * @size: allocation size in bytes + * + * This modifies msc::base, which requires msc::buf_mutex to serialize, so the + * caller is expected to hold it. + * + * Return: 0 on success, -errno otherwise. + */ +static int msc_buffer_contig_alloc(struct msc *msc, unsigned long size) +{ + unsigned long nr_pages = size >> PAGE_SHIFT; + unsigned int order = get_order(size); + struct page *page; + int ret; + + if (!size) + return 0; + + ret = sg_alloc_table(&msc->single_sgt, 1, GFP_KERNEL); + if (ret) + goto err_out; + + ret = -ENOMEM; + page = alloc_pages(GFP_KERNEL | __GFP_ZERO | GFP_DMA32, order); + if (!page) + goto err_free_sgt; + + split_page(page, order); + sg_set_buf(msc->single_sgt.sgl, page_address(page), size); + + ret = dma_map_sg(msc_dev(msc)->parent->parent, msc->single_sgt.sgl, 1, + DMA_FROM_DEVICE); + if (ret < 0) + goto err_free_pages; + + msc->nr_pages = nr_pages; + msc->base = page_address(page); + msc->base_addr = sg_dma_address(msc->single_sgt.sgl); + + return 0; + +err_free_pages: + __free_pages(page, order); + +err_free_sgt: + sg_free_table(&msc->single_sgt); + +err_out: + return ret; +} + +/** + * msc_buffer_contig_free() - free a contiguous buffer + * @msc: MSC configured in SINGLE mode + */ +static void msc_buffer_contig_free(struct msc *msc) +{ + unsigned long off; + + dma_unmap_sg(msc_dev(msc)->parent->parent, msc->single_sgt.sgl, + 1, DMA_FROM_DEVICE); + sg_free_table(&msc->single_sgt); + + for (off = 0; off < msc->nr_pages << PAGE_SHIFT; off += PAGE_SIZE) { + struct page *page = virt_to_page(msc->base + off); + + page->mapping = NULL; + __free_page(page); + } + + msc->nr_pages = 0; +} + +/** + * msc_buffer_contig_get_page() - find a page at a given offset + * @msc: MSC configured in SINGLE mode + * @pgoff: page offset + * + * Return: page, if @pgoff is within the range, NULL otherwise. + */ +static struct page *msc_buffer_contig_get_page(struct msc *msc, + unsigned long pgoff) +{ + if (pgoff >= msc->nr_pages) + return NULL; + + return virt_to_page(msc->base + (pgoff << PAGE_SHIFT)); +} + +static int __msc_buffer_win_alloc(struct msc_window *win, + unsigned int nr_segs) +{ + struct scatterlist *sg_ptr; + void *block; + int i, ret; + + ret = sg_alloc_table(win->sgt, nr_segs, GFP_KERNEL); + if (ret) + return -ENOMEM; + + for_each_sg(win->sgt->sgl, sg_ptr, nr_segs, i) { + block = dma_alloc_coherent(msc_dev(win->msc)->parent->parent, + PAGE_SIZE, &sg_dma_address(sg_ptr), + GFP_KERNEL); + if (!block) + goto err_nomem; + + sg_set_buf(sg_ptr, block, PAGE_SIZE); + } + + return nr_segs; + +err_nomem: + for_each_sg(win->sgt->sgl, sg_ptr, i, ret) + dma_free_coherent(msc_dev(win->msc)->parent->parent, PAGE_SIZE, + sg_virt(sg_ptr), sg_dma_address(sg_ptr)); + + sg_free_table(win->sgt); + + return -ENOMEM; +} + +#ifdef CONFIG_X86 +static void msc_buffer_set_uc(struct msc_window *win, unsigned int nr_segs) +{ + struct scatterlist *sg_ptr; + int i; + + for_each_sg(win->sgt->sgl, sg_ptr, nr_segs, i) { + /* Set the page as uncached */ + set_memory_uc((unsigned long)sg_virt(sg_ptr), + PFN_DOWN(sg_ptr->length)); + } +} + +static void msc_buffer_set_wb(struct msc_window *win) +{ + struct scatterlist *sg_ptr; + int i; + + for_each_sg(win->sgt->sgl, sg_ptr, win->nr_segs, i) { + /* Reset the page to write-back */ + set_memory_wb((unsigned long)sg_virt(sg_ptr), + PFN_DOWN(sg_ptr->length)); + } +} +#else /* !X86 */ +static inline void +msc_buffer_set_uc(struct msc_window *win, unsigned int nr_segs) {} +static inline void msc_buffer_set_wb(struct msc_window *win) {} +#endif /* CONFIG_X86 */ + +static struct page *msc_sg_page(struct scatterlist *sg) +{ + void *addr = sg_virt(sg); + + if (is_vmalloc_addr(addr)) + return vmalloc_to_page(addr); + + return sg_page(sg); +} + +/** + * msc_buffer_win_alloc() - alloc a window for a multiblock mode + * @msc: MSC device + * @nr_blocks: number of pages in this window + * + * This modifies msc::win_list and msc::base, which requires msc::buf_mutex + * to serialize, so the caller is expected to hold it. + * + * Return: 0 on success, -errno otherwise. + */ +static int msc_buffer_win_alloc(struct msc *msc, unsigned int nr_blocks) +{ + struct msc_window *win; + int ret = -ENOMEM; + + if (!nr_blocks) + return 0; + + win = kzalloc(sizeof(*win), GFP_KERNEL); + if (!win) + return -ENOMEM; + + win->msc = msc; + win->sgt = &win->_sgt; + win->lockout = WIN_READY; + spin_lock_init(&win->lo_lock); + + if (!list_empty(&msc->win_list)) { + struct msc_window *prev = list_last_entry(&msc->win_list, + struct msc_window, + entry); + + win->pgoff = prev->pgoff + prev->nr_blocks; + } + + if (msc->mbuf && msc->mbuf->alloc_window) + ret = msc->mbuf->alloc_window(msc->mbuf_priv, &win->sgt, + nr_blocks << PAGE_SHIFT); + else + ret = __msc_buffer_win_alloc(win, nr_blocks); + + if (ret <= 0) + goto err_nomem; + + msc_buffer_set_uc(win, ret); + + win->nr_segs = ret; + win->nr_blocks = nr_blocks; + + if (list_empty(&msc->win_list)) { + msc->base = msc_win_base(win); + msc->base_addr = msc_win_base_dma(win); + msc->cur_win = win; + } + + list_add_tail(&win->entry, &msc->win_list); + msc->nr_pages += nr_blocks; + + return 0; + +err_nomem: + kfree(win); + + return ret; +} + +static void __msc_buffer_win_free(struct msc *msc, struct msc_window *win) +{ + struct scatterlist *sg; + int i; + + for_each_sg(win->sgt->sgl, sg, win->nr_segs, i) { + struct page *page = msc_sg_page(sg); + + page->mapping = NULL; + dma_free_coherent(msc_dev(win->msc)->parent->parent, PAGE_SIZE, + sg_virt(sg), sg_dma_address(sg)); + } + sg_free_table(win->sgt); +} + +/** + * msc_buffer_win_free() - free a window from MSC's window list + * @msc: MSC device + * @win: window to free + * + * This modifies msc::win_list and msc::base, which requires msc::buf_mutex + * to serialize, so the caller is expected to hold it. + */ +static void msc_buffer_win_free(struct msc *msc, struct msc_window *win) +{ + msc->nr_pages -= win->nr_blocks; + + list_del(&win->entry); + if (list_empty(&msc->win_list)) { + msc->base = NULL; + msc->base_addr = 0; + } + + msc_buffer_set_wb(win); + + if (msc->mbuf && msc->mbuf->free_window) + msc->mbuf->free_window(msc->mbuf_priv, win->sgt); + else + __msc_buffer_win_free(msc, win); + + kfree(win); +} + +/** + * msc_buffer_relink() - set up block descriptors for multiblock mode + * @msc: MSC device + * + * This traverses msc::win_list, which requires msc::buf_mutex to serialize, + * so the caller is expected to hold it. + */ +static void msc_buffer_relink(struct msc *msc) +{ + struct msc_window *win, *next_win; + + /* call with msc::mutex locked */ + list_for_each_entry(win, &msc->win_list, entry) { + struct scatterlist *sg; + unsigned int blk; + u32 sw_tag = 0; + + /* + * Last window's next_win should point to the first window + * and MSC_SW_TAG_LASTWIN should be set. + */ + if (msc_is_last_win(win)) { + sw_tag |= MSC_SW_TAG_LASTWIN; + next_win = list_first_entry(&msc->win_list, + struct msc_window, entry); + } else { + next_win = list_next_entry(win, entry); + } + + for_each_sg(win->sgt->sgl, sg, win->nr_segs, blk) { + struct msc_block_desc *bdesc = sg_virt(sg); + + memset(bdesc, 0, sizeof(*bdesc)); + + bdesc->next_win = msc_win_base_pfn(next_win); + + /* + * Similarly to last window, last block should point + * to the first one. + */ + if (blk == win->nr_segs - 1) { + sw_tag |= MSC_SW_TAG_LASTBLK; + bdesc->next_blk = msc_win_base_pfn(win); + } else { + dma_addr_t addr = sg_dma_address(sg_next(sg)); + + bdesc->next_blk = PFN_DOWN(addr); + } + + bdesc->sw_tag = sw_tag; + bdesc->block_sz = sg->length / 64; + } + } + + /* + * Make the above writes globally visible before tracing is + * enabled to make sure hardware sees them coherently. + */ + wmb(); +} + +static void msc_buffer_multi_free(struct msc *msc) +{ + struct msc_window *win, *iter; + + list_for_each_entry_safe(win, iter, &msc->win_list, entry) + msc_buffer_win_free(msc, win); +} + +static int msc_buffer_multi_alloc(struct msc *msc, unsigned long *nr_pages, + unsigned int nr_wins) +{ + int ret, i; + + for (i = 0; i < nr_wins; i++) { + ret = msc_buffer_win_alloc(msc, nr_pages[i]); + if (ret) { + msc_buffer_multi_free(msc); + return ret; + } + } + + msc_buffer_relink(msc); + + return 0; +} + +/** + * msc_buffer_free() - free buffers for MSC + * @msc: MSC device + * + * Free MSC's storage buffers. + * + * This modifies msc::win_list and msc::base, which requires msc::buf_mutex to + * serialize, so the caller is expected to hold it. + */ +static void msc_buffer_free(struct msc *msc) +{ + if (msc->mode == MSC_MODE_SINGLE) + msc_buffer_contig_free(msc); + else if (msc->mode == MSC_MODE_MULTI) + msc_buffer_multi_free(msc); +} + +/** + * msc_buffer_alloc() - allocate a buffer for MSC + * @msc: MSC device + * @size: allocation size in bytes + * + * Allocate a storage buffer for MSC, depending on the msc::mode, it will be + * either done via msc_buffer_contig_alloc() for SINGLE operation mode or + * msc_buffer_win_alloc() for multiblock operation. The latter allocates one + * window per invocation, so in multiblock mode this can be called multiple + * times for the same MSC to allocate multiple windows. + * + * This modifies msc::win_list and msc::base, which requires msc::buf_mutex + * to serialize, so the caller is expected to hold it. + * + * Return: 0 on success, -errno otherwise. + */ +static int msc_buffer_alloc(struct msc *msc, unsigned long *nr_pages, + unsigned int nr_wins) +{ + int ret; + + /* -1: buffer not allocated */ + if (atomic_read(&msc->user_count) != -1) + return -EBUSY; + + if (msc->mode == MSC_MODE_SINGLE) { + if (nr_wins != 1) + return -EINVAL; + + ret = msc_buffer_contig_alloc(msc, nr_pages[0] << PAGE_SHIFT); + } else if (msc->mode == MSC_MODE_MULTI) { + ret = msc_buffer_multi_alloc(msc, nr_pages, nr_wins); + } else { + ret = -EINVAL; + } + + if (!ret) { + /* allocation should be visible before the counter goes to 0 */ + smp_mb__before_atomic(); + + if (WARN_ON_ONCE(atomic_cmpxchg(&msc->user_count, -1, 0) != -1)) + return -EINVAL; + } + + return ret; +} + +/** + * msc_buffer_unlocked_free_unless_used() - free a buffer unless it's in use + * @msc: MSC device + * + * This will free MSC buffer unless it is in use or there is no allocated + * buffer. + * Caller needs to hold msc::buf_mutex. + * + * Return: 0 on successful deallocation or if there was no buffer to + * deallocate, -EBUSY if there are active users. + */ +static int msc_buffer_unlocked_free_unless_used(struct msc *msc) +{ + int count, ret = 0; + + count = atomic_cmpxchg(&msc->user_count, 0, -1); + + /* > 0: buffer is allocated and has users */ + if (count > 0) + ret = -EBUSY; + /* 0: buffer is allocated, no users */ + else if (!count) + msc_buffer_free(msc); + /* < 0: no buffer, nothing to do */ + + return ret; +} + +/** + * msc_buffer_free_unless_used() - free a buffer unless it's in use + * @msc: MSC device + * + * This is a locked version of msc_buffer_unlocked_free_unless_used(). + */ +static int msc_buffer_free_unless_used(struct msc *msc) +{ + int ret; + + mutex_lock(&msc->buf_mutex); + ret = msc_buffer_unlocked_free_unless_used(msc); + mutex_unlock(&msc->buf_mutex); + + return ret; +} + +/** + * msc_buffer_get_page() - get MSC buffer page at a given offset + * @msc: MSC device + * @pgoff: page offset into the storage buffer + * + * This traverses msc::win_list, so holding msc::buf_mutex is expected from + * the caller. + * + * Return: page if @pgoff corresponds to a valid buffer page or NULL. + */ +static struct page *msc_buffer_get_page(struct msc *msc, unsigned long pgoff) +{ + struct msc_window *win; + struct scatterlist *sg; + unsigned int blk; + + if (msc->mode == MSC_MODE_SINGLE) + return msc_buffer_contig_get_page(msc, pgoff); + + list_for_each_entry(win, &msc->win_list, entry) + if (pgoff >= win->pgoff && pgoff < win->pgoff + win->nr_blocks) + goto found; + + return NULL; + +found: + pgoff -= win->pgoff; + + for_each_sg(win->sgt->sgl, sg, win->nr_segs, blk) { + struct page *page = msc_sg_page(sg); + size_t pgsz = PFN_DOWN(sg->length); + + if (pgoff < pgsz) + return page + pgoff; + + pgoff -= pgsz; + } + + return NULL; +} + +/** + * struct msc_win_to_user_struct - data for copy_to_user() callback + * @buf: userspace buffer to copy data to + * @offset: running offset + */ +struct msc_win_to_user_struct { + char __user *buf; + unsigned long offset; +}; + +/** + * msc_win_to_user() - iterator for msc_buffer_iterate() to copy data to user + * @data: callback's private data + * @src: source buffer + * @len: amount of data to copy from the source buffer + */ +static unsigned long msc_win_to_user(void *data, void *src, size_t len) +{ + struct msc_win_to_user_struct *u = data; + unsigned long ret; + + ret = copy_to_user(u->buf + u->offset, src, len); + u->offset += len - ret; + + return ret; +} + + +/* + * file operations' callbacks + */ + +static int intel_th_msc_open(struct inode *inode, struct file *file) +{ + struct intel_th_device *thdev = file->private_data; + struct msc *msc = dev_get_drvdata(&thdev->dev); + struct msc_iter *iter; + + if (!capable(CAP_SYS_RAWIO)) + return -EPERM; + + iter = msc_iter_install(msc); + if (IS_ERR(iter)) + return PTR_ERR(iter); + + file->private_data = iter; + + return nonseekable_open(inode, file); +} + +static int intel_th_msc_release(struct inode *inode, struct file *file) +{ + struct msc_iter *iter = file->private_data; + struct msc *msc = iter->msc; + + msc_iter_remove(iter, msc); + + return 0; +} + +static ssize_t +msc_single_to_user(struct msc *msc, char __user *buf, loff_t off, size_t len) +{ + unsigned long size = msc->nr_pages << PAGE_SHIFT, rem = len; + unsigned long start = off, tocopy = 0; + + if (msc->single_wrap) { + start += msc->single_sz; + if (start < size) { + tocopy = min(rem, size - start); + if (copy_to_user(buf, msc->base + start, tocopy)) + return -EFAULT; + + buf += tocopy; + rem -= tocopy; + start += tocopy; + } + + start &= size - 1; + if (rem) { + tocopy = min(rem, msc->single_sz - start); + if (copy_to_user(buf, msc->base + start, tocopy)) + return -EFAULT; + + rem -= tocopy; + } + + return len - rem; + } + + if (copy_to_user(buf, msc->base + start, rem)) + return -EFAULT; + + return len; +} + +static ssize_t intel_th_msc_read(struct file *file, char __user *buf, + size_t len, loff_t *ppos) +{ + struct msc_iter *iter = file->private_data; + struct msc *msc = iter->msc; + size_t size; + loff_t off = *ppos; + ssize_t ret = 0; + + if (!atomic_inc_unless_negative(&msc->user_count)) + return 0; + + if (msc->mode == MSC_MODE_SINGLE && !msc->single_wrap) + size = msc->single_sz; + else + size = msc->nr_pages << PAGE_SHIFT; + + if (!size) + goto put_count; + + if (off >= size) + goto put_count; + + if (off + len >= size) + len = size - off; + + if (msc->mode == MSC_MODE_SINGLE) { + ret = msc_single_to_user(msc, buf, off, len); + if (ret >= 0) + *ppos += ret; + } else if (msc->mode == MSC_MODE_MULTI) { + struct msc_win_to_user_struct u = { + .buf = buf, + .offset = 0, + }; + + ret = msc_buffer_iterate(iter, len, &u, msc_win_to_user); + if (ret >= 0) + *ppos = iter->offset; + } else { + ret = -EINVAL; + } + +put_count: + atomic_dec(&msc->user_count); + + return ret; +} + +/* + * vm operations callbacks (vm_ops) + */ + +static void msc_mmap_open(struct vm_area_struct *vma) +{ + struct msc_iter *iter = vma->vm_file->private_data; + struct msc *msc = iter->msc; + + atomic_inc(&msc->mmap_count); +} + +static void msc_mmap_close(struct vm_area_struct *vma) +{ + struct msc_iter *iter = vma->vm_file->private_data; + struct msc *msc = iter->msc; + unsigned long pg; + + if (!atomic_dec_and_mutex_lock(&msc->mmap_count, &msc->buf_mutex)) + return; + + /* drop page _refcounts */ + for (pg = 0; pg < msc->nr_pages; pg++) { + struct page *page = msc_buffer_get_page(msc, pg); + + if (WARN_ON_ONCE(!page)) + continue; + + if (page->mapping) + page->mapping = NULL; + } + + /* last mapping -- drop user_count */ + atomic_dec(&msc->user_count); + mutex_unlock(&msc->buf_mutex); +} + +static vm_fault_t msc_mmap_fault(struct vm_fault *vmf) +{ + struct msc_iter *iter = vmf->vma->vm_file->private_data; + struct msc *msc = iter->msc; + + vmf->page = msc_buffer_get_page(msc, vmf->pgoff); + if (!vmf->page) + return VM_FAULT_SIGBUS; + + get_page(vmf->page); + vmf->page->mapping = vmf->vma->vm_file->f_mapping; + vmf->page->index = vmf->pgoff; + + return 0; +} + +static const struct vm_operations_struct msc_mmap_ops = { + .open = msc_mmap_open, + .close = msc_mmap_close, + .fault = msc_mmap_fault, +}; + +static int intel_th_msc_mmap(struct file *file, struct vm_area_struct *vma) +{ + unsigned long size = vma->vm_end - vma->vm_start; + struct msc_iter *iter = vma->vm_file->private_data; + struct msc *msc = iter->msc; + int ret = -EINVAL; + + if (!size || offset_in_page(size)) + return -EINVAL; + + if (vma->vm_pgoff) + return -EINVAL; + + /* grab user_count once per mmap; drop in msc_mmap_close() */ + if (!atomic_inc_unless_negative(&msc->user_count)) + return -EINVAL; + + if (msc->mode != MSC_MODE_SINGLE && + msc->mode != MSC_MODE_MULTI) + goto out; + + if (size >> PAGE_SHIFT != msc->nr_pages) + goto out; + + atomic_set(&msc->mmap_count, 1); + ret = 0; + +out: + if (ret) + atomic_dec(&msc->user_count); + + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + vma->vm_flags |= VM_DONTEXPAND | VM_DONTCOPY; + vma->vm_ops = &msc_mmap_ops; + return ret; +} + +static const struct file_operations intel_th_msc_fops = { + .open = intel_th_msc_open, + .release = intel_th_msc_release, + .read = intel_th_msc_read, + .mmap = intel_th_msc_mmap, + .llseek = no_llseek, + .owner = THIS_MODULE, +}; + +static void intel_th_msc_wait_empty(struct intel_th_device *thdev) +{ + struct msc *msc = dev_get_drvdata(&thdev->dev); + unsigned long count; + u32 reg; + + for (reg = 0, count = MSC_PLE_WAITLOOP_DEPTH; + count && !(reg & MSCSTS_PLE); count--) { + reg = __raw_readl(msc->reg_base + REG_MSU_MSC0STS); + cpu_relax(); + } + + if (!count) + dev_dbg(msc_dev(msc), "timeout waiting for MSC0 PLE\n"); +} + +static int intel_th_msc_init(struct msc *msc) +{ + atomic_set(&msc->user_count, -1); + + msc->mode = msc->multi_is_broken ? MSC_MODE_SINGLE : MSC_MODE_MULTI; + mutex_init(&msc->buf_mutex); + INIT_LIST_HEAD(&msc->win_list); + INIT_LIST_HEAD(&msc->iter_list); + + msc->burst_len = + (ioread32(msc->reg_base + REG_MSU_MSC0CTL) & MSC_LEN) >> + __ffs(MSC_LEN); + + return 0; +} + +static int msc_win_switch(struct msc *msc) +{ + struct msc_window *first; + + if (list_empty(&msc->win_list)) + return -EINVAL; + + first = list_first_entry(&msc->win_list, struct msc_window, entry); + + if (msc_is_last_win(msc->cur_win)) + msc->cur_win = first; + else + msc->cur_win = list_next_entry(msc->cur_win, entry); + + msc->base = msc_win_base(msc->cur_win); + msc->base_addr = msc_win_base_dma(msc->cur_win); + + intel_th_trace_switch(msc->thdev); + + return 0; +} + +/** + * intel_th_msc_window_unlock - put the window back in rotation + * @dev: MSC device to which this relates + * @sgt: buffer's sg_table for the window, does nothing if NULL + */ +void intel_th_msc_window_unlock(struct device *dev, struct sg_table *sgt) +{ + struct msc *msc = dev_get_drvdata(dev); + struct msc_window *win; + + if (!sgt) + return; + + win = msc_find_window(msc, sgt, false); + if (!win) + return; + + msc_win_set_lockout(win, WIN_LOCKED, WIN_READY); + if (msc->switch_on_unlock == win) { + msc->switch_on_unlock = NULL; + msc_win_switch(msc); + } +} +EXPORT_SYMBOL_GPL(intel_th_msc_window_unlock); + +static void msc_work(struct work_struct *work) +{ + struct msc *msc = container_of(work, struct msc, work); + + intel_th_msc_deactivate(msc->thdev); +} + +static irqreturn_t intel_th_msc_interrupt(struct intel_th_device *thdev) +{ + struct msc *msc = dev_get_drvdata(&thdev->dev); + u32 msusts = ioread32(msc->msu_base + REG_MSU_MSUSTS); + u32 mask = msc->index ? MSUSTS_MSC1BLAST : MSUSTS_MSC0BLAST; + struct msc_window *win, *next_win; + + if (!msc->do_irq || !msc->mbuf) + return IRQ_NONE; + + msusts &= mask; + + if (!msusts) + return msc->enabled ? IRQ_HANDLED : IRQ_NONE; + + iowrite32(msusts, msc->msu_base + REG_MSU_MSUSTS); + + if (!msc->enabled) + return IRQ_NONE; + + /* grab the window before we do the switch */ + win = msc->cur_win; + if (!win) + return IRQ_HANDLED; + next_win = msc_next_window(win); + if (!next_win) + return IRQ_HANDLED; + + /* next window: if READY, proceed, if LOCKED, stop the trace */ + if (msc_win_set_lockout(next_win, WIN_READY, WIN_INUSE)) { + if (msc->stop_on_full) + schedule_work(&msc->work); + else + msc->switch_on_unlock = next_win; + + return IRQ_HANDLED; + } + + /* current window: INUSE -> LOCKED */ + msc_win_set_lockout(win, WIN_INUSE, WIN_LOCKED); + + msc_win_switch(msc); + + if (msc->mbuf && msc->mbuf->ready) + msc->mbuf->ready(msc->mbuf_priv, win->sgt, + msc_win_total_sz(win)); + + return IRQ_HANDLED; +} + +static const char * const msc_mode[] = { + [MSC_MODE_SINGLE] = "single", + [MSC_MODE_MULTI] = "multi", + [MSC_MODE_EXI] = "ExI", + [MSC_MODE_DEBUG] = "debug", +}; + +static ssize_t +wrap_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct msc *msc = dev_get_drvdata(dev); + + return scnprintf(buf, PAGE_SIZE, "%d\n", msc->wrap); +} + +static ssize_t +wrap_store(struct device *dev, struct device_attribute *attr, const char *buf, + size_t size) +{ + struct msc *msc = dev_get_drvdata(dev); + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + + msc->wrap = !!val; + + return size; +} + +static DEVICE_ATTR_RW(wrap); + +static void msc_buffer_unassign(struct msc *msc) +{ + lockdep_assert_held(&msc->buf_mutex); + + if (!msc->mbuf) + return; + + msc->mbuf->unassign(msc->mbuf_priv); + msu_buffer_put(msc->mbuf); + msc->mbuf_priv = NULL; + msc->mbuf = NULL; +} + +static ssize_t +mode_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct msc *msc = dev_get_drvdata(dev); + const char *mode = msc_mode[msc->mode]; + ssize_t ret; + + mutex_lock(&msc->buf_mutex); + if (msc->mbuf) + mode = msc->mbuf->name; + ret = scnprintf(buf, PAGE_SIZE, "%s\n", mode); + mutex_unlock(&msc->buf_mutex); + + return ret; +} + +static ssize_t +mode_store(struct device *dev, struct device_attribute *attr, const char *buf, + size_t size) +{ + const struct msu_buffer *mbuf = NULL; + struct msc *msc = dev_get_drvdata(dev); + size_t len = size; + char *cp, *mode; + int i, ret; + + if (!capable(CAP_SYS_RAWIO)) + return -EPERM; + + cp = memchr(buf, '\n', len); + if (cp) + len = cp - buf; + + mode = kstrndup(buf, len, GFP_KERNEL); + if (!mode) + return -ENOMEM; + + i = match_string(msc_mode, ARRAY_SIZE(msc_mode), mode); + if (i >= 0) { + kfree(mode); + goto found; + } + + /* Buffer sinks only work with a usable IRQ */ + if (!msc->do_irq) { + kfree(mode); + return -EINVAL; + } + + mbuf = msu_buffer_get(mode); + kfree(mode); + if (mbuf) + goto found; + + return -EINVAL; + +found: + if (i == MSC_MODE_MULTI && msc->multi_is_broken) + return -EOPNOTSUPP; + + mutex_lock(&msc->buf_mutex); + ret = 0; + + /* Same buffer: do nothing */ + if (mbuf && mbuf == msc->mbuf) { + /* put the extra reference we just got */ + msu_buffer_put(mbuf); + goto unlock; + } + + ret = msc_buffer_unlocked_free_unless_used(msc); + if (ret) + goto unlock; + + if (mbuf) { + void *mbuf_priv = mbuf->assign(dev, &i); + + if (!mbuf_priv) { + ret = -ENOMEM; + goto unlock; + } + + msc_buffer_unassign(msc); + msc->mbuf_priv = mbuf_priv; + msc->mbuf = mbuf; + } else { + msc_buffer_unassign(msc); + } + + msc->mode = i; + +unlock: + if (ret && mbuf) + msu_buffer_put(mbuf); + mutex_unlock(&msc->buf_mutex); + + return ret ? ret : size; +} + +static DEVICE_ATTR_RW(mode); + +static ssize_t +nr_pages_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct msc *msc = dev_get_drvdata(dev); + struct msc_window *win; + size_t count = 0; + + mutex_lock(&msc->buf_mutex); + + if (msc->mode == MSC_MODE_SINGLE) + count = scnprintf(buf, PAGE_SIZE, "%ld\n", msc->nr_pages); + else if (msc->mode == MSC_MODE_MULTI) { + list_for_each_entry(win, &msc->win_list, entry) { + count += scnprintf(buf + count, PAGE_SIZE - count, + "%d%c", win->nr_blocks, + msc_is_last_win(win) ? '\n' : ','); + } + } else { + count = scnprintf(buf, PAGE_SIZE, "unsupported\n"); + } + + mutex_unlock(&msc->buf_mutex); + + return count; +} + +static ssize_t +nr_pages_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct msc *msc = dev_get_drvdata(dev); + unsigned long val, *win = NULL, *rewin; + size_t len = size; + const char *p = buf; + char *end, *s; + int ret, nr_wins = 0; + + if (!capable(CAP_SYS_RAWIO)) + return -EPERM; + + ret = msc_buffer_free_unless_used(msc); + if (ret) + return ret; + + /* scan the comma-separated list of allocation sizes */ + end = memchr(buf, '\n', len); + if (end) + len = end - buf; + + do { + end = memchr(p, ',', len); + s = kstrndup(p, end ? end - p : len, GFP_KERNEL); + if (!s) { + ret = -ENOMEM; + goto free_win; + } + + ret = kstrtoul(s, 10, &val); + kfree(s); + + if (ret || !val) + goto free_win; + + if (nr_wins && msc->mode == MSC_MODE_SINGLE) { + ret = -EINVAL; + goto free_win; + } + + nr_wins++; + rewin = krealloc(win, sizeof(*win) * nr_wins, GFP_KERNEL); + if (!rewin) { + kfree(win); + return -ENOMEM; + } + + win = rewin; + win[nr_wins - 1] = val; + + if (!end) + break; + + /* consume the number and the following comma, hence +1 */ + len -= end - p + 1; + p = end + 1; + } while (len); + + mutex_lock(&msc->buf_mutex); + ret = msc_buffer_alloc(msc, win, nr_wins); + mutex_unlock(&msc->buf_mutex); + +free_win: + kfree(win); + + return ret ? ret : size; +} + +static DEVICE_ATTR_RW(nr_pages); + +static ssize_t +win_switch_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct msc *msc = dev_get_drvdata(dev); + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + + if (val != 1) + return -EINVAL; + + ret = -EINVAL; + mutex_lock(&msc->buf_mutex); + /* + * Window switch can only happen in the "multi" mode. + * If a external buffer is engaged, they have the full + * control over window switching. + */ + if (msc->mode == MSC_MODE_MULTI && !msc->mbuf) + ret = msc_win_switch(msc); + mutex_unlock(&msc->buf_mutex); + + return ret ? ret : size; +} + +static DEVICE_ATTR_WO(win_switch); + +static ssize_t stop_on_full_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct msc *msc = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", msc->stop_on_full); +} + +static ssize_t stop_on_full_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct msc *msc = dev_get_drvdata(dev); + int ret; + + ret = kstrtobool(buf, &msc->stop_on_full); + if (ret) + return ret; + + return size; +} + +static DEVICE_ATTR_RW(stop_on_full); + +static struct attribute *msc_output_attrs[] = { + &dev_attr_wrap.attr, + &dev_attr_mode.attr, + &dev_attr_nr_pages.attr, + &dev_attr_win_switch.attr, + &dev_attr_stop_on_full.attr, + NULL, +}; + +static struct attribute_group msc_output_group = { + .attrs = msc_output_attrs, +}; + +static int intel_th_msc_probe(struct intel_th_device *thdev) +{ + struct device *dev = &thdev->dev; + struct resource *res; + struct msc *msc; + void __iomem *base; + int err; + + res = intel_th_device_get_resource(thdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + + base = devm_ioremap(dev, res->start, resource_size(res)); + if (!base) + return -ENOMEM; + + msc = devm_kzalloc(dev, sizeof(*msc), GFP_KERNEL); + if (!msc) + return -ENOMEM; + + res = intel_th_device_get_resource(thdev, IORESOURCE_IRQ, 1); + if (!res) + msc->do_irq = 1; + + if (INTEL_TH_CAP(to_intel_th(thdev), multi_is_broken)) + msc->multi_is_broken = 1; + + msc->index = thdev->id; + + msc->thdev = thdev; + msc->reg_base = base + msc->index * 0x100; + msc->msu_base = base; + + INIT_WORK(&msc->work, msc_work); + err = intel_th_msc_init(msc); + if (err) + return err; + + dev_set_drvdata(dev, msc); + + return 0; +} + +static void intel_th_msc_remove(struct intel_th_device *thdev) +{ + struct msc *msc = dev_get_drvdata(&thdev->dev); + int ret; + + intel_th_msc_deactivate(thdev); + + /* + * Buffers should not be used at this point except if the + * output character device is still open and the parent + * device gets detached from its bus, which is a FIXME. + */ + ret = msc_buffer_free_unless_used(msc); + WARN_ON_ONCE(ret); +} + +static struct intel_th_driver intel_th_msc_driver = { + .probe = intel_th_msc_probe, + .remove = intel_th_msc_remove, + .irq = intel_th_msc_interrupt, + .wait_empty = intel_th_msc_wait_empty, + .activate = intel_th_msc_activate, + .deactivate = intel_th_msc_deactivate, + .fops = &intel_th_msc_fops, + .attr_group = &msc_output_group, + .driver = { + .name = "msc", + .owner = THIS_MODULE, + }, +}; + +module_driver(intel_th_msc_driver, + intel_th_driver_register, + intel_th_driver_unregister); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Intel(R) Trace Hub Memory Storage Unit driver"); +MODULE_AUTHOR("Alexander Shishkin <alexander.shishkin@linux.intel.com>"); diff --git a/drivers/hwtracing/intel_th/msu.h b/drivers/hwtracing/intel_th/msu.h new file mode 100644 index 000000000..e771f509b --- /dev/null +++ b/drivers/hwtracing/intel_th/msu.h @@ -0,0 +1,118 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Intel(R) Trace Hub Memory Storage Unit (MSU) data structures + * + * Copyright (C) 2014-2015 Intel Corporation. + */ + +#ifndef __INTEL_TH_MSU_H__ +#define __INTEL_TH_MSU_H__ + +enum { + REG_MSU_MSUPARAMS = 0x0000, + REG_MSU_MSUSTS = 0x0008, + REG_MSU_MINTCTL = 0x0004, /* MSU-global interrupt control */ + REG_MSU_MSC0CTL = 0x0100, /* MSC0 control */ + REG_MSU_MSC0STS = 0x0104, /* MSC0 status */ + REG_MSU_MSC0BAR = 0x0108, /* MSC0 output base address */ + REG_MSU_MSC0SIZE = 0x010c, /* MSC0 output size */ + REG_MSU_MSC0MWP = 0x0110, /* MSC0 write pointer */ + REG_MSU_MSC0NWSA = 0x011c, /* MSC0 next window start address */ + + REG_MSU_MSC1CTL = 0x0200, /* MSC1 control */ + REG_MSU_MSC1STS = 0x0204, /* MSC1 status */ + REG_MSU_MSC1BAR = 0x0208, /* MSC1 output base address */ + REG_MSU_MSC1SIZE = 0x020c, /* MSC1 output size */ + REG_MSU_MSC1MWP = 0x0210, /* MSC1 write pointer */ + REG_MSU_MSC1NWSA = 0x021c, /* MSC1 next window start address */ +}; + +/* MSUSTS bits */ +#define MSUSTS_MSU_INT BIT(0) +#define MSUSTS_MSC0BLAST BIT(16) +#define MSUSTS_MSC1BLAST BIT(24) + +/* MSCnCTL bits */ +#define MSC_EN BIT(0) +#define MSC_WRAPEN BIT(1) +#define MSC_RD_HDR_OVRD BIT(2) +#define MSC_MODE (BIT(4) | BIT(5)) +#define MSC_LEN (BIT(8) | BIT(9) | BIT(10)) + +/* MINTCTL bits */ +#define MICDE BIT(0) +#define M0BLIE BIT(16) +#define M1BLIE BIT(24) + +/* MSCnSTS bits */ +#define MSCSTS_WRAPSTAT BIT(1) /* Wrap occurred */ +#define MSCSTS_PLE BIT(2) /* Pipeline Empty */ + +/* + * Multiblock/multiwindow block descriptor + */ +struct msc_block_desc { + u32 sw_tag; + u32 block_sz; + u32 next_blk; + u32 next_win; + u32 res0[4]; + u32 hw_tag; + u32 valid_dw; + u32 ts_low; + u32 ts_high; + u32 res1[4]; +} __packed; + +#define MSC_BDESC sizeof(struct msc_block_desc) +#define DATA_IN_PAGE (PAGE_SIZE - MSC_BDESC) + +/* MSC multiblock sw tag bits */ +#define MSC_SW_TAG_LASTBLK BIT(0) +#define MSC_SW_TAG_LASTWIN BIT(1) + +/* MSC multiblock hw tag bits */ +#define MSC_HW_TAG_TRIGGER BIT(0) +#define MSC_HW_TAG_BLOCKWRAP BIT(1) +#define MSC_HW_TAG_WINWRAP BIT(2) +#define MSC_HW_TAG_ENDBIT BIT(3) + +static inline unsigned long msc_data_sz(struct msc_block_desc *bdesc) +{ + if (!bdesc->valid_dw) + return 0; + + return bdesc->valid_dw * 4 - MSC_BDESC; +} + +static inline unsigned long msc_total_sz(struct msc_block_desc *bdesc) +{ + return bdesc->valid_dw * 4; +} + +static inline unsigned long msc_block_sz(struct msc_block_desc *bdesc) +{ + return bdesc->block_sz * 64 - MSC_BDESC; +} + +static inline bool msc_block_wrapped(struct msc_block_desc *bdesc) +{ + if (bdesc->hw_tag & (MSC_HW_TAG_BLOCKWRAP | MSC_HW_TAG_WINWRAP)) + return true; + + return false; +} + +static inline bool msc_block_last_written(struct msc_block_desc *bdesc) +{ + if ((bdesc->hw_tag & MSC_HW_TAG_ENDBIT) || + (msc_data_sz(bdesc) != msc_block_sz(bdesc))) + return true; + + return false; +} + +/* waiting for Pipeline Empty bit(s) to assert for MSC */ +#define MSC_PLE_WAITLOOP_DEPTH 10000 + +#endif /* __INTEL_TH_MSU_H__ */ diff --git a/drivers/hwtracing/intel_th/pci.c b/drivers/hwtracing/intel_th/pci.c new file mode 100644 index 000000000..e25438025 --- /dev/null +++ b/drivers/hwtracing/intel_th/pci.c @@ -0,0 +1,328 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel(R) Trace Hub pci driver + * + * Copyright (C) 2014-2015 Intel Corporation. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/types.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/sysfs.h> +#include <linux/pci.h> + +#include "intel_th.h" + +#define DRIVER_NAME "intel_th_pci" + +enum { + TH_PCI_CONFIG_BAR = 0, + TH_PCI_STH_SW_BAR = 2, + TH_PCI_RTIT_BAR = 4, +}; + +#define BAR_MASK (BIT(TH_PCI_CONFIG_BAR) | BIT(TH_PCI_STH_SW_BAR)) + +#define PCI_REG_NPKDSC 0x80 +#define NPKDSC_TSACT BIT(5) + +static int intel_th_pci_activate(struct intel_th *th) +{ + struct pci_dev *pdev = to_pci_dev(th->dev); + u32 npkdsc; + int err; + + if (!INTEL_TH_CAP(th, tscu_enable)) + return 0; + + err = pci_read_config_dword(pdev, PCI_REG_NPKDSC, &npkdsc); + if (!err) { + npkdsc |= NPKDSC_TSACT; + err = pci_write_config_dword(pdev, PCI_REG_NPKDSC, npkdsc); + } + + if (err) + dev_err(&pdev->dev, "failed to read NPKDSC register\n"); + + return err; +} + +static void intel_th_pci_deactivate(struct intel_th *th) +{ + struct pci_dev *pdev = to_pci_dev(th->dev); + u32 npkdsc; + int err; + + if (!INTEL_TH_CAP(th, tscu_enable)) + return; + + err = pci_read_config_dword(pdev, PCI_REG_NPKDSC, &npkdsc); + if (!err) { + npkdsc |= NPKDSC_TSACT; + err = pci_write_config_dword(pdev, PCI_REG_NPKDSC, npkdsc); + } + + if (err) + dev_err(&pdev->dev, "failed to read NPKDSC register\n"); +} + +static int intel_th_pci_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + struct intel_th_drvdata *drvdata = (void *)id->driver_data; + struct resource resource[TH_MMIO_END + TH_NVEC_MAX] = { + [TH_MMIO_CONFIG] = pdev->resource[TH_PCI_CONFIG_BAR], + [TH_MMIO_SW] = pdev->resource[TH_PCI_STH_SW_BAR], + }; + int err, r = TH_MMIO_SW + 1, i; + struct intel_th *th; + + err = pcim_enable_device(pdev); + if (err) + return err; + + err = pcim_iomap_regions_request_all(pdev, BAR_MASK, DRIVER_NAME); + if (err) + return err; + + if (pdev->resource[TH_PCI_RTIT_BAR].start) { + resource[TH_MMIO_RTIT] = pdev->resource[TH_PCI_RTIT_BAR]; + r++; + } + + err = pci_alloc_irq_vectors(pdev, 1, 8, PCI_IRQ_ALL_TYPES); + if (err > 0) + for (i = 0; i < err; i++, r++) { + resource[r].flags = IORESOURCE_IRQ; + resource[r].start = pci_irq_vector(pdev, i); + } + + th = intel_th_alloc(&pdev->dev, drvdata, resource, r); + if (IS_ERR(th)) { + err = PTR_ERR(th); + goto err_free_irq; + } + + th->activate = intel_th_pci_activate; + th->deactivate = intel_th_pci_deactivate; + + pci_set_master(pdev); + + return 0; + +err_free_irq: + pci_free_irq_vectors(pdev); + return err; +} + +static void intel_th_pci_remove(struct pci_dev *pdev) +{ + struct intel_th *th = pci_get_drvdata(pdev); + + intel_th_free(th); + + pci_free_irq_vectors(pdev); +} + +static const struct intel_th_drvdata intel_th_1x_multi_is_broken = { + .multi_is_broken = 1, +}; + +static const struct intel_th_drvdata intel_th_2x = { + .tscu_enable = 1, + .has_mintctl = 1, +}; + +static const struct pci_device_id intel_th_pci_id_table[] = { + { + PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x9d26), + .driver_data = (kernel_ulong_t)0, + }, + { + PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0xa126), + .driver_data = (kernel_ulong_t)0, + }, + { + /* Apollo Lake */ + PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x5a8e), + .driver_data = (kernel_ulong_t)0, + }, + { + /* Broxton */ + PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x0a80), + .driver_data = (kernel_ulong_t)0, + }, + { + /* Broxton B-step */ + PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x1a8e), + .driver_data = (kernel_ulong_t)0, + }, + { + /* Kaby Lake PCH-H */ + PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0xa2a6), + .driver_data = (kernel_ulong_t)&intel_th_1x_multi_is_broken, + }, + { + /* Denverton */ + PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x19e1), + .driver_data = (kernel_ulong_t)0, + }, + { + /* Lewisburg PCH */ + PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0xa1a6), + .driver_data = (kernel_ulong_t)0, + }, + { + /* Lewisburg PCH */ + PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0xa226), + .driver_data = (kernel_ulong_t)0, + }, + { + /* Gemini Lake */ + PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x318e), + .driver_data = (kernel_ulong_t)&intel_th_2x, + }, + { + /* Cannon Lake H */ + PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0xa326), + .driver_data = (kernel_ulong_t)&intel_th_2x, + }, + { + /* Cannon Lake LP */ + PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x9da6), + .driver_data = (kernel_ulong_t)&intel_th_2x, + }, + { + /* Cedar Fork PCH */ + PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x18e1), + .driver_data = (kernel_ulong_t)&intel_th_2x, + }, + { + /* Ice Lake PCH */ + PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x34a6), + .driver_data = (kernel_ulong_t)&intel_th_2x, + }, + { + /* Comet Lake */ + PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x02a6), + .driver_data = (kernel_ulong_t)&intel_th_2x, + }, + { + /* Comet Lake PCH */ + PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x06a6), + .driver_data = (kernel_ulong_t)&intel_th_2x, + }, + { + /* Comet Lake PCH-V */ + PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0xa3a6), + .driver_data = (kernel_ulong_t)&intel_th_1x_multi_is_broken, + }, + { + /* Ice Lake NNPI */ + PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x45c5), + .driver_data = (kernel_ulong_t)&intel_th_2x, + }, + { + /* Ice Lake CPU */ + PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x8a29), + .driver_data = (kernel_ulong_t)&intel_th_2x, + }, + { + /* Tiger Lake CPU */ + PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x9a33), + .driver_data = (kernel_ulong_t)&intel_th_2x, + }, + { + /* Tiger Lake PCH */ + PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0xa0a6), + .driver_data = (kernel_ulong_t)&intel_th_2x, + }, + { + /* Tiger Lake PCH-H */ + PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x43a6), + .driver_data = (kernel_ulong_t)&intel_th_2x, + }, + { + /* Jasper Lake PCH */ + PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x4da6), + .driver_data = (kernel_ulong_t)&intel_th_2x, + }, + { + /* Jasper Lake CPU */ + PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x4e29), + .driver_data = (kernel_ulong_t)&intel_th_2x, + }, + { + /* Elkhart Lake CPU */ + PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x4529), + .driver_data = (kernel_ulong_t)&intel_th_2x, + }, + { + /* Elkhart Lake */ + PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x4b26), + .driver_data = (kernel_ulong_t)&intel_th_2x, + }, + { + /* Emmitsburg PCH */ + PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x1bcc), + .driver_data = (kernel_ulong_t)&intel_th_2x, + }, + { + /* Alder Lake */ + PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x7aa6), + .driver_data = (kernel_ulong_t)&intel_th_2x, + }, + { + /* Alder Lake-P */ + PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x51a6), + .driver_data = (kernel_ulong_t)&intel_th_2x, + }, + { + /* Alder Lake-M */ + PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x54a6), + .driver_data = (kernel_ulong_t)&intel_th_2x, + }, + { + /* Meteor Lake-P */ + PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x7e24), + .driver_data = (kernel_ulong_t)&intel_th_2x, + }, + { + /* Raptor Lake-S */ + PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x7a26), + .driver_data = (kernel_ulong_t)&intel_th_2x, + }, + { + /* Raptor Lake-S CPU */ + PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0xa76f), + .driver_data = (kernel_ulong_t)&intel_th_2x, + }, + { + /* Alder Lake CPU */ + PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x466f), + .driver_data = (kernel_ulong_t)&intel_th_2x, + }, + { + /* Rocket Lake CPU */ + PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x4c19), + .driver_data = (kernel_ulong_t)&intel_th_2x, + }, + { 0 }, +}; + +MODULE_DEVICE_TABLE(pci, intel_th_pci_id_table); + +static struct pci_driver intel_th_pci_driver = { + .name = DRIVER_NAME, + .id_table = intel_th_pci_id_table, + .probe = intel_th_pci_probe, + .remove = intel_th_pci_remove, +}; + +module_pci_driver(intel_th_pci_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Intel(R) Trace Hub PCI controller driver"); +MODULE_AUTHOR("Alexander Shishkin <alexander.shishkin@intel.com>"); diff --git a/drivers/hwtracing/intel_th/pti.c b/drivers/hwtracing/intel_th/pti.c new file mode 100644 index 000000000..0da6b787f --- /dev/null +++ b/drivers/hwtracing/intel_th/pti.c @@ -0,0 +1,343 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel(R) Trace Hub PTI output driver + * + * Copyright (C) 2014-2016 Intel Corporation. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/types.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/sizes.h> +#include <linux/printk.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/io.h> + +#include "intel_th.h" +#include "pti.h" + +struct pti_device { + void __iomem *base; + struct intel_th_device *thdev; + unsigned int mode; + unsigned int freeclk; + unsigned int clkdiv; + unsigned int patgen; + unsigned int lpp_dest_mask; + unsigned int lpp_dest; +}; + +/* map PTI widths to MODE settings of PTI_CTL register */ +static const unsigned int pti_mode[] = { + 0, 4, 8, 0, 12, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, +}; + +static int pti_width_mode(unsigned int width) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(pti_mode); i++) + if (pti_mode[i] == width) + return i; + + return -EINVAL; +} + +static ssize_t mode_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct pti_device *pti = dev_get_drvdata(dev); + + return scnprintf(buf, PAGE_SIZE, "%d\n", pti_mode[pti->mode]); +} + +static ssize_t mode_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct pti_device *pti = dev_get_drvdata(dev); + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + + ret = pti_width_mode(val); + if (ret < 0) + return ret; + + pti->mode = ret; + + return size; +} + +static DEVICE_ATTR_RW(mode); + +static ssize_t +freerunning_clock_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct pti_device *pti = dev_get_drvdata(dev); + + return scnprintf(buf, PAGE_SIZE, "%d\n", pti->freeclk); +} + +static ssize_t +freerunning_clock_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct pti_device *pti = dev_get_drvdata(dev); + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + + pti->freeclk = !!val; + + return size; +} + +static DEVICE_ATTR_RW(freerunning_clock); + +static ssize_t +clock_divider_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct pti_device *pti = dev_get_drvdata(dev); + + return scnprintf(buf, PAGE_SIZE, "%d\n", 1u << pti->clkdiv); +} + +static ssize_t +clock_divider_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct pti_device *pti = dev_get_drvdata(dev); + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + + if (!is_power_of_2(val) || val > 8 || !val) + return -EINVAL; + + pti->clkdiv = val; + + return size; +} + +static DEVICE_ATTR_RW(clock_divider); + +static struct attribute *pti_output_attrs[] = { + &dev_attr_mode.attr, + &dev_attr_freerunning_clock.attr, + &dev_attr_clock_divider.attr, + NULL, +}; + +static struct attribute_group pti_output_group = { + .attrs = pti_output_attrs, +}; + +static int intel_th_pti_activate(struct intel_th_device *thdev) +{ + struct pti_device *pti = dev_get_drvdata(&thdev->dev); + u32 ctl = PTI_EN; + + if (pti->patgen) + ctl |= pti->patgen << __ffs(PTI_PATGENMODE); + if (pti->freeclk) + ctl |= PTI_FCEN; + ctl |= pti->mode << __ffs(PTI_MODE); + ctl |= pti->clkdiv << __ffs(PTI_CLKDIV); + ctl |= pti->lpp_dest << __ffs(LPP_DEST); + + iowrite32(ctl, pti->base + REG_PTI_CTL); + + intel_th_trace_enable(thdev); + + return 0; +} + +static void intel_th_pti_deactivate(struct intel_th_device *thdev) +{ + struct pti_device *pti = dev_get_drvdata(&thdev->dev); + + intel_th_trace_disable(thdev); + + iowrite32(0, pti->base + REG_PTI_CTL); +} + +static void read_hw_config(struct pti_device *pti) +{ + u32 ctl = ioread32(pti->base + REG_PTI_CTL); + + pti->mode = (ctl & PTI_MODE) >> __ffs(PTI_MODE); + pti->clkdiv = (ctl & PTI_CLKDIV) >> __ffs(PTI_CLKDIV); + pti->freeclk = !!(ctl & PTI_FCEN); + + if (!pti_mode[pti->mode]) + pti->mode = pti_width_mode(4); + if (!pti->clkdiv) + pti->clkdiv = 1; + + if (pti->thdev->output.type == GTH_LPP) { + if (ctl & LPP_PTIPRESENT) + pti->lpp_dest_mask |= LPP_DEST_PTI; + if (ctl & LPP_BSSBPRESENT) + pti->lpp_dest_mask |= LPP_DEST_EXI; + if (ctl & LPP_DEST) + pti->lpp_dest = 1; + } +} + +static int intel_th_pti_probe(struct intel_th_device *thdev) +{ + struct device *dev = &thdev->dev; + struct resource *res; + struct pti_device *pti; + void __iomem *base; + + res = intel_th_device_get_resource(thdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + + base = devm_ioremap(dev, res->start, resource_size(res)); + if (!base) + return -ENOMEM; + + pti = devm_kzalloc(dev, sizeof(*pti), GFP_KERNEL); + if (!pti) + return -ENOMEM; + + pti->thdev = thdev; + pti->base = base; + + read_hw_config(pti); + + dev_set_drvdata(dev, pti); + + return 0; +} + +static void intel_th_pti_remove(struct intel_th_device *thdev) +{ +} + +static struct intel_th_driver intel_th_pti_driver = { + .probe = intel_th_pti_probe, + .remove = intel_th_pti_remove, + .activate = intel_th_pti_activate, + .deactivate = intel_th_pti_deactivate, + .attr_group = &pti_output_group, + .driver = { + .name = "pti", + .owner = THIS_MODULE, + }, +}; + +static const char * const lpp_dest_str[] = { "pti", "exi" }; + +static ssize_t lpp_dest_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct pti_device *pti = dev_get_drvdata(dev); + ssize_t ret = 0; + int i; + + for (i = ARRAY_SIZE(lpp_dest_str) - 1; i >= 0; i--) { + const char *fmt = pti->lpp_dest == i ? "[%s] " : "%s "; + + if (!(pti->lpp_dest_mask & BIT(i))) + continue; + + ret += scnprintf(buf + ret, PAGE_SIZE - ret, + fmt, lpp_dest_str[i]); + } + + if (ret) + buf[ret - 1] = '\n'; + + return ret; +} + +static ssize_t lpp_dest_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct pti_device *pti = dev_get_drvdata(dev); + int i; + + i = sysfs_match_string(lpp_dest_str, buf); + if (i < 0) + return i; + + if (!(pti->lpp_dest_mask & BIT(i))) + return -EINVAL; + + pti->lpp_dest = i; + return size; +} + +static DEVICE_ATTR_RW(lpp_dest); + +static struct attribute *lpp_output_attrs[] = { + &dev_attr_mode.attr, + &dev_attr_freerunning_clock.attr, + &dev_attr_clock_divider.attr, + &dev_attr_lpp_dest.attr, + NULL, +}; + +static struct attribute_group lpp_output_group = { + .attrs = lpp_output_attrs, +}; + +static struct intel_th_driver intel_th_lpp_driver = { + .probe = intel_th_pti_probe, + .remove = intel_th_pti_remove, + .activate = intel_th_pti_activate, + .deactivate = intel_th_pti_deactivate, + .attr_group = &lpp_output_group, + .driver = { + .name = "lpp", + .owner = THIS_MODULE, + }, +}; + +static int __init intel_th_pti_lpp_init(void) +{ + int err; + + err = intel_th_driver_register(&intel_th_pti_driver); + if (err) + return err; + + err = intel_th_driver_register(&intel_th_lpp_driver); + if (err) { + intel_th_driver_unregister(&intel_th_pti_driver); + return err; + } + + return 0; +} + +module_init(intel_th_pti_lpp_init); + +static void __exit intel_th_pti_lpp_exit(void) +{ + intel_th_driver_unregister(&intel_th_pti_driver); + intel_th_driver_unregister(&intel_th_lpp_driver); +} + +module_exit(intel_th_pti_lpp_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Intel(R) Trace Hub PTI/LPP output driver"); +MODULE_AUTHOR("Alexander Shishkin <alexander.shishkin@linux.intel.com>"); diff --git a/drivers/hwtracing/intel_th/pti.h b/drivers/hwtracing/intel_th/pti.h new file mode 100644 index 000000000..7dfc04313 --- /dev/null +++ b/drivers/hwtracing/intel_th/pti.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Intel(R) Trace Hub PTI output data structures + * + * Copyright (C) 2014-2015 Intel Corporation. + */ + +#ifndef __INTEL_TH_STH_H__ +#define __INTEL_TH_STH_H__ + +enum { + REG_PTI_CTL = 0x1c00, +}; + +#define PTI_EN BIT(0) +#define PTI_FCEN BIT(1) +#define PTI_MODE 0xf0 +#define LPP_PTIPRESENT BIT(8) +#define LPP_BSSBPRESENT BIT(9) +#define PTI_CLKDIV 0x000f0000 +#define PTI_PATGENMODE 0x00f00000 +#define LPP_DEST BIT(25) +#define LPP_BSSBACT BIT(30) +#define LPP_LPPBUSY BIT(31) + +#define LPP_DEST_PTI BIT(0) +#define LPP_DEST_EXI BIT(1) + +#endif /* __INTEL_TH_STH_H__ */ diff --git a/drivers/hwtracing/intel_th/sth.c b/drivers/hwtracing/intel_th/sth.c new file mode 100644 index 000000000..9ca8c4e04 --- /dev/null +++ b/drivers/hwtracing/intel_th/sth.c @@ -0,0 +1,261 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel(R) Trace Hub Software Trace Hub support + * + * Copyright (C) 2014-2015 Intel Corporation. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/types.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/mm.h> +#include <linux/slab.h> +#include <linux/stm.h> + +#include "intel_th.h" +#include "sth.h" + +struct sth_device { + void __iomem *base; + void __iomem *channels; + phys_addr_t channels_phys; + struct device *dev; + struct stm_data stm; + unsigned int sw_nmasters; +}; + +static struct intel_th_channel __iomem * +sth_channel(struct sth_device *sth, unsigned int master, unsigned int channel) +{ + struct intel_th_channel __iomem *sw_map = sth->channels; + + return &sw_map[(master - sth->stm.sw_start) * sth->stm.sw_nchannels + + channel]; +} + +static void sth_iowrite(void __iomem *dest, const unsigned char *payload, + unsigned int size) +{ + switch (size) { +#ifdef CONFIG_64BIT + case 8: + writeq_relaxed(*(u64 *)payload, dest); + break; +#endif + case 4: + writel_relaxed(*(u32 *)payload, dest); + break; + case 2: + writew_relaxed(*(u16 *)payload, dest); + break; + case 1: + writeb_relaxed(*(u8 *)payload, dest); + break; + default: + break; + } +} + +static ssize_t notrace sth_stm_packet(struct stm_data *stm_data, + unsigned int master, + unsigned int channel, + unsigned int packet, + unsigned int flags, + unsigned int size, + const unsigned char *payload) +{ + struct sth_device *sth = container_of(stm_data, struct sth_device, stm); + struct intel_th_channel __iomem *out = + sth_channel(sth, master, channel); + u64 __iomem *outp = &out->Dn; + unsigned long reg = REG_STH_TRIG; + +#ifndef CONFIG_64BIT + if (size > 4) + size = 4; +#endif + + size = rounddown_pow_of_two(size); + + switch (packet) { + /* Global packets (GERR, XSYNC, TRIG) are sent with register writes */ + case STP_PACKET_GERR: + reg += 4; + fallthrough; + + case STP_PACKET_XSYNC: + reg += 8; + fallthrough; + + case STP_PACKET_TRIG: + if (flags & STP_PACKET_TIMESTAMPED) + reg += 4; + writeb_relaxed(*payload, sth->base + reg); + break; + + case STP_PACKET_MERR: + if (size > 4) + size = 4; + + sth_iowrite(&out->MERR, payload, size); + break; + + case STP_PACKET_FLAG: + if (flags & STP_PACKET_TIMESTAMPED) + outp = (u64 __iomem *)&out->FLAG_TS; + else + outp = (u64 __iomem *)&out->FLAG; + + size = 0; + writeb_relaxed(0, outp); + break; + + case STP_PACKET_USER: + if (flags & STP_PACKET_TIMESTAMPED) + outp = &out->USER_TS; + else + outp = &out->USER; + sth_iowrite(outp, payload, size); + break; + + case STP_PACKET_DATA: + outp = &out->Dn; + + if (flags & STP_PACKET_TIMESTAMPED) + outp += 2; + if (flags & STP_PACKET_MARKED) + outp++; + + sth_iowrite(outp, payload, size); + break; + default: + return -ENOTSUPP; + } + + return size; +} + +static phys_addr_t +sth_stm_mmio_addr(struct stm_data *stm_data, unsigned int master, + unsigned int channel, unsigned int nr_chans) +{ + struct sth_device *sth = container_of(stm_data, struct sth_device, stm); + phys_addr_t addr; + + master -= sth->stm.sw_start; + addr = sth->channels_phys + (master * sth->stm.sw_nchannels + channel) * + sizeof(struct intel_th_channel); + + if (offset_in_page(addr) || + offset_in_page(nr_chans * sizeof(struct intel_th_channel))) + return 0; + + return addr; +} + +static int sth_stm_link(struct stm_data *stm_data, unsigned int master, + unsigned int channel) +{ + struct sth_device *sth = container_of(stm_data, struct sth_device, stm); + + return intel_th_set_output(to_intel_th_device(sth->dev), master); +} + +static int intel_th_sw_init(struct sth_device *sth) +{ + u32 reg; + + reg = ioread32(sth->base + REG_STH_STHCAP1); + sth->stm.sw_nchannels = reg & 0xff; + + reg = ioread32(sth->base + REG_STH_STHCAP0); + sth->stm.sw_start = reg & 0xffff; + sth->stm.sw_end = reg >> 16; + + sth->sw_nmasters = sth->stm.sw_end - sth->stm.sw_start; + dev_dbg(sth->dev, "sw_start: %x sw_end: %x masters: %x nchannels: %x\n", + sth->stm.sw_start, sth->stm.sw_end, sth->sw_nmasters, + sth->stm.sw_nchannels); + + return 0; +} + +static int intel_th_sth_probe(struct intel_th_device *thdev) +{ + struct device *dev = &thdev->dev; + struct sth_device *sth; + struct resource *res; + void __iomem *base, *channels; + int err; + + res = intel_th_device_get_resource(thdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + + base = devm_ioremap(dev, res->start, resource_size(res)); + if (!base) + return -ENOMEM; + + res = intel_th_device_get_resource(thdev, IORESOURCE_MEM, 1); + if (!res) + return -ENODEV; + + channels = devm_ioremap(dev, res->start, resource_size(res)); + if (!channels) + return -ENOMEM; + + sth = devm_kzalloc(dev, sizeof(*sth), GFP_KERNEL); + if (!sth) + return -ENOMEM; + + sth->dev = dev; + sth->base = base; + sth->channels = channels; + sth->channels_phys = res->start; + sth->stm.name = dev_name(dev); + sth->stm.packet = sth_stm_packet; + sth->stm.mmio_addr = sth_stm_mmio_addr; + sth->stm.sw_mmiosz = sizeof(struct intel_th_channel); + sth->stm.link = sth_stm_link; + + err = intel_th_sw_init(sth); + if (err) + return err; + + err = stm_register_device(dev, &sth->stm, THIS_MODULE); + if (err) { + dev_err(dev, "stm_register_device failed\n"); + return err; + } + + dev_set_drvdata(dev, sth); + + return 0; +} + +static void intel_th_sth_remove(struct intel_th_device *thdev) +{ + struct sth_device *sth = dev_get_drvdata(&thdev->dev); + + stm_unregister_device(&sth->stm); +} + +static struct intel_th_driver intel_th_sth_driver = { + .probe = intel_th_sth_probe, + .remove = intel_th_sth_remove, + .driver = { + .name = "sth", + .owner = THIS_MODULE, + }, +}; + +module_driver(intel_th_sth_driver, + intel_th_driver_register, + intel_th_driver_unregister); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Intel(R) Trace Hub Software Trace Hub driver"); +MODULE_AUTHOR("Alexander Shishkin <alexander.shishkin@intel.com>"); diff --git a/drivers/hwtracing/intel_th/sth.h b/drivers/hwtracing/intel_th/sth.h new file mode 100644 index 000000000..f97fc0c51 --- /dev/null +++ b/drivers/hwtracing/intel_th/sth.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Intel(R) Trace Hub Software Trace Hub (STH) data structures + * + * Copyright (C) 2014-2015 Intel Corporation. + */ + +#ifndef __INTEL_TH_STH_H__ +#define __INTEL_TH_STH_H__ + +enum { + REG_STH_STHCAP0 = 0x0000, /* capabilities pt1 */ + REG_STH_STHCAP1 = 0x0004, /* capabilities pt2 */ + REG_STH_TRIG = 0x0008, /* TRIG packet payload */ + REG_STH_TRIG_TS = 0x000c, /* TRIG_TS packet payload */ + REG_STH_XSYNC = 0x0010, /* XSYNC packet payload */ + REG_STH_XSYNC_TS = 0x0014, /* XSYNC_TS packet payload */ + REG_STH_GERR = 0x0018, /* GERR packet payload */ +}; + +struct intel_th_channel { + u64 Dn; + u64 DnM; + u64 DnTS; + u64 DnMTS; + u64 USER; + u64 USER_TS; + u32 FLAG; + u32 FLAG_TS; + u32 MERR; + u32 __unused; +} __packed; + +#endif /* __INTEL_TH_STH_H__ */ |