From 7bd1d4093c2fa37d1ecab05da3c9d48ea2af2264 Mon Sep 17 00:00:00 2001 From: Alexander Shishkin Date: Tue, 22 Sep 2015 15:47:10 +0300 Subject: stm class: Introduce an abstraction for System Trace Module devices A System Trace Module (STM) is a device exporting data in System Trace Protocol (STP) format as defined by MIPI STP standards. Examples of such devices are Intel(R) Trace Hub and Coresight STM. This abstraction provides a unified interface for software trace sources to send their data over an STM device to a debug host. In order to do that, such a trace source needs to be assigned a pair of master/channel identifiers that all the data from this source will be tagged with. The STP decoder on the debug host side will use these master/channel tags to distinguish different trace streams from one another inside one STP stream. This abstraction provides a configfs-based policy management mechanism for dynamic allocation of these master/channel pairs based on trace source-supplied string identifier. It has the flexibility of being defined at runtime and at the same time (provided that the policy definition is aligned with the decoding end) consistency. For userspace trace sources, this abstraction provides write()-based and mmap()-based (if the underlying stm device allows this) output mechanism. For kernel-side trace sources, we provide "stm_source" device class that can be connected to an stm device at run time. Cc: linux-api@vger.kernel.org Reviewed-by: Mathieu Poirier Signed-off-by: Alexander Shishkin Signed-off-by: Greg Kroah-Hartman --- drivers/Kconfig | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers/Kconfig') diff --git a/drivers/Kconfig b/drivers/Kconfig index 46b4a8e0f859..b6e1cea63f81 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -188,4 +188,6 @@ source "drivers/nvdimm/Kconfig" source "drivers/nvmem/Kconfig" +source "drivers/hwtracing/stm/Kconfig" + endmenu -- cgit v1.2.3-58-ga151 From 39f4034693b7c7bd1fe4cb58c93259d600f55561 Mon Sep 17 00:00:00 2001 From: Alexander Shishkin Date: Tue, 22 Sep 2015 15:47:14 +0300 Subject: intel_th: Add driver infrastructure for Intel(R) Trace Hub devices 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. For these subdevices, we create a bus, where they can be discovered and configured by userspace software. This patch creates this bus infrastructure, three types of devices (source, output, switch), resource allocation, some callback mechanisms to facilitate communication between the subdevices' drivers and some common sysfs attributes. Signed-off-by: Alexander Shishkin Signed-off-by: Greg Kroah-Hartman --- .../ABI/testing/sysfs-bus-intel_th-output-devices | 13 + Documentation/trace/intel_th.txt | 99 +++ drivers/Kconfig | 2 + drivers/Makefile | 1 + drivers/hwtracing/intel_th/Kconfig | 24 + drivers/hwtracing/intel_th/Makefile | 3 + drivers/hwtracing/intel_th/core.c | 692 +++++++++++++++++++++ drivers/hwtracing/intel_th/debug.c | 36 ++ drivers/hwtracing/intel_th/debug.h | 34 + drivers/hwtracing/intel_th/intel_th.h | 244 ++++++++ 10 files changed, 1148 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-bus-intel_th-output-devices create mode 100644 Documentation/trace/intel_th.txt create mode 100644 drivers/hwtracing/intel_th/Kconfig create mode 100644 drivers/hwtracing/intel_th/Makefile create mode 100644 drivers/hwtracing/intel_th/core.c create mode 100644 drivers/hwtracing/intel_th/debug.c create mode 100644 drivers/hwtracing/intel_th/debug.h create mode 100644 drivers/hwtracing/intel_th/intel_th.h (limited to 'drivers/Kconfig') diff --git a/Documentation/ABI/testing/sysfs-bus-intel_th-output-devices b/Documentation/ABI/testing/sysfs-bus-intel_th-output-devices new file mode 100644 index 000000000000..4d48a9451866 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-bus-intel_th-output-devices @@ -0,0 +1,13 @@ +What: /sys/bus/intel_th/devices/-/active +Date: June 2015 +KernelVersion: 4.3 +Contact: Alexander Shishkin +Description: (RW) Writes of 1 or 0 enable or disable trace output to this + output device. Reads return current status. + +What: /sys/bus/intel_th/devices/-msc/port +Date: June 2015 +KernelVersion: 4.3 +Contact: Alexander Shishkin +Description: (RO) Port number, corresponding to this output device on the + switch (GTH). diff --git a/Documentation/trace/intel_th.txt b/Documentation/trace/intel_th.txt new file mode 100644 index 000000000000..f7fc5ba5df8d --- /dev/null +++ b/Documentation/trace/intel_th.txt @@ -0,0 +1,99 @@ +Intel(R) Trace Hub (TH) +======================= + +Overview +-------- + +Intel(R) Trace Hub (TH) is a set of hardware blocks 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. For more information on the hardware, see Intel(R) Trace +Hub developer's manual [1]. + +It consists of trace sources, trace destinations (outputs) and a +switch (Global Trace Hub, GTH). These devices are placed on a bus of +their own ("intel_th"), where they can be discovered and configured +via sysfs attributes. + +Currently, the following Intel TH subdevices (blocks) are supported: + - Software Trace Hub (STH), trace source, which is a System Trace + Module (STM) device, + - Memory Storage Unit (MSU), trace output, which allows storing + trace hub output in system memory, + - Parallel Trace Interface output (PTI), trace output to an external + debug host via a PTI port, + - Global Trace Hub (GTH), which is a switch and a central component + of Intel(R) Trace Hub architecture. + +Common attributes for output devices are described in +Documentation/ABI/testing/sysfs-bus-intel_th-output-devices, the most +notable of them is "active", which enables or disables trace output +into that particular output device. + +GTH allows directing different STP masters into different output ports +via its "masters" attribute group. More detailed GTH interface +description is at Documentation/ABI/testing/sysfs-bus-intel_th-devices-gth. + +STH registers an stm class device, through which it provides interface +to userspace and kernelspace software trace sources. See +Documentation/tracing/stm.txt for more information on that. + +MSU can be configured to collect trace data into a system memory +buffer, which can later on be read from its device nodes via read() or +mmap() interface. + +On the whole, Intel(R) Trace Hub does not require any special +userspace software to function; everything can be configured, started +and collected via sysfs attributes, and device nodes. + +[1] https://software.intel.com/sites/default/files/managed/d3/3c/intel-th-developer-manual.pdf + +Bus and Subdevices +------------------ + +For each Intel TH device in the system a bus of its own is +created and assigned an id number that reflects the order in which TH +devices were emumerated. All TH subdevices (devices on intel_th bus) +begin with this id: 0-gth, 0-msc0, 0-msc1, 0-pti, 0-sth, which is +followed by device's name and an optional index. + +Output devices also get a device node in /dev/intel_thN, where N is +the Intel TH device id. For example, MSU's memory buffers, when +allocated, are accessible via /dev/intel_th0/msc{0,1}. + +Quick example +------------- + +# figure out which GTH port is the first memory controller: + +$ cat /sys/bus/intel_th/devices/0-msc0/port +0 + +# looks like it's port 0, configure master 33 to send data to port 0: + +$ echo 0 > /sys/bus/intel_th/devices/0-gth/masters/33 + +# allocate a 2-windowed multiblock buffer on the first memory +# controller, each with 64 pages: + +$ echo multi > /sys/bus/intel_th/devices/0-msc0/mode +$ echo 64,64 > /sys/bus/intel_th/devices/0-msc0/nr_pages + +# enable wrapping for this controller, too: + +$ echo 1 > /sys/bus/intel_th/devices/0-msc0/wrap + +# and enable tracing into this port: + +$ echo 1 > /sys/bus/intel_th/devices/0-msc0/active + +# .. send data to master 33, see stm.txt for more details .. +# .. wait for traces to pile up .. +# .. and stop the trace: + +$ echo 0 > /sys/bus/intel_th/devices/0-msc0/active + +# and now you can collect the trace from the device node: + +$ cat /dev/intel_th0/msc0 > my_stp_trace diff --git a/drivers/Kconfig b/drivers/Kconfig index b6e1cea63f81..709488ae882e 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -190,4 +190,6 @@ source "drivers/nvmem/Kconfig" source "drivers/hwtracing/stm/Kconfig" +source "drivers/hwtracing/intel_th/Kconfig" + endmenu diff --git a/drivers/Makefile b/drivers/Makefile index e71b5e2a2c71..e63542dd7010 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -165,6 +165,7 @@ obj-$(CONFIG_PERF_EVENTS) += perf/ obj-$(CONFIG_RAS) += ras/ obj-$(CONFIG_THUNDERBOLT) += thunderbolt/ obj-$(CONFIG_CORESIGHT) += hwtracing/coresight/ +obj-y += hwtracing/intel_th/ obj-$(CONFIG_STM) += hwtracing/stm/ obj-$(CONFIG_ANDROID) += android/ obj-$(CONFIG_NVMEM) += nvmem/ diff --git a/drivers/hwtracing/intel_th/Kconfig b/drivers/hwtracing/intel_th/Kconfig new file mode 100644 index 000000000000..0cb01ee9090e --- /dev/null +++ b/drivers/hwtracing/intel_th/Kconfig @@ -0,0 +1,24 @@ +config INTEL_TH + tristate "Intel(R) Trace Hub controller" + 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_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 000000000000..dfd7906462da --- /dev/null +++ b/drivers/hwtracing/intel_th/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_INTEL_TH) += intel_th.o +intel_th-y := core.o +intel_th-$(CONFIG_INTEL_TH_DEBUG) += debug.o diff --git a/drivers/hwtracing/intel_th/core.c b/drivers/hwtracing/intel_th/core.c new file mode 100644 index 000000000000..165d3001c301 --- /dev/null +++ b/drivers/hwtracing/intel_th/core.c @@ -0,0 +1,692 @@ +/* + * Intel(R) Trace Hub driver core + * + * Copyright (C) 2014-2015 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "intel_th.h" +#include "debug.h" + +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); + + ret = thdrv->probe(to_intel_th_device(dev)); + if (ret) + return ret; + + if (thdev->type == INTEL_TH_OUTPUT && + !intel_th_output_assigned(thdev)) + ret = hubdrv->assign(hub, thdev); + + return ret; +} + +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_device(dev->parent); + int err; + + if (thdev->type == INTEL_TH_SWITCH) { + err = device_for_each_child(dev, thdev, intel_th_child_remove); + if (err) + return err; + } + + 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) + hubdrv->unassign(hub, thdev); + } + + return 0; +} + +static struct bus_type intel_th_bus = { + .name = "intel_th", + .dev_attrs = NULL, + .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); + char *node; + + if (thdev->id >= 0) + node = kasprintf(GFP_KERNEL, "intel_th%d/%s%d", 0, thdev->name, + thdev->id); + else + node = kasprintf(GFP_KERNEL, "intel_th%d/%s", 0, 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 int intel_th_output_activate(struct intel_th_device *thdev) +{ + struct intel_th_driver *thdrv = to_intel_th_driver(thdev->dev.driver); + + if (thdrv->activate) + return thdrv->activate(thdev); + + intel_th_trace_enable(thdev); + + return 0; +} + +static void intel_th_output_deactivate(struct intel_th_device *thdev) +{ + struct intel_th_driver *thdrv = to_intel_th_driver(thdev->dev.driver); + + if (thdrv->deactivate) + thdrv->deactivate(thdev); + else + intel_th_trace_disable(thdev); +} + +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_SWITCH) + parent = th->dev; + else + parent = &th->hub->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 struct intel_th_subdevice { + const char *name; + struct resource res[3]; + unsigned nres; + unsigned type; + unsigned otype; + int id; +} intel_th_subdevices[TH_SUBDEVICE_MAX] = { + { + .nres = 1, + .res = { + { + .start = REG_GTH_OFFSET, + .end = REG_GTH_OFFSET + REG_GTH_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, + .otype = GTH_MSU, + }, + { + .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, + .otype = GTH_MSU, + }, + { + .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 = 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, + }, + { + .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, + }, +}; + +static int intel_th_populate(struct intel_th *th, struct resource *devres, + unsigned int ndevres, int irq) +{ + struct resource res[3]; + unsigned int req = 0; + int i, err; + + /* create devices for each intel_th_subdevice */ + for (i = 0; i < ARRAY_SIZE(intel_th_subdevices); i++) { + struct intel_th_subdevice *subdev = &intel_th_subdevices[i]; + struct intel_th_device *thdev; + int r; + + thdev = intel_th_device_alloc(th, subdev->type, subdev->name, + subdev->id); + if (!thdev) { + err = -ENOMEM; + goto kill_subdevs; + } + + memcpy(res, subdev->res, + sizeof(struct resource) * subdev->nres); + + for (r = 0; r < subdev->nres; r++) { + 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; + 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) { + res[r].start = irq; + } + } + + err = intel_th_device_add_resources(thdev, res, subdev->nres); + if (err) { + put_device(&thdev->dev); + goto kill_subdevs; + } + + if (subdev->type == INTEL_TH_OUTPUT) { + thdev->dev.devt = MKDEV(th->major, i); + thdev->output.type = subdev->otype; + thdev->output.port = -1; + } + + err = device_add(&thdev->dev); + if (err) { + put_device(&thdev->dev); + goto kill_subdevs; + } + + /* need switch driver to be loaded to enumerate the rest */ + if (subdev->type == INTEL_TH_SWITCH && !req) { + th->hub = thdev; + err = request_module("intel_th_%s", subdev->name); + if (!err) + req++; + } + + th->thdev[i] = thdev; + } + + return 0; + +kill_subdevs: + for (i-- ; i >= 0; i--) + intel_th_device_remove(th->thdev[i]); + + return err; +} + +static int match_devt(struct device *dev, void *data) +{ + dev_t devt = (dev_t)(unsigned long)data; + + return dev->devt == devt; +} + +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(&intel_th_bus, NULL, + (void *)(unsigned long)inode->i_rdev, + match_devt); + 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, +}; + +/** + * intel_th_alloc() - allocate a new Intel TH device and its subdevices + * @dev: parent device + * @devres: parent's resources + * @ndevres: number of resources + * @irq: irq number + */ +struct intel_th * +intel_th_alloc(struct device *dev, struct resource *devres, + unsigned int ndevres, int irq) +{ + struct intel_th *th; + int err; + + 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->dev = dev; + + err = intel_th_populate(th, devres, ndevres, irq); + if (err) + goto err_chrdev; + + 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; + + for (i = 0; i < TH_SUBDEVICE_MAX; i++) + if (th->thdev[i] != th->hub) + intel_th_device_remove(th->thdev[i]); + + intel_th_device_remove(th->hub); + + __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; + + hubdrv->enable(hub, &thdev->output); + + return 0; +} +EXPORT_SYMBOL_GPL(intel_th_trace_enable); + +/** + * 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); + + 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_device(thdev->dev.parent); + struct intel_th_driver *hubdrv = to_intel_th_driver(hub->dev.driver); + + if (!hubdrv->set_output) + return -ENOTSUPP; + + return hubdrv->set_output(hub, master); +} +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 "); diff --git a/drivers/hwtracing/intel_th/debug.c b/drivers/hwtracing/intel_th/debug.c new file mode 100644 index 000000000000..788a1f0a97ad --- /dev/null +++ b/drivers/hwtracing/intel_th/debug.c @@ -0,0 +1,36 @@ +/* + * Intel(R) Trace Hub driver debugging + * + * Copyright (C) 2014-2015 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + */ + +#include +#include +#include + +#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 000000000000..88311bad3ba4 --- /dev/null +++ b/drivers/hwtracing/intel_th/debug.h @@ -0,0 +1,34 @@ +/* + * Intel(R) Trace Hub driver debugging + * + * Copyright (C) 2014-2015 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + */ + +#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/intel_th.h b/drivers/hwtracing/intel_th/intel_th.h new file mode 100644 index 000000000000..57fd72b20fae --- /dev/null +++ b/drivers/hwtracing/intel_th/intel_th.h @@ -0,0 +1,244 @@ +/* + * Intel(R) Trace Hub data structures + * + * Copyright (C) 2014-2015 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + */ + +#ifndef __INTEL_TH_H__ +#define __INTEL_TH_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_output - descriptor INTEL_TH_OUTPUT type devices + * @port: output port number, assigned by the switch + * @type: GTH_{MSU,CTP,PTI} + * @multiblock: true for multiblock output configuration + * @active: true when this output is enabled + * + * 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; + bool multiblock; + bool active; +}; + +/** + * struct intel_th_device - device on the intel_th bus + * @dev: device + * @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 + * @output: output descriptor for INTEL_TH_OUTPUT devices + * @name: device name to match the driver + */ +struct intel_th_device { + struct device dev; + struct resource *resource; + unsigned int num_resources; + unsigned int type; + int id; + + /* 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; +} + +/** + * 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; +} + +/** + * 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 + * @enable: enable tracing for a given output device + * @disable: disable tracing for a given output device + * @fops: file operations for device nodes + * + * 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 (*enable)(struct intel_th_device *thdev, + struct intel_th_output *output); + void (*disable)(struct intel_th_device *thdev, + struct intel_th_output *output); + /* output ops */ + void (*irq)(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; + + /* 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) + +static inline struct intel_th_device * +to_intel_th_hub(struct intel_th_device *thdev) +{ + struct device *parent = thdev->dev.parent; + + if (!parent) + return NULL; + + return to_intel_th_device(parent); +} + +struct intel_th * +intel_th_alloc(struct device *dev, struct resource *devres, + unsigned int ndevres, int irq); +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_disable(struct intel_th_device *thdev); +int intel_th_set_output(struct intel_th_device *thdev, + unsigned int master); + +enum { + TH_MMIO_CONFIG = 0, + TH_MMIO_SW = 2, + TH_MMIO_END, +}; + +#define TH_SUBDEVICE_MAX 6 +#define TH_POSSIBLE_OUTPUTS 8 +#define TH_CONFIGURABLE_MASTERS 256 +#define TH_MSC_MAX 2 + +/** + * struct intel_th - Intel TH controller + * @dev: driver core's device + * @thdev: subdevices + * @hub: "switch" subdevice (GTH) + * @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; + + int id; + int major; +#ifdef CONFIG_INTEL_TH_DEBUG + struct dentry *dbg; +#endif +}; + +/* + * Register windows + */ +enum { + /* Global Trace Hub (GTH) */ + REG_GTH_OFFSET = 0x0000, + REG_GTH_LENGTH = 0x2000, + + /* 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, +}; + +/* + * GTH, output ports configuration + */ +enum { + GTH_NONE = 0, + GTH_MSU, /* memory/usb */ + GTH_CTP, /* Common Trace Port */ + GTH_PTI = 4, /* MIPI-PTI */ +}; + +#endif -- cgit v1.2.3-58-ga151 From 6a8c3be7ec8eb3c1197766f9245e0d65a4e5aff8 Mon Sep 17 00:00:00 2001 From: Alan Tull Date: Wed, 7 Oct 2015 16:36:28 +0100 Subject: add FPGA manager core API to support programming FPGA's. The following functions are exported as GPL: * fpga_mgr_buf_load Load fpga from image in buffer * fpga_mgr_firmware_load Request firmware and load it to the FPGA. * fpga_mgr_register * fpga_mgr_unregister FPGA device drivers can be added by calling fpga_mgr_register() to register a set of fpga_manager_ops to do device specific stuff. * of_fpga_mgr_get * fpga_mgr_put Get/put a reference to a fpga manager. The following sysfs files are created: * /sys/class/fpga_manager//name Name of low level driver. * /sys/class/fpga_manager//state State of fpga manager Signed-off-by: Alan Tull Acked-by: Michal Simek Signed-off-by: Greg Kroah-Hartman --- drivers/Kconfig | 2 + drivers/Makefile | 1 + drivers/fpga/Kconfig | 14 ++ drivers/fpga/Makefile | 8 + drivers/fpga/fpga-mgr.c | 382 ++++++++++++++++++++++++++++++++++++++++++ include/linux/fpga/fpga-mgr.h | 127 ++++++++++++++ 6 files changed, 534 insertions(+) create mode 100644 drivers/fpga/Kconfig create mode 100644 drivers/fpga/Makefile create mode 100644 drivers/fpga/fpga-mgr.c create mode 100644 include/linux/fpga/fpga-mgr.h (limited to 'drivers/Kconfig') diff --git a/drivers/Kconfig b/drivers/Kconfig index 709488ae882e..5a89e409ad18 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -192,4 +192,6 @@ source "drivers/hwtracing/stm/Kconfig" source "drivers/hwtracing/intel_th/Kconfig" +source "drivers/fpga/Kconfig" + endmenu diff --git a/drivers/Makefile b/drivers/Makefile index e63542dd7010..7064bf476c2a 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -169,3 +169,4 @@ obj-y += hwtracing/intel_th/ obj-$(CONFIG_STM) += hwtracing/stm/ obj-$(CONFIG_ANDROID) += android/ obj-$(CONFIG_NVMEM) += nvmem/ +obj-$(CONFIG_FPGA) += fpga/ diff --git a/drivers/fpga/Kconfig b/drivers/fpga/Kconfig new file mode 100644 index 000000000000..f1f1f6df54d3 --- /dev/null +++ b/drivers/fpga/Kconfig @@ -0,0 +1,14 @@ +# +# FPGA framework configuration +# + +menu "FPGA Configuration Support" + +config FPGA + tristate "FPGA Configuration Framework" + help + Say Y here if you want support for configuring FPGAs from the + kernel. The FPGA framework adds a FPGA manager class and FPGA + manager drivers. + +endmenu diff --git a/drivers/fpga/Makefile b/drivers/fpga/Makefile new file mode 100644 index 000000000000..3313c5266795 --- /dev/null +++ b/drivers/fpga/Makefile @@ -0,0 +1,8 @@ +# +# Makefile for the fpga framework and fpga manager drivers. +# + +# Core FPGA Manager Framework +obj-$(CONFIG_FPGA) += fpga-mgr.o + +# FPGA Manager Drivers diff --git a/drivers/fpga/fpga-mgr.c b/drivers/fpga/fpga-mgr.c new file mode 100644 index 000000000000..25261636687c --- /dev/null +++ b/drivers/fpga/fpga-mgr.c @@ -0,0 +1,382 @@ +/* + * FPGA Manager Core + * + * Copyright (C) 2013-2015 Altera Corporation + * + * With code from the mailing list: + * Copyright (C) 2013 Xilinx, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ +#include +#include +#include +#include +#include +#include +#include + +static DEFINE_IDA(fpga_mgr_ida); +static struct class *fpga_mgr_class; + +/** + * fpga_mgr_buf_load - load fpga from image in buffer + * @mgr: fpga manager + * @flags: flags setting fpga confuration modes + * @buf: buffer contain fpga image + * @count: byte count of buf + * + * Step the low level fpga manager through the device-specific steps of getting + * an FPGA ready to be configured, writing the image to it, then doing whatever + * post-configuration steps necessary. + * + * Return: 0 on success, negative error code otherwise. + */ +int fpga_mgr_buf_load(struct fpga_manager *mgr, u32 flags, const char *buf, + size_t count) +{ + struct device *dev = &mgr->dev; + int ret; + + if (!mgr) + return -ENODEV; + + /* + * Call the low level driver's write_init function. This will do the + * device-specific things to get the FPGA into the state where it is + * ready to receive an FPGA image. + */ + mgr->state = FPGA_MGR_STATE_WRITE_INIT; + ret = mgr->mops->write_init(mgr, flags, buf, count); + if (ret) { + dev_err(dev, "Error preparing FPGA for writing\n"); + mgr->state = FPGA_MGR_STATE_WRITE_INIT_ERR; + return ret; + } + + /* + * Write the FPGA image to the FPGA. + */ + mgr->state = FPGA_MGR_STATE_WRITE; + ret = mgr->mops->write(mgr, buf, count); + if (ret) { + dev_err(dev, "Error while writing image data to FPGA\n"); + mgr->state = FPGA_MGR_STATE_WRITE_ERR; + return ret; + } + + /* + * After all the FPGA image has been written, do the device specific + * steps to finish and set the FPGA into operating mode. + */ + mgr->state = FPGA_MGR_STATE_WRITE_COMPLETE; + ret = mgr->mops->write_complete(mgr, flags); + if (ret) { + dev_err(dev, "Error after writing image data to FPGA\n"); + mgr->state = FPGA_MGR_STATE_WRITE_COMPLETE_ERR; + return ret; + } + mgr->state = FPGA_MGR_STATE_OPERATING; + + return 0; +} +EXPORT_SYMBOL_GPL(fpga_mgr_buf_load); + +/** + * fpga_mgr_firmware_load - request firmware and load to fpga + * @mgr: fpga manager + * @flags: flags setting fpga confuration modes + * @image_name: name of image file on the firmware search path + * + * Request an FPGA image using the firmware class, then write out to the FPGA. + * Update the state before each step to provide info on what step failed if + * there is a failure. + * + * Return: 0 on success, negative error code otherwise. + */ +int fpga_mgr_firmware_load(struct fpga_manager *mgr, u32 flags, + const char *image_name) +{ + struct device *dev = &mgr->dev; + const struct firmware *fw; + int ret; + + if (!mgr) + return -ENODEV; + + dev_info(dev, "writing %s to %s\n", image_name, mgr->name); + + mgr->state = FPGA_MGR_STATE_FIRMWARE_REQ; + + ret = request_firmware(&fw, image_name, dev); + if (ret) { + mgr->state = FPGA_MGR_STATE_FIRMWARE_REQ_ERR; + dev_err(dev, "Error requesting firmware %s\n", image_name); + return ret; + } + + ret = fpga_mgr_buf_load(mgr, flags, fw->data, fw->size); + if (ret) + return ret; + + release_firmware(fw); + + return 0; +} +EXPORT_SYMBOL_GPL(fpga_mgr_firmware_load); + +static const char * const state_str[] = { + [FPGA_MGR_STATE_UNKNOWN] = "unknown", + [FPGA_MGR_STATE_POWER_OFF] = "power off", + [FPGA_MGR_STATE_POWER_UP] = "power up", + [FPGA_MGR_STATE_RESET] = "reset", + + /* requesting FPGA image from firmware */ + [FPGA_MGR_STATE_FIRMWARE_REQ] = "firmware request", + [FPGA_MGR_STATE_FIRMWARE_REQ_ERR] = "firmware request error", + + /* Preparing FPGA to receive image */ + [FPGA_MGR_STATE_WRITE_INIT] = "write init", + [FPGA_MGR_STATE_WRITE_INIT_ERR] = "write init error", + + /* Writing image to FPGA */ + [FPGA_MGR_STATE_WRITE] = "write", + [FPGA_MGR_STATE_WRITE_ERR] = "write error", + + /* Finishing configuration after image has been written */ + [FPGA_MGR_STATE_WRITE_COMPLETE] = "write complete", + [FPGA_MGR_STATE_WRITE_COMPLETE_ERR] = "write complete error", + + /* FPGA reports to be in normal operating mode */ + [FPGA_MGR_STATE_OPERATING] = "operating", +}; + +static ssize_t name_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fpga_manager *mgr = to_fpga_manager(dev); + + return sprintf(buf, "%s\n", mgr->name); +} + +static ssize_t state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fpga_manager *mgr = to_fpga_manager(dev); + + return sprintf(buf, "%s\n", state_str[mgr->state]); +} + +static DEVICE_ATTR_RO(name); +static DEVICE_ATTR_RO(state); + +static struct attribute *fpga_mgr_attrs[] = { + &dev_attr_name.attr, + &dev_attr_state.attr, + NULL, +}; +ATTRIBUTE_GROUPS(fpga_mgr); + +static int fpga_mgr_of_node_match(struct device *dev, const void *data) +{ + return dev->of_node == data; +} + +/** + * of_fpga_mgr_get - get an exclusive reference to a fpga mgr + * @node: device node + * + * Given a device node, get an exclusive reference to a fpga mgr. + * + * Return: fpga manager struct or IS_ERR() condition containing error code. + */ +struct fpga_manager *of_fpga_mgr_get(struct device_node *node) +{ + struct fpga_manager *mgr; + struct device *dev; + + if (!node) + return ERR_PTR(-EINVAL); + + dev = class_find_device(fpga_mgr_class, NULL, node, + fpga_mgr_of_node_match); + if (!dev) + return ERR_PTR(-ENODEV); + + mgr = to_fpga_manager(dev); + put_device(dev); + if (!mgr) + return ERR_PTR(-ENODEV); + + /* Get exclusive use of fpga manager */ + if (!mutex_trylock(&mgr->ref_mutex)) + return ERR_PTR(-EBUSY); + + if (!try_module_get(THIS_MODULE)) { + mutex_unlock(&mgr->ref_mutex); + return ERR_PTR(-ENODEV); + } + + return mgr; +} +EXPORT_SYMBOL_GPL(of_fpga_mgr_get); + +/** + * fpga_mgr_put - release a reference to a fpga manager + * @mgr: fpga manager structure + */ +void fpga_mgr_put(struct fpga_manager *mgr) +{ + if (mgr) { + module_put(THIS_MODULE); + mutex_unlock(&mgr->ref_mutex); + } +} +EXPORT_SYMBOL_GPL(fpga_mgr_put); + +/** + * fpga_mgr_register - register a low level fpga manager driver + * @dev: fpga manager device from pdev + * @name: fpga manager name + * @mops: pointer to structure of fpga manager ops + * @priv: fpga manager private data + * + * Return: 0 on success, negative error code otherwise. + */ +int fpga_mgr_register(struct device *dev, const char *name, + const struct fpga_manager_ops *mops, + void *priv) +{ + struct fpga_manager *mgr; + const char *dt_label; + int id, ret; + + if (!mops || !mops->write_init || !mops->write || + !mops->write_complete || !mops->state) { + dev_err(dev, "Attempt to register without fpga_manager_ops\n"); + return -EINVAL; + } + + if (!name || !strlen(name)) { + dev_err(dev, "Attempt to register with no name!\n"); + return -EINVAL; + } + + mgr = kzalloc(sizeof(*mgr), GFP_KERNEL); + if (!mgr) + return -ENOMEM; + + id = ida_simple_get(&fpga_mgr_ida, 0, 0, GFP_KERNEL); + if (id < 0) { + ret = id; + goto error_kfree; + } + + mutex_init(&mgr->ref_mutex); + + mgr->name = name; + mgr->mops = mops; + mgr->priv = priv; + + /* + * Initialize framework state by requesting low level driver read state + * from device. FPGA may be in reset mode or may have been programmed + * by bootloader or EEPROM. + */ + mgr->state = mgr->mops->state(mgr); + + device_initialize(&mgr->dev); + mgr->dev.class = fpga_mgr_class; + mgr->dev.parent = dev; + mgr->dev.of_node = dev->of_node; + mgr->dev.id = id; + dev_set_drvdata(dev, mgr); + + dt_label = of_get_property(mgr->dev.of_node, "label", NULL); + if (dt_label) + ret = dev_set_name(&mgr->dev, "%s", dt_label); + else + ret = dev_set_name(&mgr->dev, "fpga%d", id); + + ret = device_add(&mgr->dev); + if (ret) + goto error_device; + + dev_info(&mgr->dev, "%s registered\n", mgr->name); + + return 0; + +error_device: + ida_simple_remove(&fpga_mgr_ida, id); +error_kfree: + kfree(mgr); + + return ret; +} +EXPORT_SYMBOL_GPL(fpga_mgr_register); + +/** + * fpga_mgr_unregister - unregister a low level fpga manager driver + * @dev: fpga manager device from pdev + */ +void fpga_mgr_unregister(struct device *dev) +{ + struct fpga_manager *mgr = dev_get_drvdata(dev); + + dev_info(&mgr->dev, "%s %s\n", __func__, mgr->name); + + /* + * If the low level driver provides a method for putting fpga into + * a desired state upon unregister, do it. + */ + if (mgr->mops->fpga_remove) + mgr->mops->fpga_remove(mgr); + + device_unregister(&mgr->dev); +} +EXPORT_SYMBOL_GPL(fpga_mgr_unregister); + +static void fpga_mgr_dev_release(struct device *dev) +{ + struct fpga_manager *mgr = to_fpga_manager(dev); + + ida_simple_remove(&fpga_mgr_ida, mgr->dev.id); + kfree(mgr); +} + +static int __init fpga_mgr_class_init(void) +{ + pr_info("FPGA manager framework\n"); + + fpga_mgr_class = class_create(THIS_MODULE, "fpga_manager"); + if (IS_ERR(fpga_mgr_class)) + return PTR_ERR(fpga_mgr_class); + + fpga_mgr_class->dev_groups = fpga_mgr_groups; + fpga_mgr_class->dev_release = fpga_mgr_dev_release; + + return 0; +} + +static void __exit fpga_mgr_class_exit(void) +{ + class_destroy(fpga_mgr_class); + ida_destroy(&fpga_mgr_ida); +} + +MODULE_AUTHOR("Alan Tull "); +MODULE_DESCRIPTION("FPGA manager framework"); +MODULE_LICENSE("GPL v2"); + +subsys_initcall(fpga_mgr_class_init); +module_exit(fpga_mgr_class_exit); diff --git a/include/linux/fpga/fpga-mgr.h b/include/linux/fpga/fpga-mgr.h new file mode 100644 index 000000000000..0940bf45e2f2 --- /dev/null +++ b/include/linux/fpga/fpga-mgr.h @@ -0,0 +1,127 @@ +/* + * FPGA Framework + * + * Copyright (C) 2013-2015 Altera Corporation + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ +#include +#include + +#ifndef _LINUX_FPGA_MGR_H +#define _LINUX_FPGA_MGR_H + +struct fpga_manager; + +/** + * enum fpga_mgr_states - fpga framework states + * @FPGA_MGR_STATE_UNKNOWN: can't determine state + * @FPGA_MGR_STATE_POWER_OFF: FPGA power is off + * @FPGA_MGR_STATE_POWER_UP: FPGA reports power is up + * @FPGA_MGR_STATE_RESET: FPGA in reset state + * @FPGA_MGR_STATE_FIRMWARE_REQ: firmware request in progress + * @FPGA_MGR_STATE_FIRMWARE_REQ_ERR: firmware request failed + * @FPGA_MGR_STATE_WRITE_INIT: preparing FPGA for programming + * @FPGA_MGR_STATE_WRITE_INIT_ERR: Error during WRITE_INIT stage + * @FPGA_MGR_STATE_WRITE: writing image to FPGA + * @FPGA_MGR_STATE_WRITE_ERR: Error while writing FPGA + * @FPGA_MGR_STATE_WRITE_COMPLETE: Doing post programming steps + * @FPGA_MGR_STATE_WRITE_COMPLETE_ERR: Error during WRITE_COMPLETE + * @FPGA_MGR_STATE_OPERATING: FPGA is programmed and operating + */ +enum fpga_mgr_states { + /* default FPGA states */ + FPGA_MGR_STATE_UNKNOWN, + FPGA_MGR_STATE_POWER_OFF, + FPGA_MGR_STATE_POWER_UP, + FPGA_MGR_STATE_RESET, + + /* getting an image for loading */ + FPGA_MGR_STATE_FIRMWARE_REQ, + FPGA_MGR_STATE_FIRMWARE_REQ_ERR, + + /* write sequence: init, write, complete */ + FPGA_MGR_STATE_WRITE_INIT, + FPGA_MGR_STATE_WRITE_INIT_ERR, + FPGA_MGR_STATE_WRITE, + FPGA_MGR_STATE_WRITE_ERR, + FPGA_MGR_STATE_WRITE_COMPLETE, + FPGA_MGR_STATE_WRITE_COMPLETE_ERR, + + /* fpga is programmed and operating */ + FPGA_MGR_STATE_OPERATING, +}; + +/* + * FPGA Manager flags + * FPGA_MGR_PARTIAL_RECONFIG: do partial reconfiguration if supported + */ +#define FPGA_MGR_PARTIAL_RECONFIG BIT(0) + +/** + * struct fpga_manager_ops - ops for low level fpga manager drivers + * @state: returns an enum value of the FPGA's state + * @write_init: prepare the FPGA to receive confuration data + * @write: write count bytes of configuration data to the FPGA + * @write_complete: set FPGA to operating state after writing is done + * @fpga_remove: optional: Set FPGA into a specific state during driver remove + * + * fpga_manager_ops are the low level functions implemented by a specific + * fpga manager driver. The optional ones are tested for NULL before being + * called, so leaving them out is fine. + */ +struct fpga_manager_ops { + enum fpga_mgr_states (*state)(struct fpga_manager *mgr); + int (*write_init)(struct fpga_manager *mgr, u32 flags, + const char *buf, size_t count); + int (*write)(struct fpga_manager *mgr, const char *buf, size_t count); + int (*write_complete)(struct fpga_manager *mgr, u32 flags); + void (*fpga_remove)(struct fpga_manager *mgr); +}; + +/** + * struct fpga_manager - fpga manager structure + * @name: name of low level fpga manager + * @dev: fpga manager device + * @ref_mutex: only allows one reference to fpga manager + * @state: state of fpga manager + * @mops: pointer to struct of fpga manager ops + * @priv: low level driver private date + */ +struct fpga_manager { + const char *name; + struct device dev; + struct mutex ref_mutex; + enum fpga_mgr_states state; + const struct fpga_manager_ops *mops; + void *priv; +}; + +#define to_fpga_manager(d) container_of(d, struct fpga_manager, dev) + +int fpga_mgr_buf_load(struct fpga_manager *mgr, u32 flags, + const char *buf, size_t count); + +int fpga_mgr_firmware_load(struct fpga_manager *mgr, u32 flags, + const char *image_name); + +struct fpga_manager *of_fpga_mgr_get(struct device_node *node); + +void fpga_mgr_put(struct fpga_manager *mgr); + +int fpga_mgr_register(struct device *dev, const char *name, + const struct fpga_manager_ops *mops, void *priv); + +void fpga_mgr_unregister(struct device *dev); + +#endif /*_LINUX_FPGA_MGR_H */ -- cgit v1.2.3-58-ga151