* [nomadik:gemini-ethernet 9/9] gemini.c:undefined reference to `devm_ioremap_resource'
From: kbuild test robot @ 2018-01-13 2:53 UTC (permalink / raw)
To: linux-arm-kernel
tree: https://git.kernel.org/pub/scm/linux/kernel/git/linusw/linux-nomadik.git gemini-ethernet
head: c5fab9815a7a1857155a7911fbce85d1af11ab8f
commit: c5fab9815a7a1857155a7911fbce85d1af11ab8f [9/9] net: ethernet: Add a driver for Gemini gigabit ethernet
config: um-allyesconfig (attached as .config)
compiler: gcc-7 (Debian 7.2.0-12) 7.2.1 20171025
reproduce:
git checkout c5fab9815a7a1857155a7911fbce85d1af11ab8f
# save the attached .config to linux build tree
make ARCH=um
All errors (new ones prefixed by >>):
arch/um/drivers/vde.o: In function `vde_open_real':
(.text+0x951): warning: Using 'getgrnam' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
(.text+0x79c): warning: Using 'getpwuid' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
(.text+0xab5): warning: Using 'getaddrinfo' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
arch/um/drivers/pcap.o: In function `pcap_nametoaddr':
(.text+0xdee5): warning: Using 'gethostbyname' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
arch/um/drivers/pcap.o: In function `pcap_nametonetaddr':
(.text+0xdf85): warning: Using 'getnetbyname' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
arch/um/drivers/pcap.o: In function `pcap_nametoproto':
(.text+0xe1a5): warning: Using 'getprotobyname' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
arch/um/drivers/pcap.o: In function `pcap_nametoport':
(.text+0xdfd7): warning: Using 'getservbyname' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
drivers/net/ethernet/cortina/gemini.o: In function `geth_cleanup_freeq':
>> gemini.c:(.text+0x97d): undefined reference to `bad_dma_ops'
gemini.c:(.text+0xad5): undefined reference to `bad_dma_ops'
gemini.c:(.text+0xb85): undefined reference to `bad_dma_ops'
drivers/net/ethernet/cortina/gemini.o: In function `gemini_ethernet_probe':
>> gemini.c:(.text+0xd2b): undefined reference to `devm_ioremap_resource'
drivers/net/ethernet/cortina/gemini.o: In function `geth_freeq_alloc_map_page':
gemini.c:(.text+0x11c4): undefined reference to `bad_dma_ops'
gemini.c:(.text+0x11d0): undefined reference to `bad_dma_ops'
gemini.c:(.text+0x13dc): undefined reference to `bad_dma_ops'
drivers/net/ethernet/cortina/gemini.o: In function `geth_resize_freeq':
gemini.c:(.text+0x1ead): undefined reference to `bad_dma_ops'
gemini.c:(.text+0x20a4): undefined reference to `bad_dma_ops'
drivers/net/ethernet/cortina/gemini.o:gemini.c:(.text+0x2247): more undefined references to `bad_dma_ops' follow
drivers/net/ethernet/cortina/gemini.o: In function `gemini_ethernet_port_probe':
gemini.c:(.text+0x2dec): undefined reference to `devm_ioremap_resource'
gemini.c:(.text+0x2e4c): undefined reference to `devm_ioremap_resource'
drivers/net/ethernet/cortina/gemini.o: In function `gmac_map_tx_bufs.isra.0':
gemini.c:(.text+0x37fb): undefined reference to `bad_dma_ops'
gemini.c:(.text+0x3813): undefined reference to `bad_dma_ops'
gemini.c:(.text+0x393a): undefined reference to `bad_dma_ops'
drivers/net/ethernet/cortina/gemini.o: In function `gmac_cleanup_rxq':
gemini.c:(.text+0x3ffa): undefined reference to `bad_dma_ops'
gemini.c:(.text+0x409d): undefined reference to `bad_dma_ops'
drivers/net/ethernet/cortina/gemini.o:gemini.c:(.text+0x4245): more undefined references to `bad_dma_ops' follow
collect2: error: ld returned 1 exit status
---
0-DAY kernel test infrastructure Open Source Technology Center
https://lists.01.org/pipermail/kbuild-all Intel Corporation
-------------- next part --------------
A non-text attachment was scrubbed...
Name: .config.gz
Type: application/gzip
Size: 19820 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20180113/798eda0e/attachment-0001.gz>
^ permalink raw reply
* [PATCH v5 02/44] clk: davinci: New driver for davinci PLL clocks
From: David Lechner @ 2018-01-13 2:13 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <7fc8bd35-0416-216d-8e1e-53a0ec6e8790@ti.com>
On 01/12/2018 03:21 AM, Sekhar Nori wrote:
> On Monday 08 January 2018 07:47 AM, David Lechner wrote:
>> +static unsigned long davinci_pll_clk_recalc(struct clk_hw *hw,
>> + unsigned long parent_rate)
>> +{
>> + struct davinci_pll_clk *pll = to_davinci_pll_clk(hw);
>> + unsigned long rate = parent_rate;
>> + u32 prediv, mult, postdiv;
>> +
>> + prediv = readl(pll->base + PREDIV) & PREDIV_RATIO_MASK;
>> + mult = readl(pll->base + PLLM) & PLLM_MASK;
>> + postdiv = readl(pll->base + POSTDIV) & POSTDIV_RATIO_MASK;
>
> Shouldn't we check if the pre and post dividers are enabled before using
> them?
I dug into this and the answer is no. The enable bit acts like a gate, not
a bypass, so it does not affect the rate calculation.
^ permalink raw reply
* [PATCH v5] perf tools: Add ARM Statistical Profiling Extensions (SPE) support
From: Kim Phillips @ 2018-01-13 1:27 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20180112141423.GA9655@kernel.org>
'perf record' and 'perf report --dump-raw-trace' supported in this
release.
Example usage:
# perf record -e arm_spe/ts_enable=1,pa_enable=1/ dd if=/dev/zero of=/dev/null count=10000
# perf report --dump-raw-trace
Note that the perf.data file is portable, so the report can be run on
another architecture host if necessary.
Output will contain raw SPE data and its textual representation, such
as:
0x5c8 [0x30]: PERF_RECORD_AUXTRACE size: 0x200000 offset: 0 ref: 0x1891ad0e idx: 1 tid: 2227 cpu: 1
.
. ... ARM SPE data: size 2097152 bytes
. 00000000: 49 00 LD
. 00000002: b2 c0 3b 29 0f 00 00 ff ff VA 0xffff00000f293bc0
. 0000000b: b3 c0 eb 24 fb 00 00 00 80 PA 0xfb24ebc0 ns=1
. 00000014: 9a 00 00 LAT 0 XLAT
. 00000017: 42 16 EV RETIRED L1D-ACCESS TLB-ACCESS
. 00000019: b0 00 c4 15 08 00 00 ff ff PC 0xff00000815c400 el3 ns=1
. 00000022: 98 00 00 LAT 0 TOT
. 00000025: 71 36 6c 21 2c 09 00 00 00 TS 39395093558
. 0000002e: 49 00 LD
. 00000030: b2 80 3c 29 0f 00 00 ff ff VA 0xffff00000f293c80
. 00000039: b3 80 ec 24 fb 00 00 00 80 PA 0xfb24ec80 ns=1
. 00000042: 9a 00 00 LAT 0 XLAT
. 00000045: 42 16 EV RETIRED L1D-ACCESS TLB-ACCESS
. 00000047: b0 f4 11 16 08 00 00 ff ff PC 0xff0000081611f4 el3 ns=1
. 00000050: 98 00 00 LAT 0 TOT
. 00000053: 71 36 6c 21 2c 09 00 00 00 TS 39395093558
. 0000005c: 48 00 INSN-OTHER
. 0000005e: 42 02 EV RETIRED
. 00000060: b0 2c ef 7f 08 00 00 ff ff PC 0xff0000087fef2c el3 ns=1
. 00000069: 98 00 00 LAT 0 TOT
. 0000006c: 71 d1 6f 21 2c 09 00 00 00 TS 39395094481
...
Other release notes:
- applies to acme's perf/{core,urgent} branches, likely elsewhere
- Report is self-contained within the tool.
Record requires enabling the kernel SPE driver by
setting CONFIG_ARM_SPE_PMU.
- The intel-bts implementation was used as a starting point; its
min/default/max buffer sizes and power of 2 pages granularity need to be
revisited for ARM SPE
- Recording across multiple SPE clusters/domains not supported
- Snapshot support (record -S), and conversion to native perf events
(e.g., via 'perf inject --itrace'), are also not supported
- Technically both cs-etm and spe can be used simultaneously, however
disabled for simplicity in this release
Reviewed-by: Dongjiu Geng <gengdongjiu@huawei.com>
Acked-by: Adrian Hunter <adrian.hunter@intel.com>
Signed-off-by: Kim Phillips <kim.phillips@arm.com>
---
v5: added Geng Dongjiu's reviewed-by, addressed Adrian's comments (thanks!!):
- Codingstyle fix for perf_pmu__get_default_config()
- fixed building for aarch32 (ARCH=arm):
- protected arm_spe_pmu_default_config and arm_spe_pmu_default_config
callsites with __aarch64__ (SPE is not architecturally defined for
Aarch32).
- changed arm_spe_get_alignment()'s uint64_t cast to uintptr_t
- SPDX License identifiers for new files
- removed unneeded #include tsc.h
- removed unused struct auxtrace_record and struct perf_tool decls
v4: rebased onto acme's perf/core, whitespace fixes.
v3: trying to address comments from v2:
- despite adding a find_all_arm_spe_pmus() function to scan for all
arm_spe_<n> device instances, in order to ensure auxtrace_record__init
successfully matches the evsel type with the correct arm_spe_pmu type,
I am still having trouble running in multi-SPE PPI (heterogeneous)
environments (mmap fails with EOPNOTSUPP, as does running with
--per-thread on homogeneous systems).
- arm_spe_reference: use gettime instead of direct cntvct register access
- spe-decoder: add a comment for why SPE_EVENTS code sets packet->index.
- added arm_spe_pmu_default_config that accesses the driver
caps/min_interval and sets the default sampling period to it. This way
users don't have to specify -c explicitly. Also set is_uncore to false.
- set more sampling bits in the arm_spe and its tracking evsel. Still
unsure if too liberal, and not sure whether it needs another context
switch tracking evsel. Comments welcome!
- https://www.spinics.net/lists/arm-kernel/msg614361.html
v2: mostly addressing Mark Rutland's comments as much as possible without his
feedback to my feedback:
- decoder refactored with a get_payload, not extended to with-ext_len ones like
get_addr, named the constants
- 0x-ified %x output formats, but decided to not sign extend the addresses in
the raw dump, rather do so if necessary in the synthesis stage:
SPE implementations differ in this area, and raw dump should reflect that.
- CPU mask / new record behaviour bisected to commit e3ba76deef23064 "perf
tools: Force uncore events to system wide monitoring". Waiting to hear back
on why driver can't do system wide monitoring, even across PPIs, by e.g.,
sharing the SPE interrupts in one handler (SPE's don't differ in this record
regard).
- addressed off-list comment from M. Williams:
"Instruction Type" packet was renamed as "Operation Type".
so in the spe packet decoder: INSN_TYPE -> OP_TYPE
- do_get_packet fixed to handle excessive, successive PADding from a new source
of raw SPE data, so instead of:
. 000011ae: 00 PAD
. 000011af: 00 PAD
. 000011b0: 00 PAD
. 000011b1: 00 PAD
. 000011b2: 00 PAD
. 000011b3: 00 PAD
. 000011b4: 00 PAD
. 000011b5: 00 PAD
. 000011b6: 00 PAD
we now get:
. 000011ae: 00 00 00 00 00 00 00 00 00 PAD
- fixed 52 00 00 decoded with an empty events clause, adding 'EV' for all events
clauses now. parser writers can detect for empty event clauses by finding
nothing after it.
tools/perf/arch/arm/util/auxtrace.c | 77 +++++-
tools/perf/arch/arm/util/pmu.c | 6 +
tools/perf/arch/arm64/util/Build | 3 +-
tools/perf/arch/arm64/util/arm-spe.c | 225 +++++++++++++++++
tools/perf/util/Build | 2 +
tools/perf/util/arm-spe-pkt-decoder.c | 462 ++++++++++++++++++++++++++++++++++
tools/perf/util/arm-spe-pkt-decoder.h | 43 ++++
tools/perf/util/arm-spe.c | 309 +++++++++++++++++++++++
tools/perf/util/arm-spe.h | 31 +++
tools/perf/util/auxtrace.c | 3 +
tools/perf/util/auxtrace.h | 1 +
11 files changed, 1155 insertions(+), 7 deletions(-)
create mode 100644 tools/perf/arch/arm64/util/arm-spe.c
create mode 100644 tools/perf/util/arm-spe-pkt-decoder.c
create mode 100644 tools/perf/util/arm-spe-pkt-decoder.h
create mode 100644 tools/perf/util/arm-spe.c
create mode 100644 tools/perf/util/arm-spe.h
diff --git a/tools/perf/arch/arm/util/auxtrace.c b/tools/perf/arch/arm/util/auxtrace.c
index 8edf2cb71564..2323581b157d 100644
--- a/tools/perf/arch/arm/util/auxtrace.c
+++ b/tools/perf/arch/arm/util/auxtrace.c
@@ -22,6 +22,42 @@
#include "../../util/evlist.h"
#include "../../util/pmu.h"
#include "cs-etm.h"
+#include "arm-spe.h"
+
+static struct perf_pmu **find_all_arm_spe_pmus(int *nr_spes, int *err)
+{
+ struct perf_pmu **arm_spe_pmus = NULL;
+ int ret, i, nr_cpus = sysconf(_SC_NPROCESSORS_CONF);
+ /* arm_spe_xxxxxxxxx\0 */
+ char arm_spe_pmu_name[sizeof(ARM_SPE_PMU_NAME) + 10];
+
+ arm_spe_pmus = zalloc(sizeof(struct perf_pmu *) * nr_cpus);
+ if (!arm_spe_pmus) {
+ pr_err("spes alloc failed\n");
+ *err = -ENOMEM;
+ return NULL;
+ }
+
+ for (i = 0; i < nr_cpus; i++) {
+ ret = sprintf(arm_spe_pmu_name, "%s%d", ARM_SPE_PMU_NAME, i);
+ if (ret < 0) {
+ pr_err("sprintf failed\n");
+ *err = -ENOMEM;
+ return NULL;
+ }
+
+ arm_spe_pmus[*nr_spes] = perf_pmu__find(arm_spe_pmu_name);
+ if (arm_spe_pmus[*nr_spes]) {
+ pr_debug2("%s %d: arm_spe_pmu %d type %d name %s\n",
+ __func__, __LINE__, *nr_spes,
+ arm_spe_pmus[*nr_spes]->type,
+ arm_spe_pmus[*nr_spes]->name);
+ (*nr_spes)++;
+ }
+ }
+
+ return arm_spe_pmus;
+}
struct auxtrace_record
*auxtrace_record__init(struct perf_evlist *evlist, int *err)
@@ -29,22 +65,51 @@ struct auxtrace_record
struct perf_pmu *cs_etm_pmu;
struct perf_evsel *evsel;
bool found_etm = false;
+ bool found_spe = false;
+ static struct perf_pmu **arm_spe_pmus = NULL;
+ static int nr_spes = 0;
+ int i;
+
+ if (!evlist)
+ return NULL;
cs_etm_pmu = perf_pmu__find(CORESIGHT_ETM_PMU_NAME);
- if (evlist) {
- evlist__for_each_entry(evlist, evsel) {
- if (cs_etm_pmu &&
- evsel->attr.type == cs_etm_pmu->type)
- found_etm = true;
+ if (!arm_spe_pmus)
+ arm_spe_pmus = find_all_arm_spe_pmus(&nr_spes, err);
+
+ evlist__for_each_entry(evlist, evsel) {
+ if (cs_etm_pmu &&
+ evsel->attr.type == cs_etm_pmu->type)
+ found_etm = true;
+
+ if (!nr_spes)
+ continue;
+
+ for (i = 0; i < nr_spes; i++) {
+ if (evsel->attr.type == arm_spe_pmus[i]->type) {
+ found_spe = true;
+ break;
+ }
}
}
+ if (found_etm && found_spe) {
+ pr_err("Concurrent ARM Coresight ETM and SPE operation not currently supported\n");
+ *err = -EOPNOTSUPP;
+ return NULL;
+ }
+
if (found_etm)
return cs_etm_record_init(err);
+#if defined(__aarch64__)
+ if (found_spe)
+ return arm_spe_recording_init(err, arm_spe_pmus[i]);
+#endif
+
/*
- * Clear 'err' even if we haven't found a cs_etm event - that way perf
+ * Clear 'err' even if we haven't found an event - that way perf
* record can still be used even if tracers aren't present. The NULL
* return value will take care of telling the infrastructure HW tracing
* isn't available.
diff --git a/tools/perf/arch/arm/util/pmu.c b/tools/perf/arch/arm/util/pmu.c
index 98d67399a0d6..ac4dffc807b8 100644
--- a/tools/perf/arch/arm/util/pmu.c
+++ b/tools/perf/arch/arm/util/pmu.c
@@ -20,6 +20,7 @@
#include <linux/perf_event.h>
#include "cs-etm.h"
+#include "arm-spe.h"
#include "../../util/pmu.h"
struct perf_event_attr
@@ -30,7 +31,12 @@ struct perf_event_attr
/* add ETM default config here */
pmu->selectable = true;
pmu->set_drv_config = cs_etm_set_drv_config;
+#if defined(__aarch64__)
+ } else if (strstarts(pmu->name, ARM_SPE_PMU_NAME)) {
+ return arm_spe_pmu_default_config(pmu);
+#endif
}
+
#endif
return NULL;
}
diff --git a/tools/perf/arch/arm64/util/Build b/tools/perf/arch/arm64/util/Build
index cef6fb38d17e..f9969bb88ccb 100644
--- a/tools/perf/arch/arm64/util/Build
+++ b/tools/perf/arch/arm64/util/Build
@@ -3,4 +3,5 @@ libperf-$(CONFIG_LOCAL_LIBUNWIND) += unwind-libunwind.o
libperf-$(CONFIG_AUXTRACE) += ../../arm/util/pmu.o \
../../arm/util/auxtrace.o \
- ../../arm/util/cs-etm.o
+ ../../arm/util/cs-etm.o \
+ arm-spe.o
diff --git a/tools/perf/arch/arm64/util/arm-spe.c b/tools/perf/arch/arm64/util/arm-spe.c
new file mode 100644
index 000000000000..1120e39c1b00
--- /dev/null
+++ b/tools/perf/arch/arm64/util/arm-spe.c
@@ -0,0 +1,225 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Arm Statistical Profiling Extensions (SPE) support
+ * Copyright (c) 2017-2018, Arm Ltd.
+ */
+
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/bitops.h>
+#include <linux/log2.h>
+#include <time.h>
+
+#include "../../util/cpumap.h"
+#include "../../util/evsel.h"
+#include "../../util/evlist.h"
+#include "../../util/session.h"
+#include "../../util/util.h"
+#include "../../util/pmu.h"
+#include "../../util/debug.h"
+#include "../../util/auxtrace.h"
+#include "../../util/arm-spe.h"
+
+#define KiB(x) ((x) * 1024)
+#define MiB(x) ((x) * 1024 * 1024)
+
+struct arm_spe_recording {
+ struct auxtrace_record itr;
+ struct perf_pmu *arm_spe_pmu;
+ struct perf_evlist *evlist;
+};
+
+static size_t
+arm_spe_info_priv_size(struct auxtrace_record *itr __maybe_unused,
+ struct perf_evlist *evlist __maybe_unused)
+{
+ return ARM_SPE_AUXTRACE_PRIV_SIZE;
+}
+
+static int arm_spe_info_fill(struct auxtrace_record *itr,
+ struct perf_session *session,
+ struct auxtrace_info_event *auxtrace_info,
+ size_t priv_size)
+{
+ struct arm_spe_recording *sper =
+ container_of(itr, struct arm_spe_recording, itr);
+ struct perf_pmu *arm_spe_pmu = sper->arm_spe_pmu;
+
+ if (priv_size != ARM_SPE_AUXTRACE_PRIV_SIZE)
+ return -EINVAL;
+
+ if (!session->evlist->nr_mmaps)
+ return -EINVAL;
+
+ auxtrace_info->type = PERF_AUXTRACE_ARM_SPE;
+ auxtrace_info->priv[ARM_SPE_PMU_TYPE] = arm_spe_pmu->type;
+
+ return 0;
+}
+
+static int arm_spe_recording_options(struct auxtrace_record *itr,
+ struct perf_evlist *evlist,
+ struct record_opts *opts)
+{
+ struct arm_spe_recording *sper =
+ container_of(itr, struct arm_spe_recording, itr);
+ struct perf_pmu *arm_spe_pmu = sper->arm_spe_pmu;
+ struct perf_evsel *evsel, *arm_spe_evsel = NULL;
+ bool privileged = geteuid() == 0 || perf_event_paranoid() < 0;
+ struct perf_evsel *tracking_evsel;
+ int err;
+
+ sper->evlist = evlist;
+
+ evlist__for_each_entry(evlist, evsel) {
+ if (evsel->attr.type == arm_spe_pmu->type) {
+ if (arm_spe_evsel) {
+ pr_err("There may be only one " ARM_SPE_PMU_NAME "x event\n");
+ return -EINVAL;
+ }
+ evsel->attr.freq = 0;
+ evsel->attr.sample_period = 1;
+ arm_spe_evsel = evsel;
+ opts->full_auxtrace = true;
+ }
+ }
+
+ if (!opts->full_auxtrace)
+ return 0;
+
+ /* We are in full trace mode but '-m,xyz' wasn't specified */
+ if (opts->full_auxtrace && !opts->auxtrace_mmap_pages) {
+ if (privileged) {
+ opts->auxtrace_mmap_pages = MiB(4) / page_size;
+ } else {
+ opts->auxtrace_mmap_pages = KiB(128) / page_size;
+ if (opts->mmap_pages == UINT_MAX)
+ opts->mmap_pages = KiB(256) / page_size;
+ }
+ }
+
+ /* Validate auxtrace_mmap_pages */
+ if (opts->auxtrace_mmap_pages) {
+ size_t sz = opts->auxtrace_mmap_pages * (size_t)page_size;
+ size_t min_sz = KiB(8);
+
+ if (sz < min_sz || !is_power_of_2(sz)) {
+ pr_err("Invalid mmap size for ARM SPE: must be@least %zuKiB and a power of 2\n",
+ min_sz / 1024);
+ return -EINVAL;
+ }
+ }
+
+
+ /*
+ * To obtain the auxtrace buffer file descriptor, the auxtrace event
+ * must come first.
+ */
+ perf_evlist__to_front(evlist, arm_spe_evsel);
+
+ perf_evsel__set_sample_bit(arm_spe_evsel, CPU);
+ perf_evsel__set_sample_bit(arm_spe_evsel, TIME);
+ perf_evsel__set_sample_bit(arm_spe_evsel, TID);
+
+ /* Add dummy event to keep tracking */
+ err = parse_events(evlist, "dummy:u", NULL);
+ if (err)
+ return err;
+
+ tracking_evsel = perf_evlist__last(evlist);
+ perf_evlist__set_tracking_event(evlist, tracking_evsel);
+
+ tracking_evsel->attr.freq = 0;
+ tracking_evsel->attr.sample_period = 1;
+ perf_evsel__set_sample_bit(tracking_evsel, TIME);
+ perf_evsel__set_sample_bit(tracking_evsel, CPU);
+ perf_evsel__reset_sample_bit(tracking_evsel, BRANCH_STACK);
+
+ return 0;
+}
+
+static u64 arm_spe_reference(struct auxtrace_record *itr __maybe_unused)
+{
+ struct timespec ts;
+
+ clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
+
+ return ts.tv_sec ^ ts.tv_nsec;
+}
+
+static void arm_spe_recording_free(struct auxtrace_record *itr)
+{
+ struct arm_spe_recording *sper =
+ container_of(itr, struct arm_spe_recording, itr);
+
+ free(sper);
+}
+
+static int arm_spe_read_finish(struct auxtrace_record *itr, int idx)
+{
+ struct arm_spe_recording *sper =
+ container_of(itr, struct arm_spe_recording, itr);
+ struct perf_evsel *evsel;
+
+ evlist__for_each_entry(sper->evlist, evsel) {
+ if (evsel->attr.type == sper->arm_spe_pmu->type)
+ return perf_evlist__enable_event_idx(sper->evlist,
+ evsel, idx);
+ }
+ return -EINVAL;
+}
+
+struct auxtrace_record *arm_spe_recording_init(int *err,
+ struct perf_pmu *arm_spe_pmu)
+{
+ struct arm_spe_recording *sper;
+
+ if (!arm_spe_pmu) {
+ *err = -ENODEV;
+ return NULL;
+ }
+
+ sper = zalloc(sizeof(struct arm_spe_recording));
+ if (!sper) {
+ *err = -ENOMEM;
+ return NULL;
+ }
+
+ sper->arm_spe_pmu = arm_spe_pmu;
+ sper->itr.recording_options = arm_spe_recording_options;
+ sper->itr.info_priv_size = arm_spe_info_priv_size;
+ sper->itr.info_fill = arm_spe_info_fill;
+ sper->itr.free = arm_spe_recording_free;
+ sper->itr.reference = arm_spe_reference;
+ sper->itr.read_finish = arm_spe_read_finish;
+ sper->itr.alignment = 0;
+
+ return &sper->itr;
+}
+
+struct perf_event_attr
+*arm_spe_pmu_default_config(struct perf_pmu *arm_spe_pmu)
+{
+ struct perf_event_attr *attr;
+
+ attr = zalloc(sizeof(struct perf_event_attr));
+ if (!attr) {
+ pr_err("arm_spe default config cannot allocate a perf_event_attr\n");
+ return NULL;
+ }
+
+ /*
+ * If kernel driver doesn't advertise a minimum,
+ * use max allowable by PMSIDR_EL1.INTERVAL
+ */
+ if (perf_pmu__scan_file(arm_spe_pmu, "caps/min_interval", "%llu",
+ &attr->sample_period) != 1) {
+ pr_debug("arm_spe driver doesn't advertise a min. interval. Using 4096\n");
+ attr->sample_period = 4096;
+ }
+
+ arm_spe_pmu->selectable = true;
+ arm_spe_pmu->is_uncore = false;
+
+ return attr;
+}
diff --git a/tools/perf/util/Build b/tools/perf/util/Build
index a3de7916fe63..7c6a8b461e24 100644
--- a/tools/perf/util/Build
+++ b/tools/perf/util/Build
@@ -86,6 +86,8 @@ libperf-$(CONFIG_AUXTRACE) += auxtrace.o
libperf-$(CONFIG_AUXTRACE) += intel-pt-decoder/
libperf-$(CONFIG_AUXTRACE) += intel-pt.o
libperf-$(CONFIG_AUXTRACE) += intel-bts.o
+libperf-$(CONFIG_AUXTRACE) += arm-spe.o
+libperf-$(CONFIG_AUXTRACE) += arm-spe-pkt-decoder.o
libperf-y += parse-branch-options.o
libperf-y += dump-insn.o
libperf-y += parse-regs-options.o
diff --git a/tools/perf/util/arm-spe-pkt-decoder.c b/tools/perf/util/arm-spe-pkt-decoder.c
new file mode 100644
index 000000000000..061d168dcdf5
--- /dev/null
+++ b/tools/perf/util/arm-spe-pkt-decoder.c
@@ -0,0 +1,462 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Arm Statistical Profiling Extensions (SPE) support
+ * Copyright (c) 2017-2018, Arm Ltd.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <endian.h>
+#include <byteswap.h>
+
+#include "arm-spe-pkt-decoder.h"
+
+#define BIT(n) (1ULL << (n))
+
+#define NS_FLAG BIT(63)
+#define EL_FLAG (BIT(62) | BIT(61))
+
+#define SPE_HEADER0_PAD 0x0
+#define SPE_HEADER0_END 0x1
+#define SPE_HEADER0_ADDRESS 0x30 /* address packet (short) */
+#define SPE_HEADER0_ADDRESS_MASK 0x38
+#define SPE_HEADER0_COUNTER 0x18 /* counter packet (short) */
+#define SPE_HEADER0_COUNTER_MASK 0x38
+#define SPE_HEADER0_TIMESTAMP 0x71
+#define SPE_HEADER0_TIMESTAMP 0x71
+#define SPE_HEADER0_EVENTS 0x2
+#define SPE_HEADER0_EVENTS_MASK 0xf
+#define SPE_HEADER0_SOURCE 0x3
+#define SPE_HEADER0_SOURCE_MASK 0xf
+#define SPE_HEADER0_CONTEXT 0x24
+#define SPE_HEADER0_CONTEXT_MASK 0x3c
+#define SPE_HEADER0_OP_TYPE 0x8
+#define SPE_HEADER0_OP_TYPE_MASK 0x3c
+#define SPE_HEADER1_ALIGNMENT 0x0
+#define SPE_HEADER1_ADDRESS 0xb0 /* address packet (extended) */
+#define SPE_HEADER1_ADDRESS_MASK 0xf8
+#define SPE_HEADER1_COUNTER 0x98 /* counter packet (extended) */
+#define SPE_HEADER1_COUNTER_MASK 0xf8
+
+#if __BYTE_ORDER == __BIG_ENDIAN
+#define le16_to_cpu bswap_16
+#define le32_to_cpu bswap_32
+#define le64_to_cpu bswap_64
+#define memcpy_le64(d, s, n) do { \
+ memcpy((d), (s), (n)); \
+ *(d) = le64_to_cpu(*(d)); \
+} while (0)
+#else
+#define le16_to_cpu
+#define le32_to_cpu
+#define le64_to_cpu
+#define memcpy_le64 memcpy
+#endif
+
+static const char * const arm_spe_packet_name[] = {
+ [ARM_SPE_PAD] = "PAD",
+ [ARM_SPE_END] = "END",
+ [ARM_SPE_TIMESTAMP] = "TS",
+ [ARM_SPE_ADDRESS] = "ADDR",
+ [ARM_SPE_COUNTER] = "LAT",
+ [ARM_SPE_CONTEXT] = "CONTEXT",
+ [ARM_SPE_OP_TYPE] = "OP-TYPE",
+ [ARM_SPE_EVENTS] = "EVENTS",
+ [ARM_SPE_DATA_SOURCE] = "DATA-SOURCE",
+};
+
+const char *arm_spe_pkt_name(enum arm_spe_pkt_type type)
+{
+ return arm_spe_packet_name[type];
+}
+
+/* return ARM SPE payload size from its encoding,
+ * which is in bits 5:4 of the byte.
+ * 00 : byte
+ * 01 : halfword (2)
+ * 10 : word (4)
+ * 11 : doubleword (8)
+ */
+static int payloadlen(unsigned char byte)
+{
+ return 1 << ((byte & 0x30) >> 4);
+}
+
+static int arm_spe_get_payload(const unsigned char *buf, size_t len,
+ struct arm_spe_pkt *packet)
+{
+ size_t payload_len = payloadlen(buf[0]);
+
+ if (len < 1 + payload_len)
+ return ARM_SPE_NEED_MORE_BYTES;
+
+ buf++;
+
+ switch (payload_len) {
+ case 1: packet->payload = *(uint8_t *)buf; break;
+ case 2: packet->payload = le16_to_cpu(*(uint16_t *)buf); break;
+ case 4: packet->payload = le32_to_cpu(*(uint32_t *)buf); break;
+ case 8: packet->payload = le64_to_cpu(*(uint64_t *)buf); break;
+ default: return ARM_SPE_BAD_PACKET;
+ }
+
+ return 1 + payload_len;
+}
+
+static int arm_spe_get_pad(struct arm_spe_pkt *packet)
+{
+ packet->type = ARM_SPE_PAD;
+ return 1;
+}
+
+static int arm_spe_get_alignment(const unsigned char *buf, size_t len,
+ struct arm_spe_pkt *packet)
+{
+ unsigned int alignment = 1 << ((buf[0] & 0xf) + 1);
+
+ if (len < alignment)
+ return ARM_SPE_NEED_MORE_BYTES;
+
+ packet->type = ARM_SPE_PAD;
+ return alignment - (((uintptr_t)buf) & (alignment - 1));
+}
+
+static int arm_spe_get_end(struct arm_spe_pkt *packet)
+{
+ packet->type = ARM_SPE_END;
+ return 1;
+}
+
+static int arm_spe_get_timestamp(const unsigned char *buf, size_t len,
+ struct arm_spe_pkt *packet)
+{
+ packet->type = ARM_SPE_TIMESTAMP;
+ return arm_spe_get_payload(buf, len, packet);
+}
+
+static int arm_spe_get_events(const unsigned char *buf, size_t len,
+ struct arm_spe_pkt *packet)
+{
+ int ret = arm_spe_get_payload(buf, len, packet);
+
+ packet->type = ARM_SPE_EVENTS;
+
+ /* we use index to identify Events with a less number of
+ * comparisons in arm_spe_pkt_desc(): E.g., the LLC-ACCESS,
+ * LLC-REFILL, and REMOTE-ACCESS events are identified iff
+ * index > 1.
+ */
+ packet->index = ret - 1;
+
+ return ret;
+}
+
+static int arm_spe_get_data_source(const unsigned char *buf, size_t len,
+ struct arm_spe_pkt *packet)
+{
+ packet->type = ARM_SPE_DATA_SOURCE;
+ return arm_spe_get_payload(buf, len, packet);
+}
+
+static int arm_spe_get_context(const unsigned char *buf, size_t len,
+ struct arm_spe_pkt *packet)
+{
+ packet->type = ARM_SPE_CONTEXT;
+ packet->index = buf[0] & 0x3;
+
+ return arm_spe_get_payload(buf, len, packet);
+}
+
+static int arm_spe_get_op_type(const unsigned char *buf, size_t len,
+ struct arm_spe_pkt *packet)
+{
+ packet->type = ARM_SPE_OP_TYPE;
+ packet->index = buf[0] & 0x3;
+ return arm_spe_get_payload(buf, len, packet);
+}
+
+static int arm_spe_get_counter(const unsigned char *buf, size_t len,
+ const unsigned char ext_hdr, struct arm_spe_pkt *packet)
+{
+ if (len < 2)
+ return ARM_SPE_NEED_MORE_BYTES;
+
+ packet->type = ARM_SPE_COUNTER;
+ if (ext_hdr)
+ packet->index = ((buf[0] & 0x3) << 3) | (buf[1] & 0x7);
+ else
+ packet->index = buf[0] & 0x7;
+
+ packet->payload = le16_to_cpu(*(uint16_t *)(buf + 1));
+
+ return 1 + ext_hdr + 2;
+}
+
+static int arm_spe_get_addr(const unsigned char *buf, size_t len,
+ const unsigned char ext_hdr, struct arm_spe_pkt *packet)
+{
+ if (len < 8)
+ return ARM_SPE_NEED_MORE_BYTES;
+
+ packet->type = ARM_SPE_ADDRESS;
+ if (ext_hdr)
+ packet->index = ((buf[0] & 0x3) << 3) | (buf[1] & 0x7);
+ else
+ packet->index = buf[0] & 0x7;
+
+ memcpy_le64(&packet->payload, buf + 1, 8);
+
+ return 1 + ext_hdr + 8;
+}
+
+static int arm_spe_do_get_packet(const unsigned char *buf, size_t len,
+ struct arm_spe_pkt *packet)
+{
+ unsigned int byte;
+
+ memset(packet, 0, sizeof(struct arm_spe_pkt));
+
+ if (!len)
+ return ARM_SPE_NEED_MORE_BYTES;
+
+ byte = buf[0];
+ if (byte == SPE_HEADER0_PAD)
+ return arm_spe_get_pad(packet);
+ else if (byte == SPE_HEADER0_END) /* no timestamp at end of record */
+ return arm_spe_get_end(packet);
+ else if (byte & 0xc0 /* 0y11xxxxxx */) {
+ if (byte & 0x80) {
+ if ((byte & SPE_HEADER0_ADDRESS_MASK) == SPE_HEADER0_ADDRESS)
+ return arm_spe_get_addr(buf, len, 0, packet);
+ if ((byte & SPE_HEADER0_COUNTER_MASK) == SPE_HEADER0_COUNTER)
+ return arm_spe_get_counter(buf, len, 0, packet);
+ } else
+ if (byte == SPE_HEADER0_TIMESTAMP)
+ return arm_spe_get_timestamp(buf, len, packet);
+ else if ((byte & SPE_HEADER0_EVENTS_MASK) == SPE_HEADER0_EVENTS)
+ return arm_spe_get_events(buf, len, packet);
+ else if ((byte & SPE_HEADER0_SOURCE_MASK) == SPE_HEADER0_SOURCE)
+ return arm_spe_get_data_source(buf, len, packet);
+ else if ((byte & SPE_HEADER0_CONTEXT_MASK) == SPE_HEADER0_CONTEXT)
+ return arm_spe_get_context(buf, len, packet);
+ else if ((byte & SPE_HEADER0_OP_TYPE_MASK) == SPE_HEADER0_OP_TYPE)
+ return arm_spe_get_op_type(buf, len, packet);
+ } else if ((byte & 0xe0) == 0x20 /* 0y001xxxxx */) {
+ /* 16-bit header */
+ byte = buf[1];
+ if (byte == SPE_HEADER1_ALIGNMENT)
+ return arm_spe_get_alignment(buf, len, packet);
+ else if ((byte & SPE_HEADER1_ADDRESS_MASK) == SPE_HEADER1_ADDRESS)
+ return arm_spe_get_addr(buf, len, 1, packet);
+ else if ((byte & SPE_HEADER1_COUNTER_MASK) == SPE_HEADER1_COUNTER)
+ return arm_spe_get_counter(buf, len, 1, packet);
+ }
+
+ return ARM_SPE_BAD_PACKET;
+}
+
+int arm_spe_get_packet(const unsigned char *buf, size_t len,
+ struct arm_spe_pkt *packet)
+{
+ int ret;
+
+ ret = arm_spe_do_get_packet(buf, len, packet);
+ /* put multiple consecutive PADs on the same line, up to
+ * the fixed-width output format of 16 bytes per line.
+ */
+ if (ret > 0 && packet->type == ARM_SPE_PAD) {
+ while (ret < 16 && len > (size_t)ret && !buf[ret])
+ ret += 1;
+ }
+ return ret;
+}
+
+int arm_spe_pkt_desc(const struct arm_spe_pkt *packet, char *buf,
+ size_t buf_len)
+{
+ int ret, ns, el, index = packet->index;
+ unsigned long long payload = packet->payload;
+ const char *name = arm_spe_pkt_name(packet->type);
+
+ switch (packet->type) {
+ case ARM_SPE_BAD:
+ case ARM_SPE_PAD:
+ case ARM_SPE_END:
+ return snprintf(buf, buf_len, "%s", name);
+ case ARM_SPE_EVENTS: {
+ size_t blen = buf_len;
+
+ ret = 0;
+ ret = snprintf(buf, buf_len, "EV");
+ buf += ret;
+ blen -= ret;
+ if (payload & 0x1) {
+ ret = snprintf(buf, buf_len, " EXCEPTION-GEN");
+ buf += ret;
+ blen -= ret;
+ }
+ if (payload & 0x2) {
+ ret = snprintf(buf, buf_len, " RETIRED");
+ buf += ret;
+ blen -= ret;
+ }
+ if (payload & 0x4) {
+ ret = snprintf(buf, buf_len, " L1D-ACCESS");
+ buf += ret;
+ blen -= ret;
+ }
+ if (payload & 0x8) {
+ ret = snprintf(buf, buf_len, " L1D-REFILL");
+ buf += ret;
+ blen -= ret;
+ }
+ if (payload & 0x10) {
+ ret = snprintf(buf, buf_len, " TLB-ACCESS");
+ buf += ret;
+ blen -= ret;
+ }
+ if (payload & 0x20) {
+ ret = snprintf(buf, buf_len, " TLB-REFILL");
+ buf += ret;
+ blen -= ret;
+ }
+ if (payload & 0x40) {
+ ret = snprintf(buf, buf_len, " NOT-TAKEN");
+ buf += ret;
+ blen -= ret;
+ }
+ if (payload & 0x80) {
+ ret = snprintf(buf, buf_len, " MISPRED");
+ buf += ret;
+ blen -= ret;
+ }
+ if (index > 1) {
+ if (payload & 0x100) {
+ ret = snprintf(buf, buf_len, " LLC-ACCESS");
+ buf += ret;
+ blen -= ret;
+ }
+ if (payload & 0x200) {
+ ret = snprintf(buf, buf_len, " LLC-REFILL");
+ buf += ret;
+ blen -= ret;
+ }
+ if (payload & 0x400) {
+ ret = snprintf(buf, buf_len, " REMOTE-ACCESS");
+ buf += ret;
+ blen -= ret;
+ }
+ }
+ if (ret < 0)
+ return ret;
+ blen -= ret;
+ return buf_len - blen;
+ }
+ case ARM_SPE_OP_TYPE:
+ switch (index) {
+ case 0: return snprintf(buf, buf_len, "%s", payload & 0x1 ?
+ "COND-SELECT" : "INSN-OTHER");
+ case 1: {
+ size_t blen = buf_len;
+
+ if (payload & 0x1)
+ ret = snprintf(buf, buf_len, "ST");
+ else
+ ret = snprintf(buf, buf_len, "LD");
+ buf += ret;
+ blen -= ret;
+ if (payload & 0x2) {
+ if (payload & 0x4) {
+ ret = snprintf(buf, buf_len, " AT");
+ buf += ret;
+ blen -= ret;
+ }
+ if (payload & 0x8) {
+ ret = snprintf(buf, buf_len, " EXCL");
+ buf += ret;
+ blen -= ret;
+ }
+ if (payload & 0x10) {
+ ret = snprintf(buf, buf_len, " AR");
+ buf += ret;
+ blen -= ret;
+ }
+ } else if (payload & 0x4) {
+ ret = snprintf(buf, buf_len, " SIMD-FP");
+ buf += ret;
+ blen -= ret;
+ }
+ if (ret < 0)
+ return ret;
+ blen -= ret;
+ return buf_len - blen;
+ }
+ case 2: {
+ size_t blen = buf_len;
+
+ ret = snprintf(buf, buf_len, "B");
+ buf += ret;
+ blen -= ret;
+ if (payload & 0x1) {
+ ret = snprintf(buf, buf_len, " COND");
+ buf += ret;
+ blen -= ret;
+ }
+ if (payload & 0x2) {
+ ret = snprintf(buf, buf_len, " IND");
+ buf += ret;
+ blen -= ret;
+ }
+ if (ret < 0)
+ return ret;
+ blen -= ret;
+ return buf_len - blen;
+ }
+ default: return 0;
+ }
+ case ARM_SPE_DATA_SOURCE:
+ case ARM_SPE_TIMESTAMP:
+ return snprintf(buf, buf_len, "%s %lld", name, payload);
+ case ARM_SPE_ADDRESS:
+ switch (index) {
+ case 0:
+ case 1: ns = !!(packet->payload & NS_FLAG);
+ el = (packet->payload & EL_FLAG) >> 61;
+ payload &= ~(0xffULL << 56);
+ return snprintf(buf, buf_len, "%s 0x%llx el%d ns=%d",
+ (index == 1) ? "TGT" : "PC", payload, el, ns);
+ case 2: return snprintf(buf, buf_len, "VA 0x%llx", payload);
+ case 3: ns = !!(packet->payload & NS_FLAG);
+ payload &= ~(0xffULL << 56);
+ return snprintf(buf, buf_len, "PA 0x%llx ns=%d",
+ payload, ns);
+ default: return 0;
+ }
+ case ARM_SPE_CONTEXT:
+ return snprintf(buf, buf_len, "%s 0x%lx el%d", name,
+ (unsigned long)payload, index + 1);
+ case ARM_SPE_COUNTER: {
+ size_t blen = buf_len;
+
+ ret = snprintf(buf, buf_len, "%s %d ", name,
+ (unsigned short)payload);
+ buf += ret;
+ blen -= ret;
+ switch (index) {
+ case 0: ret = snprintf(buf, buf_len, "TOT"); break;
+ case 1: ret = snprintf(buf, buf_len, "ISSUE"); break;
+ case 2: ret = snprintf(buf, buf_len, "XLAT"); break;
+ default: ret = 0;
+ }
+ if (ret < 0)
+ return ret;
+ blen -= ret;
+ return buf_len - blen;
+ }
+ default:
+ break;
+ }
+
+ return snprintf(buf, buf_len, "%s 0x%llx (%d)",
+ name, payload, packet->index);
+}
diff --git a/tools/perf/util/arm-spe-pkt-decoder.h b/tools/perf/util/arm-spe-pkt-decoder.h
new file mode 100644
index 000000000000..dfd2aac95d69
--- /dev/null
+++ b/tools/perf/util/arm-spe-pkt-decoder.h
@@ -0,0 +1,43 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Arm Statistical Profiling Extensions (SPE) support
+ * Copyright (c) 2017-2018, Arm Ltd.
+ */
+
+#ifndef INCLUDE__ARM_SPE_PKT_DECODER_H__
+#define INCLUDE__ARM_SPE_PKT_DECODER_H__
+
+#include <stddef.h>
+#include <stdint.h>
+
+#define ARM_SPE_PKT_DESC_MAX 256
+
+#define ARM_SPE_NEED_MORE_BYTES -1
+#define ARM_SPE_BAD_PACKET -2
+
+enum arm_spe_pkt_type {
+ ARM_SPE_BAD,
+ ARM_SPE_PAD,
+ ARM_SPE_END,
+ ARM_SPE_TIMESTAMP,
+ ARM_SPE_ADDRESS,
+ ARM_SPE_COUNTER,
+ ARM_SPE_CONTEXT,
+ ARM_SPE_OP_TYPE,
+ ARM_SPE_EVENTS,
+ ARM_SPE_DATA_SOURCE,
+};
+
+struct arm_spe_pkt {
+ enum arm_spe_pkt_type type;
+ unsigned char index;
+ uint64_t payload;
+};
+
+const char *arm_spe_pkt_name(enum arm_spe_pkt_type);
+
+int arm_spe_get_packet(const unsigned char *buf, size_t len,
+ struct arm_spe_pkt *packet);
+
+int arm_spe_pkt_desc(const struct arm_spe_pkt *packet, char *buf, size_t len);
+#endif
diff --git a/tools/perf/util/arm-spe.c b/tools/perf/util/arm-spe.c
new file mode 100644
index 000000000000..8600a4f26ef5
--- /dev/null
+++ b/tools/perf/util/arm-spe.c
@@ -0,0 +1,309 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Arm Statistical Profiling Extensions (SPE) support
+ * Copyright (c) 2017-2018, Arm Ltd.
+ */
+
+#include <endian.h>
+#include <errno.h>
+#include <byteswap.h>
+#include <inttypes.h>
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/bitops.h>
+#include <linux/log2.h>
+
+#include "cpumap.h"
+#include "color.h"
+#include "evsel.h"
+#include "evlist.h"
+#include "machine.h"
+#include "session.h"
+#include "util.h"
+#include "thread.h"
+#include "debug.h"
+#include "auxtrace.h"
+#include "arm-spe.h"
+#include "arm-spe-pkt-decoder.h"
+
+struct arm_spe {
+ struct auxtrace auxtrace;
+ struct auxtrace_queues queues;
+ struct auxtrace_heap heap;
+ u32 auxtrace_type;
+ struct perf_session *session;
+ struct machine *machine;
+ u32 pmu_type;
+};
+
+struct arm_spe_queue {
+ struct arm_spe *spe;
+ unsigned int queue_nr;
+ struct auxtrace_buffer *buffer;
+ bool on_heap;
+ bool done;
+ pid_t pid;
+ pid_t tid;
+ int cpu;
+};
+
+static void arm_spe_dump(struct arm_spe *spe __maybe_unused,
+ unsigned char *buf, size_t len)
+{
+ struct arm_spe_pkt packet;
+ size_t pos = 0;
+ int ret, pkt_len, i;
+ char desc[ARM_SPE_PKT_DESC_MAX];
+ const char *color = PERF_COLOR_BLUE;
+
+ color_fprintf(stdout, color,
+ ". ... ARM SPE data: size %zu bytes\n",
+ len);
+
+ while (len) {
+ ret = arm_spe_get_packet(buf, len, &packet);
+ if (ret > 0)
+ pkt_len = ret;
+ else
+ pkt_len = 1;
+ printf(".");
+ color_fprintf(stdout, color, " %08x: ", pos);
+ for (i = 0; i < pkt_len; i++)
+ color_fprintf(stdout, color, " %02x", buf[i]);
+ for (; i < 16; i++)
+ color_fprintf(stdout, color, " ");
+ if (ret > 0) {
+ ret = arm_spe_pkt_desc(&packet, desc,
+ ARM_SPE_PKT_DESC_MAX);
+ if (ret > 0)
+ color_fprintf(stdout, color, " %s\n", desc);
+ } else {
+ color_fprintf(stdout, color, " Bad packet!\n");
+ }
+ pos += pkt_len;
+ buf += pkt_len;
+ len -= pkt_len;
+ }
+}
+
+static void arm_spe_dump_event(struct arm_spe *spe, unsigned char *buf,
+ size_t len)
+{
+ printf(".\n");
+ arm_spe_dump(spe, buf, len);
+}
+
+static struct arm_spe_queue *arm_spe_alloc_queue(struct arm_spe *spe,
+ unsigned int queue_nr)
+{
+ struct arm_spe_queue *speq;
+
+ speq = zalloc(sizeof(struct arm_spe_queue));
+ if (!speq)
+ return NULL;
+
+ speq->spe = spe;
+ speq->queue_nr = queue_nr;
+ speq->pid = -1;
+ speq->tid = -1;
+ speq->cpu = -1;
+
+ return speq;
+}
+
+static int arm_spe_setup_queue(struct arm_spe *spe,
+ struct auxtrace_queue *queue,
+ unsigned int queue_nr)
+{
+ struct arm_spe_queue *speq = queue->priv;
+
+ if (list_empty(&queue->head))
+ return 0;
+
+ if (!speq) {
+ speq = arm_spe_alloc_queue(spe, queue_nr);
+ if (!speq)
+ return -ENOMEM;
+ queue->priv = speq;
+
+ if (queue->cpu != -1)
+ speq->cpu = queue->cpu;
+ speq->tid = queue->tid;
+ }
+
+ if (!speq->on_heap && !speq->buffer) {
+ int ret;
+
+ speq->buffer = auxtrace_buffer__next(queue, NULL);
+ if (!speq->buffer)
+ return 0;
+
+ ret = auxtrace_heap__add(&spe->heap, queue_nr,
+ speq->buffer->reference);
+ if (ret)
+ return ret;
+ speq->on_heap = true;
+ }
+
+ return 0;
+}
+
+static int arm_spe_setup_queues(struct arm_spe *spe)
+{
+ unsigned int i;
+ int ret;
+
+ for (i = 0; i < spe->queues.nr_queues; i++) {
+ ret = arm_spe_setup_queue(spe, &spe->queues.queue_array[i],
+ i);
+ if (ret)
+ return ret;
+ }
+ return 0;
+}
+
+static inline int arm_spe_update_queues(struct arm_spe *spe)
+{
+ if (spe->queues.new_data) {
+ spe->queues.new_data = false;
+ return arm_spe_setup_queues(spe);
+ }
+ return 0;
+}
+
+static int arm_spe_process_event(struct perf_session *session __maybe_unused,
+ union perf_event *event __maybe_unused,
+ struct perf_sample *sample __maybe_unused,
+ struct perf_tool *tool __maybe_unused)
+{
+ return 0;
+}
+
+static int arm_spe_process_auxtrace_event(struct perf_session *session,
+ union perf_event *event,
+ struct perf_tool *tool __maybe_unused)
+{
+ struct arm_spe *spe = container_of(session->auxtrace, struct arm_spe,
+ auxtrace);
+ struct auxtrace_buffer *buffer;
+ off_t data_offset;
+ int fd = perf_data__fd(session->data);
+ int err;
+
+ if (perf_data__is_pipe(session->data)) {
+ data_offset = 0;
+ } else {
+ data_offset = lseek(fd, 0, SEEK_CUR);
+ if (data_offset == -1)
+ return -errno;
+ }
+
+ err = auxtrace_queues__add_event(&spe->queues, session, event,
+ data_offset, &buffer);
+ if (err)
+ return err;
+
+ /* Dump here now we have copied a piped trace out of the pipe */
+ if (dump_trace) {
+ if (auxtrace_buffer__get_data(buffer, fd)) {
+ arm_spe_dump_event(spe, buffer->data,
+ buffer->size);
+ auxtrace_buffer__put_data(buffer);
+ }
+ }
+
+ return 0;
+}
+
+static int arm_spe_flush(struct perf_session *session __maybe_unused,
+ struct perf_tool *tool __maybe_unused)
+{
+ return 0;
+}
+
+static void arm_spe_free_queue(void *priv)
+{
+ struct arm_spe_queue *speq = priv;
+
+ if (!speq)
+ return;
+ free(speq);
+}
+
+static void arm_spe_free_events(struct perf_session *session)
+{
+ struct arm_spe *spe = container_of(session->auxtrace, struct arm_spe,
+ auxtrace);
+ struct auxtrace_queues *queues = &spe->queues;
+ unsigned int i;
+
+ for (i = 0; i < queues->nr_queues; i++) {
+ arm_spe_free_queue(queues->queue_array[i].priv);
+ queues->queue_array[i].priv = NULL;
+ }
+ auxtrace_queues__free(queues);
+}
+
+static void arm_spe_free(struct perf_session *session)
+{
+ struct arm_spe *spe = container_of(session->auxtrace, struct arm_spe,
+ auxtrace);
+
+ auxtrace_heap__free(&spe->heap);
+ arm_spe_free_events(session);
+ session->auxtrace = NULL;
+ free(spe);
+}
+
+static const char * const arm_spe_info_fmts[] = {
+ [ARM_SPE_PMU_TYPE] = " PMU Type %"PRId64"\n",
+};
+
+static void arm_spe_print_info(u64 *arr)
+{
+ if (!dump_trace)
+ return;
+
+ fprintf(stdout, arm_spe_info_fmts[ARM_SPE_PMU_TYPE], arr[ARM_SPE_PMU_TYPE]);
+}
+
+int arm_spe_process_auxtrace_info(union perf_event *event,
+ struct perf_session *session)
+{
+ struct auxtrace_info_event *auxtrace_info = &event->auxtrace_info;
+ size_t min_sz = sizeof(u64) * ARM_SPE_PMU_TYPE;
+ struct arm_spe *spe;
+ int err;
+
+ if (auxtrace_info->header.size < sizeof(struct auxtrace_info_event) +
+ min_sz)
+ return -EINVAL;
+
+ spe = zalloc(sizeof(struct arm_spe));
+ if (!spe)
+ return -ENOMEM;
+
+ err = auxtrace_queues__init(&spe->queues);
+ if (err)
+ goto err_free;
+
+ spe->session = session;
+ spe->machine = &session->machines.host; /* No kvm support */
+ spe->auxtrace_type = auxtrace_info->type;
+ spe->pmu_type = auxtrace_info->priv[ARM_SPE_PMU_TYPE];
+
+ spe->auxtrace.process_event = arm_spe_process_event;
+ spe->auxtrace.process_auxtrace_event = arm_spe_process_auxtrace_event;
+ spe->auxtrace.flush_events = arm_spe_flush;
+ spe->auxtrace.free_events = arm_spe_free_events;
+ spe->auxtrace.free = arm_spe_free;
+ session->auxtrace = &spe->auxtrace;
+
+ arm_spe_print_info(&auxtrace_info->priv[0]);
+
+ return 0;
+
+err_free:
+ free(spe);
+ return err;
+}
diff --git a/tools/perf/util/arm-spe.h b/tools/perf/util/arm-spe.h
new file mode 100644
index 000000000000..88dea6f96aab
--- /dev/null
+++ b/tools/perf/util/arm-spe.h
@@ -0,0 +1,31 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Arm Statistical Profiling Extensions (SPE) support
+ * Copyright (c) 2017-2018, Arm Ltd.
+ */
+
+#ifndef INCLUDE__PERF_ARM_SPE_H__
+#define INCLUDE__PERF_ARM_SPE_H__
+
+#define ARM_SPE_PMU_NAME "arm_spe_"
+
+enum {
+ ARM_SPE_PMU_TYPE,
+ ARM_SPE_PER_CPU_MMAPS,
+ ARM_SPE_AUXTRACE_PRIV_MAX,
+};
+
+#define ARM_SPE_AUXTRACE_PRIV_SIZE (ARM_SPE_AUXTRACE_PRIV_MAX * sizeof(u64))
+
+union perf_event;
+struct perf_session;
+struct perf_pmu;
+
+struct auxtrace_record *arm_spe_recording_init(int *err,
+ struct perf_pmu *arm_spe_pmu);
+
+int arm_spe_process_auxtrace_info(union perf_event *event,
+ struct perf_session *session);
+
+struct perf_event_attr *arm_spe_pmu_default_config(struct perf_pmu *arm_spe_pmu);
+#endif
diff --git a/tools/perf/util/auxtrace.c b/tools/perf/util/auxtrace.c
index a33491416400..f682f7a58a02 100644
--- a/tools/perf/util/auxtrace.c
+++ b/tools/perf/util/auxtrace.c
@@ -57,6 +57,7 @@
#include "intel-pt.h"
#include "intel-bts.h"
+#include "arm-spe.h"
#include "sane_ctype.h"
#include "symbol/kallsyms.h"
@@ -913,6 +914,8 @@ int perf_event__process_auxtrace_info(struct perf_tool *tool __maybe_unused,
return intel_pt_process_auxtrace_info(event, session);
case PERF_AUXTRACE_INTEL_BTS:
return intel_bts_process_auxtrace_info(event, session);
+ case PERF_AUXTRACE_ARM_SPE:
+ return arm_spe_process_auxtrace_info(event, session);
case PERF_AUXTRACE_CS_ETM:
case PERF_AUXTRACE_UNKNOWN:
default:
diff --git a/tools/perf/util/auxtrace.h b/tools/perf/util/auxtrace.h
index d19e11b68de7..453c148d2158 100644
--- a/tools/perf/util/auxtrace.h
+++ b/tools/perf/util/auxtrace.h
@@ -43,6 +43,7 @@ enum auxtrace_type {
PERF_AUXTRACE_INTEL_PT,
PERF_AUXTRACE_INTEL_BTS,
PERF_AUXTRACE_CS_ETM,
+ PERF_AUXTRACE_ARM_SPE,
};
enum itrace_period_type {
--
2.15.1
^ permalink raw reply related
* [PATCH v5 02/44] clk: davinci: New driver for davinci PLL clocks
From: David Lechner @ 2018-01-13 1:11 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <eb2b1a63-9c7c-aeca-170f-d38642442438@ti.com>
On 01/12/2018 10:18 AM, Sekhar Nori wrote:
> On Friday 12 January 2018 08:55 PM, David Lechner wrote:
>>>
>>> PLL output on DA850 must never be below 300MHz or above 600MHz (see
>>> datasheet table "Allowed PLL Operating Conditions"). Does this take care
>>> of that? Thats one of the main reasons I recall I went with some
>>> specific values of prediv, pllm and post div in
>>> arch/arm/mach-davinci/da850.c
>>
>> Apparently, I missed this requirement. It looks like I am going to have to
>> rework things so that there is some coordination between the PLL and the
>> PLLDIV clocks in order to get the < 300MHz operating points.
>
> Just to make sure we are on the same page. The datasheet
> constraint is 600 >= PLLOUT >= 300. PLLOUT is output of POSTDIV.
Hmm... I am on a different page. It looks to me like PLLOUT is the output
of PLLM, not POSTDIV. The datasheet says nothing at all and the TRM does
not say it explicitly, but footnote 2 on the table "System PLLC Output
Clocks", for example, makes it pretty clear.
>
> The operating points are defined in terms of ARM frequency (and
> voltage). The OPPs defined in kernel today are here:
> https://git.kernel.org/pub/scm/linux/kernel/git/nsekhar/linux-davinci.git/tree/arch/arm/mach-davinci/da850.c#n1092
>
> Thanks,
> Sekhar
>
^ permalink raw reply
* Status of NanoPi NEO Air WiFi dts patch
From: Martin Kelly @ 2018-01-13 1:06 UTC (permalink / raw)
To: linux-arm-kernel
Hi,
While trying to get WiFi to work on the NanoPi NEO Air, I came across
this patch, which appears not to have been merged:
https://patchwork.kernel.org/patch/9642071/
Are there any plans to get this patch merged?
Thanks,
Martin
^ permalink raw reply
* [PATCH v6 12/12] ACPI: Add PPTT to injectable table list
From: Jeremy Linton @ 2018-01-13 0:59 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20180113005920.28658-1-jeremy.linton@arm.com>
Add ACPI_SIG_PPTT to the table so initrd's can override the
system topology.
Signed-off-by: Geoffrey Blake <geoffrey.blake@arm.com>
Signed-off-by: Jeremy Linton <jeremy.linton@arm.com>
---
drivers/acpi/tables.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/acpi/tables.c b/drivers/acpi/tables.c
index 80ce2a7d224b..6d254450115b 100644
--- a/drivers/acpi/tables.c
+++ b/drivers/acpi/tables.c
@@ -456,7 +456,8 @@ static const char * const table_sigs[] = {
ACPI_SIG_SLIC, ACPI_SIG_SPCR, ACPI_SIG_SPMI, ACPI_SIG_TCPA,
ACPI_SIG_UEFI, ACPI_SIG_WAET, ACPI_SIG_WDAT, ACPI_SIG_WDDT,
ACPI_SIG_WDRT, ACPI_SIG_DSDT, ACPI_SIG_FADT, ACPI_SIG_PSDT,
- ACPI_SIG_RSDT, ACPI_SIG_XSDT, ACPI_SIG_SSDT, NULL };
+ ACPI_SIG_RSDT, ACPI_SIG_XSDT, ACPI_SIG_SSDT, ACPI_SIG_PPTT,
+ NULL };
#define ACPI_HEADER_SIZE sizeof(struct acpi_table_header)
--
2.13.5
^ permalink raw reply related
* [PATCH v6 11/12] arm64: topology: enable ACPI/PPTT based CPU topology
From: Jeremy Linton @ 2018-01-13 0:59 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20180113005920.28658-1-jeremy.linton@arm.com>
Propagate the topology information from the PPTT tree to the
cpu_topology array. We can get the thread id, core_id and
cluster_id by assuming certain levels of the PPTT tree correspond
to those concepts. The package_id is flagged in the tree and can be
found by calling find_acpi_cpu_topology_package() which terminates
its search when it finds an ACPI node flagged as the physical
package. If the tree doesn't contain enough levels to represent
all of the requested levels then the root node will be returned
for all subsequent levels.
Cc: Juri Lelli <juri.lelli@arm.com>
Signed-off-by: Jeremy Linton <jeremy.linton@arm.com>
---
arch/arm64/kernel/topology.c | 46 +++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 45 insertions(+), 1 deletion(-)
diff --git a/arch/arm64/kernel/topology.c b/arch/arm64/kernel/topology.c
index 7b06e263fdd1..ce8ec7fd6b32 100644
--- a/arch/arm64/kernel/topology.c
+++ b/arch/arm64/kernel/topology.c
@@ -11,6 +11,7 @@
* for more details.
*/
+#include <linux/acpi.h>
#include <linux/arch_topology.h>
#include <linux/cpu.h>
#include <linux/cpumask.h>
@@ -22,6 +23,7 @@
#include <linux/sched.h>
#include <linux/sched/topology.h>
#include <linux/slab.h>
+#include <linux/smp.h>
#include <linux/string.h>
#include <asm/cpu.h>
@@ -300,6 +302,46 @@ static void __init reset_cpu_topology(void)
}
}
+#ifdef CONFIG_ACPI
+/*
+ * Propagate the topology information of the processor_topology_node tree to the
+ * cpu_topology array.
+ */
+static int __init parse_acpi_topology(void)
+{
+ bool is_threaded;
+ int cpu, topology_id;
+
+ is_threaded = read_cpuid_mpidr() & MPIDR_MT_BITMASK;
+
+ for_each_possible_cpu(cpu) {
+ topology_id = find_acpi_cpu_topology(cpu, 0);
+ if (topology_id < 0)
+ return topology_id;
+
+ if (is_threaded) {
+ cpu_topology[cpu].thread_id = topology_id;
+ topology_id = find_acpi_cpu_topology(cpu, 1);
+ cpu_topology[cpu].core_id = topology_id;
+ topology_id = find_acpi_cpu_topology_package(cpu);
+ cpu_topology[cpu].package_id = topology_id;
+ } else {
+ cpu_topology[cpu].thread_id = -1;
+ cpu_topology[cpu].core_id = topology_id;
+ topology_id = find_acpi_cpu_topology_package(cpu);
+ cpu_topology[cpu].package_id = topology_id;
+ }
+ }
+
+ return 0;
+}
+
+#else
+static inline int __init parse_acpi_topology(void)
+{
+ return -EINVAL;
+}
+#endif
void __init init_cpu_topology(void)
{
@@ -309,6 +351,8 @@ void __init init_cpu_topology(void)
* Discard anything that was parsed if we hit an error so we
* don't use partial information.
*/
- if (of_have_populated_dt() && parse_dt_topology())
+ if ((!acpi_disabled) && parse_acpi_topology())
+ reset_cpu_topology();
+ else if (of_have_populated_dt() && parse_dt_topology())
reset_cpu_topology();
}
--
2.13.5
^ permalink raw reply related
* [PATCH v6 10/12] arm64: topology: rename cluster_id
From: Jeremy Linton @ 2018-01-13 0:59 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20180113005920.28658-1-jeremy.linton@arm.com>
Lets match the name of the arm64 topology field
to the kernel macro that uses it.
Cc: Vincent Guittot <vincent.guittot@linaro.org>
Cc: Dietmar Eggemann <dietmar.eggemann@arm.com>
Cc: Juri Lelli <juri.lelli@arm.com>
Signed-off-by: Jeremy Linton <jeremy.linton@arm.com>
---
arch/arm64/include/asm/topology.h | 4 ++--
arch/arm64/kernel/topology.c | 27 ++++++++++++++-------------
2 files changed, 16 insertions(+), 15 deletions(-)
diff --git a/arch/arm64/include/asm/topology.h b/arch/arm64/include/asm/topology.h
index c4f2d50491eb..6b10459e6905 100644
--- a/arch/arm64/include/asm/topology.h
+++ b/arch/arm64/include/asm/topology.h
@@ -7,14 +7,14 @@
struct cpu_topology {
int thread_id;
int core_id;
- int cluster_id;
+ int package_id;
cpumask_t thread_sibling;
cpumask_t core_sibling;
};
extern struct cpu_topology cpu_topology[NR_CPUS];
-#define topology_physical_package_id(cpu) (cpu_topology[cpu].cluster_id)
+#define topology_physical_package_id(cpu) (cpu_topology[cpu].package_id)
#define topology_core_id(cpu) (cpu_topology[cpu].core_id)
#define topology_core_cpumask(cpu) (&cpu_topology[cpu].core_sibling)
#define topology_sibling_cpumask(cpu) (&cpu_topology[cpu].thread_sibling)
diff --git a/arch/arm64/kernel/topology.c b/arch/arm64/kernel/topology.c
index 8d48b233e6ce..7b06e263fdd1 100644
--- a/arch/arm64/kernel/topology.c
+++ b/arch/arm64/kernel/topology.c
@@ -51,7 +51,7 @@ static int __init get_cpu_for_node(struct device_node *node)
return -1;
}
-static int __init parse_core(struct device_node *core, int cluster_id,
+static int __init parse_core(struct device_node *core, int package_id,
int core_id)
{
char name[10];
@@ -67,7 +67,7 @@ static int __init parse_core(struct device_node *core, int cluster_id,
leaf = false;
cpu = get_cpu_for_node(t);
if (cpu >= 0) {
- cpu_topology[cpu].cluster_id = cluster_id;
+ cpu_topology[cpu].package_id = package_id;
cpu_topology[cpu].core_id = core_id;
cpu_topology[cpu].thread_id = i;
} else {
@@ -89,7 +89,7 @@ static int __init parse_core(struct device_node *core, int cluster_id,
return -EINVAL;
}
- cpu_topology[cpu].cluster_id = cluster_id;
+ cpu_topology[cpu].package_id = package_id;
cpu_topology[cpu].core_id = core_id;
} else if (leaf) {
pr_err("%pOF: Can't get CPU for leaf core\n", core);
@@ -105,7 +105,7 @@ static int __init parse_cluster(struct device_node *cluster, int depth)
bool leaf = true;
bool has_cores = false;
struct device_node *c;
- static int cluster_id __initdata;
+ static int package_id __initdata;
int core_id = 0;
int i, ret;
@@ -144,7 +144,7 @@ static int __init parse_cluster(struct device_node *cluster, int depth)
}
if (leaf) {
- ret = parse_core(c, cluster_id, core_id++);
+ ret = parse_core(c, package_id, core_id++);
} else {
pr_err("%pOF: Non-leaf cluster with core %s\n",
cluster, name);
@@ -162,7 +162,7 @@ static int __init parse_cluster(struct device_node *cluster, int depth)
pr_warn("%pOF: empty cluster\n", cluster);
if (leaf)
- cluster_id++;
+ package_id++;
return 0;
}
@@ -198,7 +198,7 @@ static int __init parse_dt_topology(void)
* only mark cores described in the DT as possible.
*/
for_each_possible_cpu(cpu)
- if (cpu_topology[cpu].cluster_id == -1)
+ if (cpu_topology[cpu].package_id == -1)
ret = -EINVAL;
out_map:
@@ -228,7 +228,7 @@ static void update_siblings_masks(unsigned int cpuid)
for_each_possible_cpu(cpu) {
cpu_topo = &cpu_topology[cpu];
- if (cpuid_topo->cluster_id != cpu_topo->cluster_id)
+ if (cpuid_topo->package_id != cpu_topo->package_id)
continue;
cpumask_set_cpu(cpuid, &cpu_topo->core_sibling);
@@ -249,7 +249,7 @@ void store_cpu_topology(unsigned int cpuid)
struct cpu_topology *cpuid_topo = &cpu_topology[cpuid];
u64 mpidr;
- if (cpuid_topo->cluster_id != -1)
+ if (cpuid_topo->package_id != -1)
goto topology_populated;
mpidr = read_cpuid_mpidr();
@@ -263,19 +263,19 @@ void store_cpu_topology(unsigned int cpuid)
/* Multiprocessor system : Multi-threads per core */
cpuid_topo->thread_id = MPIDR_AFFINITY_LEVEL(mpidr, 0);
cpuid_topo->core_id = MPIDR_AFFINITY_LEVEL(mpidr, 1);
- cpuid_topo->cluster_id = MPIDR_AFFINITY_LEVEL(mpidr, 2) |
+ cpuid_topo->package_id = MPIDR_AFFINITY_LEVEL(mpidr, 2) |
MPIDR_AFFINITY_LEVEL(mpidr, 3) << 8;
} else {
/* Multiprocessor system : Single-thread per core */
cpuid_topo->thread_id = -1;
cpuid_topo->core_id = MPIDR_AFFINITY_LEVEL(mpidr, 0);
- cpuid_topo->cluster_id = MPIDR_AFFINITY_LEVEL(mpidr, 1) |
+ cpuid_topo->package_id = MPIDR_AFFINITY_LEVEL(mpidr, 1) |
MPIDR_AFFINITY_LEVEL(mpidr, 2) << 8 |
MPIDR_AFFINITY_LEVEL(mpidr, 3) << 16;
}
pr_debug("CPU%u: cluster %d core %d thread %d mpidr %#016llx\n",
- cpuid, cpuid_topo->cluster_id, cpuid_topo->core_id,
+ cpuid, cpuid_topo->package_id, cpuid_topo->core_id,
cpuid_topo->thread_id, mpidr);
topology_populated:
@@ -291,7 +291,7 @@ static void __init reset_cpu_topology(void)
cpu_topo->thread_id = -1;
cpu_topo->core_id = 0;
- cpu_topo->cluster_id = -1;
+ cpu_topo->package_id = -1;
cpumask_clear(&cpu_topo->core_sibling);
cpumask_set_cpu(cpu, &cpu_topo->core_sibling);
@@ -300,6 +300,7 @@ static void __init reset_cpu_topology(void)
}
}
+
void __init init_cpu_topology(void)
{
reset_cpu_topology();
--
2.13.5
^ permalink raw reply related
* [PATCH v6 09/12] ACPI/PPTT: Add topology parsing code
From: Jeremy Linton @ 2018-01-13 0:59 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20180113005920.28658-1-jeremy.linton@arm.com>
The PPTT can be used to determine the groupings of CPU's at
given levels in the system. Lets add a few routines to the PPTT
parsing code to return a unique id for each unique level in the
processor hierarchy. This can then be matched to build
thread/core/cluster/die/package/etc mappings for each processing
element in the system.
Signed-off-by: Jeremy Linton <jeremy.linton@arm.com>
---
drivers/acpi/pptt.c | 115 +++++++++++++++++++++++++++++++++++++++++++++++++++
include/linux/acpi.h | 3 ++
2 files changed, 118 insertions(+)
diff --git a/drivers/acpi/pptt.c b/drivers/acpi/pptt.c
index 4f5ab19c3a08..83d89d683f16 100644
--- a/drivers/acpi/pptt.c
+++ b/drivers/acpi/pptt.c
@@ -412,6 +412,79 @@ static void cache_setup_acpi_cpu(struct acpi_table_header *table,
}
}
+/* Passing level values greater than this will result in search termination */
+#define PPTT_ABORT_PACKAGE 0xFF
+
+/*
+ * Given an acpi_pptt_processor node, walk up until we identify the
+ * package that the node is associated with, or we run out of levels
+ * to request or the search is terminated with a flag match
+ * The level parameter also serves to limit possible loops within the tree.
+ */
+static struct acpi_pptt_processor *acpi_find_processor_package_id(
+ struct acpi_table_header *table_hdr,
+ struct acpi_pptt_processor *cpu,
+ int level, int flag)
+{
+ struct acpi_pptt_processor *prev_node;
+
+ while (cpu && level) {
+ if (cpu->flags & flag)
+ break;
+ pr_debug("level %d\n", level);
+ prev_node = fetch_pptt_node(table_hdr, cpu->parent);
+ if (prev_node == NULL)
+ break;
+ cpu = prev_node;
+ level--;
+ }
+ return cpu;
+}
+
+/*
+ * Get a unique value given a cpu, and a topology level, that can be
+ * matched to determine which cpus share common topological features
+ * at that level.
+ */
+static int topology_get_acpi_cpu_tag(struct acpi_table_header *table,
+ unsigned int cpu, int level, int flag)
+{
+ struct acpi_pptt_processor *cpu_node;
+ u32 acpi_cpu_id = get_acpi_id_for_cpu(cpu);
+
+ cpu_node = acpi_find_processor_node(table, acpi_cpu_id);
+ if (cpu_node) {
+ cpu_node = acpi_find_processor_package_id(table, cpu_node,
+ level, flag);
+ /* Only the first level has a guaranteed id */
+ if (level == 0)
+ return cpu_node->acpi_processor_id;
+ return (int)((u8 *)cpu_node - (u8 *)table);
+ }
+ pr_err_once("PPTT table found, but unable to locate core for %d\n",
+ cpu);
+ return -ENOENT;
+}
+
+static int find_acpi_cpu_topology_tag(unsigned int cpu, int level, int flag)
+{
+ struct acpi_table_header *table;
+ acpi_status status;
+ int retval;
+
+ status = acpi_get_table(ACPI_SIG_PPTT, 0, &table);
+ if (ACPI_FAILURE(status)) {
+ pr_err_once("No PPTT table found, cpu topology may be inaccurate\n");
+ return -ENOENT;
+ }
+ retval = topology_get_acpi_cpu_tag(table, cpu, level, flag);
+ pr_debug("Topology Setup ACPI cpu %d, level %d ret = %d\n",
+ cpu, level, retval);
+ acpi_put_table(table);
+
+ return retval;
+}
+
/**
* acpi_find_last_cache_level() - Determines the number of cache levels for a PE
* @cpu: Kernel logical cpu number
@@ -475,3 +548,45 @@ int cache_setup_acpi(unsigned int cpu)
return status;
}
+
+/**
+ * find_acpi_cpu_topology() - Determine a unique topology value for a given cpu
+ * @cpu: Kernel logical cpu number
+ * @level: The topological level for which we would like a unique ID
+ *
+ * Determine a topology unique ID for each thread/core/cluster/mc_grouping
+ * /socket/etc. This ID can then be used to group peers, which will have
+ * matching ids.
+ *
+ * The search terminates when either the requested level is found or
+ * we reach a root node. Levels beyond the termination point will return the
+ * same unique ID. The unique id for level 0 is the acpi processor id. All
+ * other levels beyond this use a generated value to uniquely identify
+ * a topological feature.
+ *
+ * Return: -ENOENT if the PPTT doesn't exist, or the cpu cannot be found.
+ * Otherwise returns a value which represents a unique topological feature.
+ */
+int find_acpi_cpu_topology(unsigned int cpu, int level)
+{
+ return find_acpi_cpu_topology_tag(cpu, level, 0);
+}
+
+/**
+ * find_acpi_cpu_topology_package() - Determine a unique cpu package value
+ * @cpu: Kernel logical cpu number
+ *
+ * Determine a topology unique package ID for the given cpu.
+ * This ID can then be used to group peers, which will have matching ids.
+ *
+ * The search terminates when either a level is found with the PHYSICAL_PACKAGE
+ * flag set or we reach a root node.
+ *
+ * Return: -ENOENT if the PPTT doesn't exist, or the cpu cannot be found.
+ * Otherwise returns a value which represents the package for this cpu.
+ */
+int find_acpi_cpu_topology_package(unsigned int cpu)
+{
+ return find_acpi_cpu_topology_tag(cpu, PPTT_ABORT_PACKAGE,
+ ACPI_PPTT_PHYSICAL_PACKAGE);
+}
diff --git a/include/linux/acpi.h b/include/linux/acpi.h
index dc1ebfeeb5ec..117d13934487 100644
--- a/include/linux/acpi.h
+++ b/include/linux/acpi.h
@@ -1266,4 +1266,7 @@ static inline int lpit_read_residency_count_address(u64 *address)
}
#endif
+int find_acpi_cpu_topology(unsigned int cpu, int level);
+int find_acpi_cpu_topology_package(unsigned int cpu);
+
#endif /*_LINUX_ACPI_H*/
--
2.13.5
^ permalink raw reply related
* [PATCH v6 08/12] arm64: Add support for ACPI based firmware tables
From: Jeremy Linton @ 2018-01-13 0:59 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20180113005920.28658-1-jeremy.linton@arm.com>
The /sys cache entries should support ACPI/PPTT generated cache
topology information. Lets detect ACPI systems and call
an arch specific cache_setup_acpi() routine to update the hardware
probed cache topology.
For arm64, if ACPI is enabled, determine the max number of cache
levels and populate them using the PPTT table if one is available.
Signed-off-by: Jeremy Linton <jeremy.linton@arm.com>
---
arch/arm64/kernel/cacheinfo.c | 15 ++++++++++-----
1 file changed, 10 insertions(+), 5 deletions(-)
diff --git a/arch/arm64/kernel/cacheinfo.c b/arch/arm64/kernel/cacheinfo.c
index 380f2e2fbed5..0bf0a835122f 100644
--- a/arch/arm64/kernel/cacheinfo.c
+++ b/arch/arm64/kernel/cacheinfo.c
@@ -17,6 +17,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+#include <linux/acpi.h>
#include <linux/cacheinfo.h>
#include <linux/of.h>
@@ -46,7 +47,7 @@ static void ci_leaf_init(struct cacheinfo *this_leaf,
static int __init_cache_level(unsigned int cpu)
{
- unsigned int ctype, level, leaves, of_level;
+ unsigned int ctype, level, leaves, fw_level;
struct cpu_cacheinfo *this_cpu_ci = get_cpu_cacheinfo(cpu);
for (level = 1, leaves = 0; level <= MAX_CACHE_LEVEL; level++) {
@@ -59,15 +60,19 @@ static int __init_cache_level(unsigned int cpu)
leaves += (ctype == CACHE_TYPE_SEPARATE) ? 2 : 1;
}
- of_level = of_find_last_cache_level(cpu);
- if (level < of_level) {
+ if (acpi_disabled)
+ fw_level = of_find_last_cache_level(cpu);
+ else
+ fw_level = acpi_find_last_cache_level(cpu);
+
+ if (level < fw_level) {
/*
* some external caches not specified in CLIDR_EL1
* the information may be available in the device tree
* only unified external caches are considered here
*/
- leaves += (of_level - level);
- level = of_level;
+ leaves += (fw_level - level);
+ level = fw_level;
}
this_cpu_ci->num_levels = level;
--
2.13.5
^ permalink raw reply related
* [PATCH v6 07/12] drivers: base cacheinfo: Add support for ACPI based firmware tables
From: Jeremy Linton @ 2018-01-13 0:59 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20180113005920.28658-1-jeremy.linton@arm.com>
Add a entry to to struct cacheinfo to maintain a reference to the PPTT
node which can be used to match identical caches across cores. Also
stub out cache_setup_acpi() so that individual architectures can
enable ACPI topology parsing.
Signed-off-by: Jeremy Linton <jeremy.linton@arm.com>
---
drivers/acpi/pptt.c | 1 +
drivers/base/cacheinfo.c | 20 +++++++++++++-------
include/linux/cacheinfo.h | 9 +++++++++
3 files changed, 23 insertions(+), 7 deletions(-)
diff --git a/drivers/acpi/pptt.c b/drivers/acpi/pptt.c
index 2c4b3ed862a8..4f5ab19c3a08 100644
--- a/drivers/acpi/pptt.c
+++ b/drivers/acpi/pptt.c
@@ -329,6 +329,7 @@ static void update_cache_properties(struct cacheinfo *this_leaf,
{
int valid_flags = 0;
+ this_leaf->fw_unique = cpu_node;
if (found_cache->flags & ACPI_PPTT_SIZE_PROPERTY_VALID) {
this_leaf->size = found_cache->size;
valid_flags++;
diff --git a/drivers/base/cacheinfo.c b/drivers/base/cacheinfo.c
index 217aa90fb036..ee51e33cc37c 100644
--- a/drivers/base/cacheinfo.c
+++ b/drivers/base/cacheinfo.c
@@ -208,16 +208,16 @@ static int cache_setup_of_node(unsigned int cpu)
if (index != cache_leaves(cpu)) /* not all OF nodes populated */
return -ENOENT;
-
return 0;
}
+
#else
static inline int cache_setup_of_node(unsigned int cpu) { return 0; }
static inline bool cache_leaves_are_shared(struct cacheinfo *this_leaf,
struct cacheinfo *sib_leaf)
{
/*
- * For non-DT systems, assume unique level 1 cache, system-wide
+ * For non-DT/ACPI systems, assume unique level 1 caches, system-wide
* shared caches for all other levels. This will be used only if
* arch specific code has not populated shared_cpu_map
*/
@@ -225,6 +225,11 @@ static inline bool cache_leaves_are_shared(struct cacheinfo *this_leaf,
}
#endif
+int __weak cache_setup_acpi(unsigned int cpu)
+{
+ return -ENOTSUPP;
+}
+
static int cache_shared_cpu_map_setup(unsigned int cpu)
{
struct cpu_cacheinfo *this_cpu_ci = get_cpu_cacheinfo(cpu);
@@ -235,11 +240,11 @@ static int cache_shared_cpu_map_setup(unsigned int cpu)
if (this_cpu_ci->cpu_map_populated)
return 0;
- if (of_have_populated_dt())
+ if (!acpi_disabled)
+ ret = cache_setup_acpi(cpu);
+ else if (of_have_populated_dt())
ret = cache_setup_of_node(cpu);
- else if (!acpi_disabled)
- /* No cache property/hierarchy support yet in ACPI */
- ret = -ENOTSUPP;
+
if (ret)
return ret;
@@ -290,7 +295,8 @@ static void cache_shared_cpu_map_remove(unsigned int cpu)
cpumask_clear_cpu(cpu, &sib_leaf->shared_cpu_map);
cpumask_clear_cpu(sibling, &this_leaf->shared_cpu_map);
}
- of_node_put(this_leaf->fw_unique);
+ if (of_have_populated_dt())
+ of_node_put(this_leaf->fw_unique);
}
}
diff --git a/include/linux/cacheinfo.h b/include/linux/cacheinfo.h
index 6f2e6c87b64c..65b0ae30016e 100644
--- a/include/linux/cacheinfo.h
+++ b/include/linux/cacheinfo.h
@@ -98,6 +98,15 @@ struct cpu_cacheinfo *get_cpu_cacheinfo(unsigned int cpu);
int init_cache_level(unsigned int cpu);
int populate_cache_leaves(unsigned int cpu);
void cache_of_set_props(struct cacheinfo *this_leaf, struct device_node *np);
+int cache_setup_acpi(unsigned int cpu);
+int acpi_find_last_cache_level(unsigned int cpu);
+#ifndef CONFIG_ACPI
+int acpi_find_last_cache_level(unsigned int cpu)
+{
+ /*ACPI kernels should be built with PPTT support*/
+ return 0;
+}
+#endif
const struct attribute_group *cache_get_priv_group(struct cacheinfo *this_leaf);
--
2.13.5
^ permalink raw reply related
* [PATCH v6 06/12] ACPI: Enable PPTT support on ARM64
From: Jeremy Linton @ 2018-01-13 0:59 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20180113005920.28658-1-jeremy.linton@arm.com>
Now that we have a PPTT parser, in preparation for its use
on arm64, lets build it.
Signed-off-by: Jeremy Linton <jeremy.linton@arm.com>
---
arch/arm64/Kconfig | 1 +
drivers/acpi/Kconfig | 3 +++
drivers/acpi/Makefile | 1 +
3 files changed, 5 insertions(+)
diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig
index c9a7e9e1414f..23bf30319d31 100644
--- a/arch/arm64/Kconfig
+++ b/arch/arm64/Kconfig
@@ -7,6 +7,7 @@ config ARM64
select ACPI_REDUCED_HARDWARE_ONLY if ACPI
select ACPI_MCFG if ACPI
select ACPI_SPCR_TABLE if ACPI
+ select ACPI_PPTT if ACPI
select ARCH_CLOCKSOURCE_DATA
select ARCH_HAS_DEBUG_VIRTUAL
select ARCH_HAS_DEVMEM_IS_ALLOWED
diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig
index 46505396869e..df7aebf0af0e 100644
--- a/drivers/acpi/Kconfig
+++ b/drivers/acpi/Kconfig
@@ -545,6 +545,9 @@ config ACPI_CONFIGFS
if ARM64
source "drivers/acpi/arm64/Kconfig"
+
+config ACPI_PPTT
+ bool
endif
config TPS68470_PMIC_OPREGION
diff --git a/drivers/acpi/Makefile b/drivers/acpi/Makefile
index 41954a601989..b6056b566df4 100644
--- a/drivers/acpi/Makefile
+++ b/drivers/acpi/Makefile
@@ -87,6 +87,7 @@ obj-$(CONFIG_ACPI_BGRT) += bgrt.o
obj-$(CONFIG_ACPI_CPPC_LIB) += cppc_acpi.o
obj-$(CONFIG_ACPI_SPCR_TABLE) += spcr.o
obj-$(CONFIG_ACPI_DEBUGGER_USER) += acpi_dbg.o
+obj-$(CONFIG_ACPI_PPTT) += pptt.o
# processor has its own "processor." module_param namespace
processor-y := processor_driver.o
--
2.13.5
^ permalink raw reply related
* [PATCH v6 05/12] ACPI/PPTT: Add Processor Properties Topology Table parsing
From: Jeremy Linton @ 2018-01-13 0:59 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20180113005920.28658-1-jeremy.linton@arm.com>
ACPI 6.2 adds a new table, which describes how processing units
are related to each other in tree like fashion. Caches are
also sprinkled throughout the tree and describe the properties
of the caches in relation to other caches and processing units.
Add the code to parse the cache hierarchy and report the total
number of levels of cache for a given core using
acpi_find_last_cache_level() as well as fill out the individual
cores cache information with cache_setup_acpi() once the
cpu_cacheinfo structure has been populated by the arch specific
code.
An additional patch later in the set adds the ability to report
peers in the topology using find_acpi_cpu_topology()
to report a unique ID for each processing unit at a given level
in the tree. These unique id's can then be used to match related
processing units which exist as threads, COD (clusters
on die), within a given package, etc.
Signed-off-by: Jeremy Linton <jeremy.linton@arm.com>
---
drivers/acpi/pptt.c | 476 ++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 476 insertions(+)
create mode 100644 drivers/acpi/pptt.c
diff --git a/drivers/acpi/pptt.c b/drivers/acpi/pptt.c
new file mode 100644
index 000000000000..2c4b3ed862a8
--- /dev/null
+++ b/drivers/acpi/pptt.c
@@ -0,0 +1,476 @@
+/*
+ * Copyright (C) 2018, ARM
+ *
+ * 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.
+ *
+ * This file implements parsing of Processor Properties Topology Table (PPTT)
+ * which is optionally used to describe the processor and cache topology.
+ * Due to the relative pointers used throughout the table, this doesn't
+ * leverage the existing subtable parsing in the kernel.
+ *
+ * The PPTT structure is an inverted tree, with each node potentially
+ * holding one or two inverted tree data structures describing
+ * the caches available at that level. Each cache structure optionally
+ * contains properties describing the cache at a given level which can be
+ * used to override hardware probed values.
+ */
+#define pr_fmt(fmt) "ACPI PPTT: " fmt
+
+#include <linux/acpi.h>
+#include <linux/cacheinfo.h>
+#include <acpi/processor.h>
+
+/* total number of attributes checked by the properties code */
+#define PPTT_CHECKED_ATTRIBUTES 6
+
+/*
+ * Given the PPTT table, find and verify that the subtable entry
+ * is located within the table
+ */
+static struct acpi_subtable_header *fetch_pptt_subtable(
+ struct acpi_table_header *table_hdr, u32 pptt_ref)
+{
+ struct acpi_subtable_header *entry;
+
+ /* there isn't a subtable at reference 0 */
+ if (pptt_ref < sizeof(struct acpi_subtable_header))
+ return NULL;
+
+ if (pptt_ref + sizeof(struct acpi_subtable_header) > table_hdr->length)
+ return NULL;
+
+ entry = ACPI_ADD_PTR(struct acpi_subtable_header, table_hdr, pptt_ref);
+
+ if (pptt_ref + entry->length > table_hdr->length)
+ return NULL;
+
+ return entry;
+}
+
+static struct acpi_pptt_processor *fetch_pptt_node(
+ struct acpi_table_header *table_hdr, u32 pptt_ref)
+{
+ return (struct acpi_pptt_processor *)fetch_pptt_subtable(table_hdr,
+ pptt_ref);
+}
+
+static struct acpi_pptt_cache *fetch_pptt_cache(
+ struct acpi_table_header *table_hdr, u32 pptt_ref)
+{
+ return (struct acpi_pptt_cache *)fetch_pptt_subtable(table_hdr,
+ pptt_ref);
+}
+
+static struct acpi_subtable_header *acpi_get_pptt_resource(
+ struct acpi_table_header *table_hdr,
+ struct acpi_pptt_processor *node, int resource)
+{
+ u32 *ref;
+
+ if (resource >= node->number_of_priv_resources)
+ return NULL;
+
+ ref = ACPI_ADD_PTR(u32, node, sizeof(struct acpi_pptt_processor));
+ ref += resource;
+
+ return fetch_pptt_subtable(table_hdr, *ref);
+}
+
+/*
+ * Attempt to find a given cache level, while counting the max number
+ * of cache levels for the cache node.
+ *
+ * Given a pptt resource, verify that it is a cache node, then walk
+ * down each level of caches, counting how many levels are found
+ * as well as checking the cache type (icache, dcache, unified). If a
+ * level & type match, then we set found, and continue the search.
+ * Once the entire cache branch has been walked return its max
+ * depth.
+ */
+static int acpi_pptt_walk_cache(struct acpi_table_header *table_hdr,
+ int local_level,
+ struct acpi_subtable_header *res,
+ struct acpi_pptt_cache **found,
+ int level, int type)
+{
+ struct acpi_pptt_cache *cache;
+
+ if (res->type != ACPI_PPTT_TYPE_CACHE)
+ return 0;
+
+ cache = (struct acpi_pptt_cache *) res;
+ while (cache) {
+ local_level++;
+
+ if ((local_level == level) &&
+ (cache->flags & ACPI_PPTT_CACHE_TYPE_VALID) &&
+ ((cache->attributes & ACPI_PPTT_MASK_CACHE_TYPE) == type)) {
+ if ((*found != NULL) && (cache != *found))
+ pr_err("Found duplicate cache level/type unable to determine uniqueness\n");
+
+ pr_debug("Found cache @ level %d\n", level);
+ *found = cache;
+ /*
+ * continue looking at this node's resource list
+ * to verify that we don't find a duplicate
+ * cache node.
+ */
+ }
+ cache = fetch_pptt_cache(table_hdr, cache->next_level_of_cache);
+ }
+ return local_level;
+}
+
+/*
+ * Given a CPU node look for cache levels that exist at this level, and then
+ * for each cache node, count how many levels exist below (logically above) it.
+ * If a level and type are specified, and we find that level/type, abort
+ * processing and return the acpi_pptt_cache structure.
+ */
+static struct acpi_pptt_cache *acpi_find_cache_level(
+ struct acpi_table_header *table_hdr,
+ struct acpi_pptt_processor *cpu_node,
+ int *starting_level, int level, int type)
+{
+ struct acpi_subtable_header *res;
+ int number_of_levels = *starting_level;
+ int resource = 0;
+ struct acpi_pptt_cache *ret = NULL;
+ int local_level;
+
+ /* walk down from processor node */
+ while ((res = acpi_get_pptt_resource(table_hdr, cpu_node, resource))) {
+ resource++;
+
+ local_level = acpi_pptt_walk_cache(table_hdr, *starting_level,
+ res, &ret, level, type);
+ /*
+ * we are looking for the max depth. Since its potentially
+ * possible for a given node to have resources with differing
+ * depths verify that the depth we have found is the largest.
+ */
+ if (number_of_levels < local_level)
+ number_of_levels = local_level;
+ }
+ if (number_of_levels > *starting_level)
+ *starting_level = number_of_levels;
+
+ return ret;
+}
+
+/*
+ * Given a processor node containing a processing unit, walk into it and count
+ * how many levels exist solely for it, and then walk up each level until we hit
+ * the root node (ignore the package level because it may be possible to have
+ * caches that exist across packages). Count the number of cache levels that
+ * exist at each level on the way up.
+ */
+static int acpi_process_node(struct acpi_table_header *table_hdr,
+ struct acpi_pptt_processor *cpu_node)
+{
+ int total_levels = 0;
+
+ do {
+ acpi_find_cache_level(table_hdr, cpu_node, &total_levels, 0, 0);
+ cpu_node = fetch_pptt_node(table_hdr, cpu_node->parent);
+ } while (cpu_node);
+
+ return total_levels;
+}
+
+/*
+ * Determine if the *node parameter is a leaf node by iterating the
+ * PPTT table, looking for nodes which reference it.
+ * Return 0 if we find a node referencing the passed node,
+ * or 1 if we don't.
+ */
+static int acpi_pptt_leaf_node(struct acpi_table_header *table_hdr,
+ struct acpi_pptt_processor *node)
+{
+ struct acpi_subtable_header *entry;
+ unsigned long table_end;
+ u32 node_entry;
+ struct acpi_pptt_processor *cpu_node;
+
+ table_end = (unsigned long)table_hdr + table_hdr->length;
+ node_entry = ACPI_PTR_DIFF(node, table_hdr);
+ entry = ACPI_ADD_PTR(struct acpi_subtable_header, table_hdr,
+ sizeof(struct acpi_table_pptt));
+
+ while ((unsigned long)(entry + 1) < table_end) {
+ cpu_node = (struct acpi_pptt_processor *)entry;
+ if ((entry->type == ACPI_PPTT_TYPE_PROCESSOR) &&
+ (cpu_node->parent == node_entry))
+ return 0;
+ entry = ACPI_ADD_PTR(struct acpi_subtable_header, entry,
+ entry->length);
+ }
+ return 1;
+}
+
+/*
+ * Find the subtable entry describing the provided processor.
+ * This is done by iterating the PPTT table looking for processor nodes
+ * which have an acpi_processor_id that matches the acpi_cpu_id parameter
+ * passed into the function. If we find a node that matches this criteria
+ * we verify that its a leaf node in the topology rather than depending
+ * on the valid flag, which doesn't need to be set for leaf nodes.
+ */
+static struct acpi_pptt_processor *acpi_find_processor_node(
+ struct acpi_table_header *table_hdr,
+ u32 acpi_cpu_id)
+{
+ struct acpi_subtable_header *entry;
+ unsigned long table_end;
+ struct acpi_pptt_processor *cpu_node;
+
+ table_end = (unsigned long)table_hdr + table_hdr->length;
+ entry = ACPI_ADD_PTR(struct acpi_subtable_header, table_hdr,
+ sizeof(struct acpi_table_pptt));
+
+ /* find the processor structure associated with this cpuid */
+ while ((unsigned long)(entry + 1) < table_end) {
+ cpu_node = (struct acpi_pptt_processor *)entry;
+
+ if (entry->length == 0) {
+ pr_err("Invalid zero length subtable\n");
+ break;
+ }
+ if ((entry->type == ACPI_PPTT_TYPE_PROCESSOR) &&
+ (acpi_cpu_id == cpu_node->acpi_processor_id) &&
+ acpi_pptt_leaf_node(table_hdr, cpu_node)) {
+ return (struct acpi_pptt_processor *)entry;
+ }
+
+ entry = ACPI_ADD_PTR(struct acpi_subtable_header, entry,
+ entry->length);
+ }
+
+ return NULL;
+}
+
+static int acpi_find_cache_levels(struct acpi_table_header *table_hdr,
+ u32 acpi_cpu_id)
+{
+ int number_of_levels = 0;
+ struct acpi_pptt_processor *cpu;
+
+ cpu = acpi_find_processor_node(table_hdr, acpi_cpu_id);
+ if (cpu)
+ number_of_levels = acpi_process_node(table_hdr, cpu);
+
+ return number_of_levels;
+}
+
+/* Convert the linux cache_type to a ACPI PPTT cache type value */
+static u8 acpi_cache_type(enum cache_type type)
+{
+ switch (type) {
+ case CACHE_TYPE_DATA:
+ pr_debug("Looking for data cache\n");
+ return ACPI_PPTT_CACHE_TYPE_DATA;
+ case CACHE_TYPE_INST:
+ pr_debug("Looking for instruction cache\n");
+ return ACPI_PPTT_CACHE_TYPE_INSTR;
+ default:
+ case CACHE_TYPE_UNIFIED:
+ pr_debug("Looking for unified cache\n");
+ /*
+ * It is important that ACPI_PPTT_CACHE_TYPE_UNIFIED
+ * contains the bit pattern that will match both
+ * ACPI unified bit patterns because we use it later
+ * to match both cases.
+ */
+ return ACPI_PPTT_CACHE_TYPE_UNIFIED;
+ }
+}
+
+/* find the ACPI node describing the cache type/level for the given CPU */
+static struct acpi_pptt_cache *acpi_find_cache_node(
+ struct acpi_table_header *table_hdr, u32 acpi_cpu_id,
+ enum cache_type type, unsigned int level,
+ struct acpi_pptt_processor **node)
+{
+ int total_levels = 0;
+ struct acpi_pptt_cache *found = NULL;
+ struct acpi_pptt_processor *cpu_node;
+ u8 acpi_type = acpi_cache_type(type);
+
+ pr_debug("Looking for CPU %d's level %d cache type %d\n",
+ acpi_cpu_id, level, acpi_type);
+
+ cpu_node = acpi_find_processor_node(table_hdr, acpi_cpu_id);
+
+ while ((cpu_node) && (!found)) {
+ found = acpi_find_cache_level(table_hdr, cpu_node,
+ &total_levels, level, acpi_type);
+ *node = cpu_node;
+ cpu_node = fetch_pptt_node(table_hdr, cpu_node->parent);
+ }
+
+ return found;
+}
+
+/*
+ * The ACPI spec implies that the fields in the cache structures are used to
+ * extend and correct the information probed from the hardware. In the case
+ * of arm64 the CCSIDR probing has been removed because it might be incorrect.
+ */
+static void update_cache_properties(struct cacheinfo *this_leaf,
+ struct acpi_pptt_cache *found_cache,
+ struct acpi_pptt_processor *cpu_node)
+{
+ int valid_flags = 0;
+
+ if (found_cache->flags & ACPI_PPTT_SIZE_PROPERTY_VALID) {
+ this_leaf->size = found_cache->size;
+ valid_flags++;
+ }
+ if (found_cache->flags & ACPI_PPTT_LINE_SIZE_VALID) {
+ this_leaf->coherency_line_size = found_cache->line_size;
+ valid_flags++;
+ }
+ if (found_cache->flags & ACPI_PPTT_NUMBER_OF_SETS_VALID) {
+ this_leaf->number_of_sets = found_cache->number_of_sets;
+ valid_flags++;
+ }
+ if (found_cache->flags & ACPI_PPTT_ASSOCIATIVITY_VALID) {
+ this_leaf->ways_of_associativity = found_cache->associativity;
+ valid_flags++;
+ }
+ if (found_cache->flags & ACPI_PPTT_WRITE_POLICY_VALID) {
+ switch (found_cache->attributes & ACPI_PPTT_MASK_WRITE_POLICY) {
+ case ACPI_PPTT_CACHE_POLICY_WT:
+ this_leaf->attributes = CACHE_WRITE_THROUGH;
+ break;
+ case ACPI_PPTT_CACHE_POLICY_WB:
+ this_leaf->attributes = CACHE_WRITE_BACK;
+ break;
+ }
+ valid_flags++;
+ }
+ if (found_cache->flags & ACPI_PPTT_ALLOCATION_TYPE_VALID) {
+ switch (found_cache->attributes & ACPI_PPTT_MASK_ALLOCATION_TYPE) {
+ case ACPI_PPTT_CACHE_READ_ALLOCATE:
+ this_leaf->attributes |= CACHE_READ_ALLOCATE;
+ break;
+ case ACPI_PPTT_CACHE_WRITE_ALLOCATE:
+ this_leaf->attributes |= CACHE_WRITE_ALLOCATE;
+ break;
+ case ACPI_PPTT_CACHE_RW_ALLOCATE:
+ case ACPI_PPTT_CACHE_RW_ALLOCATE_ALT:
+ this_leaf->attributes |=
+ CACHE_READ_ALLOCATE | CACHE_WRITE_ALLOCATE;
+ break;
+ }
+ valid_flags++;
+ }
+ /*
+ * If all the above flags are valid, and the cache type is NOCACHE
+ * update the cache type as well.
+ */
+ if ((this_leaf->type == CACHE_TYPE_NOCACHE) &&
+ (valid_flags == PPTT_CHECKED_ATTRIBUTES))
+ this_leaf->type = CACHE_TYPE_UNIFIED;
+}
+
+/*
+ * Update the kernel cache information for each level of cache
+ * associated with the given acpi cpu.
+ */
+static void cache_setup_acpi_cpu(struct acpi_table_header *table,
+ unsigned int cpu)
+{
+ struct acpi_pptt_cache *found_cache;
+ struct cpu_cacheinfo *this_cpu_ci = get_cpu_cacheinfo(cpu);
+ u32 acpi_cpu_id = get_acpi_id_for_cpu(cpu);
+ struct cacheinfo *this_leaf;
+ unsigned int index = 0;
+ struct acpi_pptt_processor *cpu_node = NULL;
+
+ while (index < get_cpu_cacheinfo(cpu)->num_leaves) {
+ this_leaf = this_cpu_ci->info_list + index;
+ found_cache = acpi_find_cache_node(table, acpi_cpu_id,
+ this_leaf->type,
+ this_leaf->level,
+ &cpu_node);
+ pr_debug("found = %p %p\n", found_cache, cpu_node);
+ if (found_cache)
+ update_cache_properties(this_leaf,
+ found_cache,
+ cpu_node);
+
+ index++;
+ }
+}
+
+/**
+ * acpi_find_last_cache_level() - Determines the number of cache levels for a PE
+ * @cpu: Kernel logical cpu number
+ *
+ * Given a logical cpu number, returns the number of levels of cache represented
+ * in the PPTT. Errors caused by lack of a PPTT table, or otherwise, return 0
+ * indicating we didn't find any cache levels.
+ *
+ * Return: Cache levels visible to this core.
+ */
+int acpi_find_last_cache_level(unsigned int cpu)
+{
+ u32 acpi_cpu_id;
+ struct acpi_table_header *table;
+ int number_of_levels = 0;
+ acpi_status status;
+
+ pr_debug("Cache Setup find last level cpu=%d\n", cpu);
+
+ acpi_cpu_id = get_acpi_id_for_cpu(cpu);
+ status = acpi_get_table(ACPI_SIG_PPTT, 0, &table);
+ if (ACPI_FAILURE(status)) {
+ pr_err_once("No PPTT table found, cache topology may be inaccurate\n");
+ } else {
+ number_of_levels = acpi_find_cache_levels(table, acpi_cpu_id);
+ acpi_put_table(table);
+ }
+ pr_debug("Cache Setup find last level level=%d\n", number_of_levels);
+
+ return number_of_levels;
+}
+
+/**
+ * cache_setup_acpi() - Override CPU cache topology with data from the PPTT
+ * @cpu: Kernel logical cpu number
+ *
+ * Updates the global cache info provided by cpu_get_cacheinfo()
+ * when there are valid properties in the acpi_pptt_cache nodes. A
+ * successful parse may not result in any updates if none of the
+ * cache levels have any valid flags set. Futher, a unique value is
+ * associated with each known CPU cache entry. This unique value
+ * can be used to determine whether caches are shared between cpus.
+ *
+ * Return: -ENOENT on failure to find table, or 0 on success
+ */
+int cache_setup_acpi(unsigned int cpu)
+{
+ struct acpi_table_header *table;
+ acpi_status status;
+
+ pr_debug("Cache Setup ACPI cpu %d\n", cpu);
+
+ status = acpi_get_table(ACPI_SIG_PPTT, 0, &table);
+ if (ACPI_FAILURE(status)) {
+ pr_err_once("No PPTT table found, cache topology may be inaccurate\n");
+ return -ENOENT;
+ }
+
+ cache_setup_acpi_cpu(table, cpu);
+ acpi_put_table(table);
+
+ return status;
+}
--
2.13.5
^ permalink raw reply related
* [PATCH v6 04/12] arm64/acpi: Create arch specific cpu to acpi id helper
From: Jeremy Linton @ 2018-01-13 0:59 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20180113005920.28658-1-jeremy.linton@arm.com>
Its helpful to be able to lookup the acpi_processor_id associated
with a logical cpu. Provide an arm64 helper to do this.
Signed-off-by: Jeremy Linton <jeremy.linton@arm.com>
---
arch/arm64/include/asm/acpi.h | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/arch/arm64/include/asm/acpi.h b/arch/arm64/include/asm/acpi.h
index 32f465a80e4e..0db62a4cbce2 100644
--- a/arch/arm64/include/asm/acpi.h
+++ b/arch/arm64/include/asm/acpi.h
@@ -86,6 +86,10 @@ static inline bool acpi_has_cpu_in_madt(void)
}
struct acpi_madt_generic_interrupt *acpi_cpu_get_madt_gicc(int cpu);
+static inline u32 get_acpi_id_for_cpu(unsigned int cpu)
+{
+ return acpi_cpu_get_madt_gicc(cpu)->uid;
+}
static inline void arch_fix_phys_package_id(int num, u32 slot) { }
void __init acpi_init_cpus(void);
--
2.13.5
^ permalink raw reply related
* [PATCH v6 03/12] cacheinfo: rename of_node to fw_unique
From: Jeremy Linton @ 2018-01-13 0:59 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20180113005920.28658-1-jeremy.linton@arm.com>
Rename and change the type of of_node to indicate
it is a generic pointer which is generally only used
for comparison purposes. In a later patch we will apply
an ACPI/PPTT "token" pointer in fw_unique so that
the code which builds the shared cpu masks can be reused.
Cc: Palmer Dabbelt <palmer@sifive.com>
Cc: Albert Ou <albert@sifive.com>
Signed-off-by: Jeremy Linton <jeremy.linton@arm.com>
---
arch/riscv/kernel/cacheinfo.c | 2 +-
drivers/base/cacheinfo.c | 16 +++++++++-------
include/linux/cacheinfo.h | 8 +++-----
3 files changed, 13 insertions(+), 13 deletions(-)
diff --git a/arch/riscv/kernel/cacheinfo.c b/arch/riscv/kernel/cacheinfo.c
index 6f4500233cf8..6b0219af88d4 100644
--- a/arch/riscv/kernel/cacheinfo.c
+++ b/arch/riscv/kernel/cacheinfo.c
@@ -20,7 +20,7 @@ static void ci_leaf_init(struct cacheinfo *this_leaf,
struct device_node *node,
enum cache_type type, unsigned int level)
{
- this_leaf->of_node = node;
+ this_leaf->fw_unique = node;
this_leaf->level = level;
this_leaf->type = type;
/* not a sector cache */
diff --git a/drivers/base/cacheinfo.c b/drivers/base/cacheinfo.c
index fc0d42bbd9eb..217aa90fb036 100644
--- a/drivers/base/cacheinfo.c
+++ b/drivers/base/cacheinfo.c
@@ -47,7 +47,7 @@ struct cpu_cacheinfo *get_cpu_cacheinfo(unsigned int cpu)
static inline bool cache_leaves_are_shared(struct cacheinfo *this_leaf,
struct cacheinfo *sib_leaf)
{
- return sib_leaf->of_node == this_leaf->of_node;
+ return sib_leaf->fw_unique == this_leaf->fw_unique;
}
/* OF properties to query for a given cache type */
@@ -178,9 +178,10 @@ static int cache_setup_of_node(unsigned int cpu)
struct cpu_cacheinfo *this_cpu_ci = get_cpu_cacheinfo(cpu);
unsigned int index = 0;
- /* skip if of_node is already populated */
- if (this_cpu_ci->info_list->of_node)
+ /* skip if fw_unique is already populated */
+ if (this_cpu_ci->info_list->fw_unique) {
return 0;
+ }
if (!cpu_dev) {
pr_err("No cpu device for CPU %d\n", cpu);
@@ -201,7 +202,7 @@ static int cache_setup_of_node(unsigned int cpu)
if (!np)
break;
cache_of_set_props(this_leaf, np);
- this_leaf->of_node = np;
+ this_leaf->fw_unique = np;
index++;
}
@@ -289,7 +290,7 @@ static void cache_shared_cpu_map_remove(unsigned int cpu)
cpumask_clear_cpu(cpu, &sib_leaf->shared_cpu_map);
cpumask_clear_cpu(sibling, &this_leaf->shared_cpu_map);
}
- of_node_put(this_leaf->of_node);
+ of_node_put(this_leaf->fw_unique);
}
}
@@ -334,8 +335,9 @@ static int detect_cache_attributes(unsigned int cpu)
if (ret)
goto free_ci;
/*
- * For systems using DT for cache hierarchy, of_node and shared_cpu_map
- * will be set up here only if they are not populated already
+ * For systems using DT for cache hierarchy, fw_unique
+ * and shared_cpu_map will be set up here only if they are
+ * not populated already
*/
ret = cache_shared_cpu_map_setup(cpu);
if (ret) {
diff --git a/include/linux/cacheinfo.h b/include/linux/cacheinfo.h
index d35299a590a4..6f2e6c87b64c 100644
--- a/include/linux/cacheinfo.h
+++ b/include/linux/cacheinfo.h
@@ -34,9 +34,8 @@ enum cache_type {
* @shared_cpu_map: logical cpumask representing all the cpus sharing
* this cache node
* @attributes: bitfield representing various cache attributes
- * @of_node: if devicetree is used, this represents either the cpu node in
- * case there's no explicit cache node or the cache node itself in the
- * device tree
+ * @fw_unique: Unique value used to determine if different cacheinfo
+ * structures represent a single hardware cache instance.
* @disable_sysfs: indicates whether this node is visible to the user via
* sysfs or not
* @priv: pointer to any private data structure specific to particular
@@ -65,8 +64,7 @@ struct cacheinfo {
#define CACHE_ALLOCATE_POLICY_MASK \
(CACHE_READ_ALLOCATE | CACHE_WRITE_ALLOCATE)
#define CACHE_ID BIT(4)
-
- struct device_node *of_node;
+ void *fw_unique;
bool disable_sysfs;
void *priv;
};
--
2.13.5
^ permalink raw reply related
* [PATCH v6 02/12] drivers: base: cacheinfo: setup DT cache properties early
From: Jeremy Linton @ 2018-01-13 0:59 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20180113005920.28658-1-jeremy.linton@arm.com>
The original intent in cacheinfo was that an architecture
specific populate_cache_leaves() would probe the hardware
and then cache_shared_cpu_map_setup() and
cache_override_properties() would provide firmware help to
extend/expand upon what was probed. Arm64 was really
the only architecture that was working this way, and
with the removal of most of the hardware probing logic it
became clear that it was possible to simplify the logic a bit.
This patch combines the walk of the DT nodes with the
code updating the cache size/line_size and nr_sets.
cache_override_properties() (which was DT specific) is
then removed. The result is that cacheinfo.of_node is
no longer used as a temporary place to hold DT references
for future calls that update cache properties. That change
helps to clarify its one remaining use (matching
cacheinfo nodes that represent shared caches) which
will be used by the ACPI/PPTT code in the following patches.
Cc: Palmer Dabbelt <palmer@sifive.com>
Cc: Albert Ou <albert@sifive.com>
Signed-off-by: Jeremy Linton <jeremy.linton@arm.com>
---
arch/riscv/kernel/cacheinfo.c | 1 +
drivers/base/cacheinfo.c | 65 +++++++++++++++++++------------------------
include/linux/cacheinfo.h | 1 +
3 files changed, 31 insertions(+), 36 deletions(-)
diff --git a/arch/riscv/kernel/cacheinfo.c b/arch/riscv/kernel/cacheinfo.c
index 10ed2749e246..6f4500233cf8 100644
--- a/arch/riscv/kernel/cacheinfo.c
+++ b/arch/riscv/kernel/cacheinfo.c
@@ -30,6 +30,7 @@ static void ci_leaf_init(struct cacheinfo *this_leaf,
CACHE_WRITE_BACK
| CACHE_READ_ALLOCATE
| CACHE_WRITE_ALLOCATE;
+ cache_of_set_props(this_leaf, node);
}
static int __init_cache_level(unsigned int cpu)
diff --git a/drivers/base/cacheinfo.c b/drivers/base/cacheinfo.c
index a883a213fcd5..fc0d42bbd9eb 100644
--- a/drivers/base/cacheinfo.c
+++ b/drivers/base/cacheinfo.c
@@ -43,6 +43,7 @@ struct cpu_cacheinfo *get_cpu_cacheinfo(unsigned int cpu)
}
#ifdef CONFIG_OF
+
static inline bool cache_leaves_are_shared(struct cacheinfo *this_leaf,
struct cacheinfo *sib_leaf)
{
@@ -82,7 +83,7 @@ static inline int get_cacheinfo_idx(enum cache_type type)
return type;
}
-static void cache_size(struct cacheinfo *this_leaf)
+static void cache_size(struct cacheinfo *this_leaf, struct device_node *np)
{
const char *propname;
const __be32 *cache_size;
@@ -91,13 +92,14 @@ static void cache_size(struct cacheinfo *this_leaf)
ct_idx = get_cacheinfo_idx(this_leaf->type);
propname = cache_type_info[ct_idx].size_prop;
- cache_size = of_get_property(this_leaf->of_node, propname, NULL);
+ cache_size = of_get_property(np, propname, NULL);
if (cache_size)
this_leaf->size = of_read_number(cache_size, 1);
}
/* not cache_line_size() because that's a macro in include/linux/cache.h */
-static void cache_get_line_size(struct cacheinfo *this_leaf)
+static void cache_get_line_size(struct cacheinfo *this_leaf,
+ struct device_node *np)
{
const __be32 *line_size;
int i, lim, ct_idx;
@@ -109,7 +111,7 @@ static void cache_get_line_size(struct cacheinfo *this_leaf)
const char *propname;
propname = cache_type_info[ct_idx].line_size_props[i];
- line_size = of_get_property(this_leaf->of_node, propname, NULL);
+ line_size = of_get_property(np, propname, NULL);
if (line_size)
break;
}
@@ -118,7 +120,7 @@ static void cache_get_line_size(struct cacheinfo *this_leaf)
this_leaf->coherency_line_size = of_read_number(line_size, 1);
}
-static void cache_nr_sets(struct cacheinfo *this_leaf)
+static void cache_nr_sets(struct cacheinfo *this_leaf, struct device_node *np)
{
const char *propname;
const __be32 *nr_sets;
@@ -127,7 +129,7 @@ static void cache_nr_sets(struct cacheinfo *this_leaf)
ct_idx = get_cacheinfo_idx(this_leaf->type);
propname = cache_type_info[ct_idx].nr_sets_prop;
- nr_sets = of_get_property(this_leaf->of_node, propname, NULL);
+ nr_sets = of_get_property(np, propname, NULL);
if (nr_sets)
this_leaf->number_of_sets = of_read_number(nr_sets, 1);
}
@@ -146,32 +148,26 @@ static void cache_associativity(struct cacheinfo *this_leaf)
this_leaf->ways_of_associativity = (size / nr_sets) / line_size;
}
-static bool cache_node_is_unified(struct cacheinfo *this_leaf)
+static bool cache_node_is_unified(struct cacheinfo *this_leaf,
+ struct device_node *np)
{
- return of_property_read_bool(this_leaf->of_node, "cache-unified");
+ return of_property_read_bool(np, "cache-unified");
}
-static void cache_of_override_properties(unsigned int cpu)
+void cache_of_set_props(struct cacheinfo *this_leaf, struct device_node *np)
{
- int index;
- struct cacheinfo *this_leaf;
- struct cpu_cacheinfo *this_cpu_ci = get_cpu_cacheinfo(cpu);
-
- for (index = 0; index < cache_leaves(cpu); index++) {
- this_leaf = this_cpu_ci->info_list + index;
- /*
- * init_cache_level must setup the cache level correctly
- * overriding the architecturally specified levels, so
- * if type is NONE at this stage, it should be unified
- */
- if (this_leaf->type == CACHE_TYPE_NOCACHE &&
- cache_node_is_unified(this_leaf))
- this_leaf->type = CACHE_TYPE_UNIFIED;
- cache_size(this_leaf);
- cache_get_line_size(this_leaf);
- cache_nr_sets(this_leaf);
- cache_associativity(this_leaf);
- }
+ /*
+ * init_cache_level must setup the cache level correctly
+ * overriding the architecturally specified levels, so
+ * if type is NONE at this stage, it should be unified
+ */
+ if (this_leaf->type == CACHE_TYPE_NOCACHE &&
+ cache_node_is_unified(this_leaf, np))
+ this_leaf->type = CACHE_TYPE_UNIFIED;
+ cache_size(this_leaf, np);
+ cache_get_line_size(this_leaf, np);
+ cache_nr_sets(this_leaf, np);
+ cache_associativity(this_leaf);
}
static int cache_setup_of_node(unsigned int cpu)
@@ -204,6 +200,7 @@ static int cache_setup_of_node(unsigned int cpu)
np = of_node_get(np);/* cpu node itself */
if (!np)
break;
+ cache_of_set_props(this_leaf, np);
this_leaf->of_node = np;
index++;
}
@@ -214,7 +211,6 @@ static int cache_setup_of_node(unsigned int cpu)
return 0;
}
#else
-static void cache_of_override_properties(unsigned int cpu) { }
static inline int cache_setup_of_node(unsigned int cpu) { return 0; }
static inline bool cache_leaves_are_shared(struct cacheinfo *this_leaf,
struct cacheinfo *sib_leaf)
@@ -297,12 +293,6 @@ static void cache_shared_cpu_map_remove(unsigned int cpu)
}
}
-static void cache_override_properties(unsigned int cpu)
-{
- if (of_have_populated_dt())
- return cache_of_override_properties(cpu);
-}
-
static void free_cache_attributes(unsigned int cpu)
{
if (!per_cpu_cacheinfo(cpu))
@@ -336,6 +326,10 @@ static int detect_cache_attributes(unsigned int cpu)
if (per_cpu_cacheinfo(cpu) == NULL)
return -ENOMEM;
+ /*
+ * populate_cache_leaves() may completely setup the cache leaves and
+ * shared_cpu_map or it may leave it partially setup.
+ */
ret = populate_cache_leaves(cpu);
if (ret)
goto free_ci;
@@ -349,7 +343,6 @@ static int detect_cache_attributes(unsigned int cpu)
goto free_ci;
}
- cache_override_properties(cpu);
return 0;
free_ci:
diff --git a/include/linux/cacheinfo.h b/include/linux/cacheinfo.h
index 3d9805297cda..d35299a590a4 100644
--- a/include/linux/cacheinfo.h
+++ b/include/linux/cacheinfo.h
@@ -99,6 +99,7 @@ int func(unsigned int cpu) \
struct cpu_cacheinfo *get_cpu_cacheinfo(unsigned int cpu);
int init_cache_level(unsigned int cpu);
int populate_cache_leaves(unsigned int cpu);
+void cache_of_set_props(struct cacheinfo *this_leaf, struct device_node *np);
const struct attribute_group *cache_get_priv_group(struct cacheinfo *this_leaf);
--
2.13.5
^ permalink raw reply related
* [PATCH v6 01/12] drivers: base: cacheinfo: move cache_setup_of_node()
From: Jeremy Linton @ 2018-01-13 0:59 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20180113005920.28658-1-jeremy.linton@arm.com>
In preparation for the next patch, and to aid in
review of that patch, lets move cache_setup_of_node
farther down in the module without any changes.
Signed-off-by: Jeremy Linton <jeremy.linton@arm.com>
---
drivers/base/cacheinfo.c | 80 ++++++++++++++++++++++++------------------------
1 file changed, 40 insertions(+), 40 deletions(-)
diff --git a/drivers/base/cacheinfo.c b/drivers/base/cacheinfo.c
index 07532d83be0b..a883a213fcd5 100644
--- a/drivers/base/cacheinfo.c
+++ b/drivers/base/cacheinfo.c
@@ -43,46 +43,6 @@ struct cpu_cacheinfo *get_cpu_cacheinfo(unsigned int cpu)
}
#ifdef CONFIG_OF
-static int cache_setup_of_node(unsigned int cpu)
-{
- struct device_node *np;
- struct cacheinfo *this_leaf;
- struct device *cpu_dev = get_cpu_device(cpu);
- struct cpu_cacheinfo *this_cpu_ci = get_cpu_cacheinfo(cpu);
- unsigned int index = 0;
-
- /* skip if of_node is already populated */
- if (this_cpu_ci->info_list->of_node)
- return 0;
-
- if (!cpu_dev) {
- pr_err("No cpu device for CPU %d\n", cpu);
- return -ENODEV;
- }
- np = cpu_dev->of_node;
- if (!np) {
- pr_err("Failed to find cpu%d device node\n", cpu);
- return -ENOENT;
- }
-
- while (index < cache_leaves(cpu)) {
- this_leaf = this_cpu_ci->info_list + index;
- if (this_leaf->level != 1)
- np = of_find_next_cache_node(np);
- else
- np = of_node_get(np);/* cpu node itself */
- if (!np)
- break;
- this_leaf->of_node = np;
- index++;
- }
-
- if (index != cache_leaves(cpu)) /* not all OF nodes populated */
- return -ENOENT;
-
- return 0;
-}
-
static inline bool cache_leaves_are_shared(struct cacheinfo *this_leaf,
struct cacheinfo *sib_leaf)
{
@@ -213,6 +173,46 @@ static void cache_of_override_properties(unsigned int cpu)
cache_associativity(this_leaf);
}
}
+
+static int cache_setup_of_node(unsigned int cpu)
+{
+ struct device_node *np;
+ struct cacheinfo *this_leaf;
+ struct device *cpu_dev = get_cpu_device(cpu);
+ struct cpu_cacheinfo *this_cpu_ci = get_cpu_cacheinfo(cpu);
+ unsigned int index = 0;
+
+ /* skip if of_node is already populated */
+ if (this_cpu_ci->info_list->of_node)
+ return 0;
+
+ if (!cpu_dev) {
+ pr_err("No cpu device for CPU %d\n", cpu);
+ return -ENODEV;
+ }
+ np = cpu_dev->of_node;
+ if (!np) {
+ pr_err("Failed to find cpu%d device node\n", cpu);
+ return -ENOENT;
+ }
+
+ while (index < cache_leaves(cpu)) {
+ this_leaf = this_cpu_ci->info_list + index;
+ if (this_leaf->level != 1)
+ np = of_find_next_cache_node(np);
+ else
+ np = of_node_get(np);/* cpu node itself */
+ if (!np)
+ break;
+ this_leaf->of_node = np;
+ index++;
+ }
+
+ if (index != cache_leaves(cpu)) /* not all OF nodes populated */
+ return -ENOENT;
+
+ return 0;
+}
#else
static void cache_of_override_properties(unsigned int cpu) { }
static inline int cache_setup_of_node(unsigned int cpu) { return 0; }
--
2.13.5
^ permalink raw reply related
* [PATCH v6 00/12] Support PPTT for ARM64
From: Jeremy Linton @ 2018-01-13 0:59 UTC (permalink / raw)
To: linux-arm-kernel
This patch set is dependent on "[14/15] ACPICA: ACPI 6.2: Additional
PPTT flags" https://patchwork.kernel.org/patch/10064191/
ACPI 6.2 adds the Processor Properties Topology Table (PPTT), which is
used to describe the processor and cache topology. Ideally it is
used to extend/override information provided by the hardware, but
right now ARM64 is entirely dependent on firmware provided tables.
This patch parses the table for the cache topology and CPU topology.
When we enable ACPI/PPTT for arm64 we map the physical_id to the
PPTT node flagged as the physical package by the firmware.
This results in topologies that match what the remainder of the
system expects.
For example on juno:
[root at mammon-juno-rh topology]# lstopo-no-graphics
Package L#0
L2 L#0 (1024KB)
L1d L#0 (32KB) + L1i L#0 (32KB) + Core L#0 + PU L#0 (P#0)
L1d L#1 (32KB) + L1i L#1 (32KB) + Core L#1 + PU L#1 (P#1)
L1d L#2 (32KB) + L1i L#2 (32KB) + Core L#2 + PU L#2 (P#2)
L1d L#3 (32KB) + L1i L#3 (32KB) + Core L#3 + PU L#3 (P#3)
L2 L#1 (2048KB)
L1d L#4 (32KB) + L1i L#4 (48KB) + Core L#4 + PU L#4 (P#4)
L1d L#5 (32KB) + L1i L#5 (48KB) + Core L#5 + PU L#5 (P#5)
HostBridge L#0
PCIBridge
PCIBridge
PCIBridge
PCI 1095:3132
Block(Disk) L#0 "sda"
PCIBridge
PCI 1002:68f9
GPU L#1 "renderD128"
GPU L#2 "card0"
GPU L#3 "controlD64"
PCIBridge
PCI 11ab:4380
Net L#4 "enp8s0"
Git tree at:
http://linux-arm.org/git?p=linux-jlinton.git
branch: pptt_v6
v5->v6:
Add additional patches which re-factor how the initial DT code sets
up the cacheinfo structure so that its not as dependent on the
of_node stored in that tree. Once that is done we rename it
for use with the ACPI code.
Additionally there were a fair number of minor name/location/etc
tweaks scattered about made in response to review comments.
v4->v5:
Update the cache type from NOCACHE to UNIFIED when all the cache
attributes we update are valid. This fixes a problem where caches
which are entirely created by the PPTT don't show up in lstopo.
Give the PPTT its own firmware_node in the cache structure instead of
sharing it with the of_node.
Move some pieces around between patches.
v3->v4:
Suppress the "Found duplicate cache level/type..." message if the
duplicate cache entry is actually a duplicate node. This allows cases
like L1I and L1D nodes that point at the same L2 node to be accepted
without the warning.
Remove cluster/physical split code. Add a patch to rename cluster_id
so that its clear the topology may not be referring to a cluster.
Add additional ACPICA patch for the PPTT cache properties. This matches
an outstanding ACPICA pull that should be merged in the near future.
Replace a number of (struct*)((u8*)ptr+offset) constructs with ACPI_ADD_PTR
Split out the topology parsing into an additional patch.
Tweak the cpu topology code to terminate on either a level, or a flag.
Add an additional function which retrives the physical package id
for a given cpu.
Various other comments/tweaks.
v2->v3:
Remove valid bit check on leaf nodes. Now simply being a leaf node
is sufficient to verify the processor id against the ACPI
processor ids (gotten from MADT).
Use the acpi processor for the "level 0" Id. This makes the /sys
visible core/thread ids more human readable if the firmware uses
small consecutive values for processor ids.
Added PPTT to the list of injectable ACPI tables.
Fix bug which kept the code from using the processor node as intended
in v2, caused by misuse of git rebase/fixup.
v1->v2:
The parser keys off the acpi_pptt_processor node to determine
unique cache's rather than the acpi_pptt_cache referenced by the
processor node. This allows PPTT tables which "share" cache nodes
across cpu nodes despite not being a shared cache.
Jeremy Linton (12):
drivers: base: cacheinfo: move cache_setup_of_node()
drivers: base: cacheinfo: setup DT cache properties early
cacheinfo: rename of_node to fw_unique
arm64/acpi: Create arch specific cpu to acpi id helper
ACPI/PPTT: Add Processor Properties Topology Table parsing
ACPI: Enable PPTT support on ARM64
drivers: base cacheinfo: Add support for ACPI based firmware tables
arm64: Add support for ACPI based firmware tables
ACPI/PPTT: Add topology parsing code
arm64: topology: rename cluster_id
arm64: topology: enable ACPI/PPTT based CPU topology
ACPI: Add PPTT to injectable table list
arch/arm64/Kconfig | 1 +
arch/arm64/include/asm/acpi.h | 4 +
arch/arm64/include/asm/topology.h | 4 +-
arch/arm64/kernel/cacheinfo.c | 15 +-
arch/arm64/kernel/topology.c | 73 ++++-
arch/riscv/kernel/cacheinfo.c | 3 +-
drivers/acpi/Kconfig | 3 +
drivers/acpi/Makefile | 1 +
drivers/acpi/pptt.c | 592 ++++++++++++++++++++++++++++++++++++++
drivers/acpi/tables.c | 3 +-
drivers/base/cacheinfo.c | 159 +++++-----
include/linux/acpi.h | 3 +
include/linux/cacheinfo.h | 18 +-
13 files changed, 772 insertions(+), 107 deletions(-)
create mode 100644 drivers/acpi/pptt.c
--
2.13.5
^ permalink raw reply
* [PATCH v1] EDAC, armv8: Add Cache Error Reporting driver for ARMv8 processors
From: Kyle Yan @ 2018-01-13 0:50 UTC (permalink / raw)
To: linux-arm-kernel
Interrupt based EDAC driver for ARMv8 processors that implement
RAS for error detection of CPU caches and lso allows optional polling
of error syndrome registers if interrupts are not supported.
Signed-off-by: Kyle Yan <kyan@codeaurora.org>
---
drivers/edac/Kconfig | 21 ++
drivers/edac/Makefile | 1 +
drivers/edac/armv8_edac.c | 489 ++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 511 insertions(+)
create mode 100644 drivers/edac/armv8_edac.c
diff --git a/drivers/edac/Kconfig b/drivers/edac/Kconfig
index 96afb2a..47a68e3 100644
--- a/drivers/edac/Kconfig
+++ b/drivers/edac/Kconfig
@@ -457,4 +457,25 @@ config EDAC_XGENE
Support for error detection and correction on the
APM X-Gene family of SOCs.
+config EDAC_ARMV8
+ depends on (ARM || ARM64)
+ tristate "ARMv8 L1/L2/L3/SCU Caches ECC"
+ help
+ Support for error detection and correction on ARMv8 cores
+ supporting RAS features. Reports errors caught by ARMv8
+ ECC mechanism.
+ For debugging issues having to do with stability and overall system
+ health, you should probably say 'Y' here.
+
+config EDAC_ARMV8_POLL
+ depends on EDAC_ARMV8
+ bool "Poll on ARMv8 ECC registers"
+ help
+ This option chooses whether or not you want to poll on the Kryo3xx
+ ECC registers. When this is enabled, the polling rate can be set as
+ a module parameter. By default, it will call the polling function
+ every second.
+ This option should only be used if the associated interrupt lines
+ are not enabled.
+
endif # EDAC
diff --git a/drivers/edac/Makefile b/drivers/edac/Makefile
index 0fd9ffa..57113ba 100644
--- a/drivers/edac/Makefile
+++ b/drivers/edac/Makefile
@@ -43,6 +43,7 @@ obj-$(CONFIG_EDAC_IE31200) += ie31200_edac.o
obj-$(CONFIG_EDAC_X38) += x38_edac.o
obj-$(CONFIG_EDAC_I82860) += i82860_edac.o
obj-$(CONFIG_EDAC_R82600) += r82600_edac.o
+obj-$(CONFIG_EDAC_ARMV8) += armv8_edac.o
amd64_edac_mod-y := amd64_edac.o
amd64_edac_mod-$(CONFIG_EDAC_DEBUG) += amd64_edac_dbg.o
diff --git a/drivers/edac/armv8_edac.c b/drivers/edac/armv8_edac.c
new file mode 100644
index 0000000..d986c47
--- /dev/null
+++ b/drivers/edac/armv8_edac.c
@@ -0,0 +1,489 @@
+/* Copyright (c) 2016-2018, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that 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 <linux/kernel.h>
+#include <linux/edac.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/smp.h>
+#include <linux/cpu.h>
+#include <linux/cpu_pm.h>
+#include <linux/interrupt.h>
+#include <linux/of_irq.h>
+
+#include "edac_mc.h"
+#include "edac_device.h"
+
+static int poll_msec = 1000;
+module_param(poll_msec, int, 0444);
+
+static bool panic_on_ue = 0;
+module_param_named(panic_on_ue, panic_on_ue, bool, 0664);
+
+#define L1 0x0
+#define L2 0x1
+#define L3 0x2
+
+#define EDAC_CPU "armv8_edac"
+
+#define ERRXSTATUS_VALID(a) ((a >> 30) & 0x1)
+#define ERRXSTATUS_UE(a) ((a >> 29) & 0x1)
+#define ERRXSTATUS_SERR(a) (a & 0xFF)
+
+#define ERRXMISC_LVL(a) ((a >> 1) & 0x7)
+#define ERRXMISC_WAY(a) ((a >> 28) & 0xF)
+
+#define ERRXCTLR_ENABLE 0x10f
+#define ERRXMISC_OVERFLOW 0x7F7F00000000ULL
+
+static inline void set_errxctlr_el1(void)
+{
+ asm volatile("msr s3_0_c5_c4_1, %0" : : "r" (ERRXCTLR_ENABLE));
+}
+
+static inline void set_errxmisc_overflow(void)
+{
+ asm volatile("msr s3_0_c5_c5_0, %0" : : "r" (ERRXMISC_OVERFLOW));
+}
+
+static inline void write_errselr_el1(u64 val)
+{
+ asm volatile("msr s3_0_c5_c3_1, %0" : : "r" (val));
+}
+
+static inline u64 read_errxstatus_el1(void)
+{
+ u64 val;
+
+ asm volatile("mrs %0, s3_0_c5_c4_2" : "=r" (val));
+ return val;
+}
+
+static inline u64 read_errxmisc_el1(void)
+{
+ u64 val;
+
+ asm volatile("mrs %0, s3_0_c5_c5_0" : "=r" (val));
+ return val;
+}
+
+static inline void clear_errxstatus_valid(u64 val)
+{
+ asm volatile("msr s3_0_c5_c4_2, %0" : : "r" (val));
+}
+
+struct errors_edac {
+ const char * const msg;
+ void (*func)(struct edac_device_ctl_info *edac_dev,
+ int inst_nr, int block_nr, const char *msg);
+};
+
+static const struct errors_edac errors[] = {
+ { "L1 Correctable Error", edac_device_handle_ce },
+ { "L1 Uncorrectable Error", edac_device_handle_ue },
+ { "L2 Correctable Error", edac_device_handle_ce },
+ { "L2 Uncorrectable Error", edac_device_handle_ue },
+ { "L3 Correctable Error", edac_device_handle_ce },
+ { "L3 Uncorrectable Error", edac_device_handle_ue },
+};
+
+#define L1_CE 0
+#define L1_UE 1
+#define L2_CE 2
+#define L2_UE 3
+#define L3_CE 4
+#define L3_UE 5
+
+#define DATA_BUF_ERR 0x2
+#define CACHE_DATA_ERR 0x6
+#define CACHE_TAG_DIRTY_ERR 0x7
+#define TLB_PARITY_ERR_DATA 0x8
+#define TLB_PARITY_ERR_TAG 0x9
+#define BUS_ERROR 0x12
+
+struct erp_drvdata {
+ struct edac_device_ctl_info *edev_ctl;
+ struct erp_drvdata __percpu **erp_cpu_drvdata;
+ struct notifier_block nb_pm;
+ int ppi;
+};
+
+static struct erp_drvdata *panic_handler_drvdata;
+
+static DEFINE_SPINLOCK(armv8_edac_lock);
+
+static void l1_l2_irq_enable(void *info)
+{
+ int irq = *(int *)info;
+
+ enable_percpu_irq(irq, IRQ_TYPE_LEVEL_HIGH);
+}
+
+static int request_erp_irq(struct platform_device *pdev, const char *propname,
+ const char *desc, irq_handler_t handler,
+ void *ed, int percpu)
+{
+ int rc;
+ struct resource *r;
+ struct erp_drvdata *drv = ed;
+
+ r = platform_get_resource_byname(pdev, IORESOURCE_IRQ, propname);
+
+ if (!r) {
+ pr_err("ARMv8 CPU ERP: Could not find <%s> IRQ property. Proceeding anyway.\n",
+ propname);
+ goto out;
+ }
+
+ if (!percpu) {
+ rc = devm_request_threaded_irq(&pdev->dev, r->start, NULL,
+ handler,
+ IRQF_ONESHOT | IRQF_TRIGGER_HIGH,
+ desc,
+ ed);
+
+ if (rc) {
+ pr_err("ARMv8 CPU ERP: Failed to request IRQ %d: %d (%s / %s). Proceeding anyway.\n",
+ (int) r->start, rc, propname, desc);
+ goto out;
+ }
+
+ } else {
+ drv->erp_cpu_drvdata = alloc_percpu(struct erp_drvdata *);
+ if (!drv->erp_cpu_drvdata) {
+ pr_err("Failed to allocate percpu erp data\n");
+ goto out;
+ }
+
+ *raw_cpu_ptr(drv->erp_cpu_drvdata) = drv;
+ rc = request_percpu_irq(r->start, handler, desc,
+ drv->erp_cpu_drvdata);
+
+ if (rc) {
+ pr_err("ARMv8 CPU ERP: Failed to request IRQ %d: %d (%s / %s). Proceeding anyway.\n",
+ (int) r->start, rc, propname, desc);
+ goto out_free;
+ }
+
+ drv->ppi = r->start;
+ on_each_cpu(l1_l2_irq_enable, &(r->start), 1);
+ }
+
+ return 0;
+
+out_free:
+ free_percpu(drv->erp_cpu_drvdata);
+ drv->erp_cpu_drvdata = NULL;
+out:
+ return 1;
+}
+
+static void dump_err_reg(int errorcode, int level, u64 errxstatus, u64 errxmisc,
+ struct edac_device_ctl_info *edev_ctl)
+{
+ edac_printk(KERN_CRIT, EDAC_CPU, "ERRXSTATUS_EL1: %llx\n", errxstatus);
+ edac_printk(KERN_CRIT, EDAC_CPU, "ERRXMISC_EL1: %llx\n", errxmisc);
+ edac_printk(KERN_CRIT, EDAC_CPU, "Cache level: L%d\n", level+1);
+
+ switch (ERRXSTATUS_SERR(errxstatus)) {
+ case DATA_BUF_ERR:
+ edac_printk(KERN_CRIT, EDAC_CPU, "ECC Error from internal data buffer\n");
+ break;
+
+ case CACHE_DATA_ERR:
+ edac_printk(KERN_CRIT, EDAC_CPU, "ECC Error from cache data RAM\n");
+ break;
+
+ case CACHE_TAG_DIRTY_ERR:
+ edac_printk(KERN_CRIT, EDAC_CPU, "ECC Error from cache tag or dirty RAM\n");
+ break;
+
+ case TLB_PARITY_ERR_DATA:
+ edac_printk(KERN_CRIT, EDAC_CPU, "Parity error on TLB DATA RAM\n");
+ break;
+
+ case TLB_PARITY_ERR_TAG:
+ edac_printk(KERN_CRIT, EDAC_CPU, "Parity error on TLB TAG RAM\n");
+ break;
+
+ case BUS_ERROR:
+ edac_printk(KERN_CRIT, EDAC_CPU, "Bus Error\n");
+ break;
+ }
+
+ if (level == L3)
+ edac_printk(KERN_CRIT, EDAC_CPU,
+ "Way: %d\n", (int) ERRXMISC_WAY(errxmisc));
+ else
+ edac_printk(KERN_CRIT, EDAC_CPU,
+ "Way: %d\n", (int) ERRXMISC_WAY(errxmisc) >> 2);
+
+ edev_ctl->panic_on_ue = panic_on_ue;
+ errors[errorcode].func(edev_ctl, smp_processor_id(),
+ level, errors[errorcode].msg);
+}
+
+static void armv8_parse_l1_l2_cache_error(u64 errxstatus, u64 errxmisc,
+ struct edac_device_ctl_info *edev_ctl)
+{
+ switch (ERRXMISC_LVL(errxmisc)) {
+ case L1:
+ if (ERRXSTATUS_UE(errxstatus))
+ dump_err_reg(L1_UE, L1, errxstatus, errxmisc,
+ edev_ctl);
+ else
+ dump_err_reg(L1_CE, L1, errxstatus, errxmisc,
+ edev_ctl);
+ break;
+ case L2:
+ if (ERRXSTATUS_UE(errxstatus))
+ dump_err_reg(L2_UE, L2, errxstatus, errxmisc,
+ edev_ctl);
+ else
+ dump_err_reg(L2_CE, L2, errxstatus, errxmisc,
+ edev_ctl);
+ break;
+ default:
+ edac_printk(KERN_CRIT, EDAC_CPU, "Unknown ERRXMISC_LVL value\n");
+ }
+}
+
+static bool armv8_check_l1_l2_ecc(void *info)
+{
+ struct edac_device_ctl_info *edev_ctl = info;
+ u64 errxstatus;
+ u64 errxmisc;
+ unsigned long flags;
+
+ spin_lock_irqsave(&armv8_edac_lock, flags);
+ write_errselr_el1(0);
+ errxstatus = read_errxstatus_el1();
+
+ if (ERRXSTATUS_VALID(errxstatus)) {
+ errxmisc = read_errxmisc_el1();
+ edac_printk(KERN_CRIT, EDAC_CPU,
+ "CPU%d detected a L1/L2 cache error\n",
+ smp_processor_id());
+
+ armv8_parse_l1_l2_cache_error(errxstatus, errxmisc, edev_ctl);
+ clear_errxstatus_valid(errxstatus);
+ spin_unlock_irqrestore(&armv8_edac_lock, flags);
+ return true;
+ }
+ spin_unlock_irqrestore(&armv8_edac_lock, flags);
+ return false;
+}
+
+static bool armv8_check_l3_scu_error(struct edac_device_ctl_info *edev_ctl)
+{
+ u64 errxstatus = 0;
+ u64 errxmisc = 0;
+ unsigned long flags;
+
+ spin_lock_irqsave(&armv8_edac_lock, flags);
+ write_errselr_el1(1);
+ errxstatus = read_errxstatus_el1();
+ errxmisc = read_errxmisc_el1();
+
+ if (ERRXSTATUS_VALID(errxstatus) &&
+ ERRXMISC_LVL(errxmisc) == L3) {
+ if (ERRXSTATUS_UE(errxstatus)) {
+ edac_printk(KERN_CRIT, EDAC_CPU, "Detected L3 uncorrectable error\n");
+ dump_err_reg(L3_UE, L3, errxstatus, errxmisc,
+ edev_ctl);
+ } else {
+ edac_printk(KERN_CRIT, EDAC_CPU, "Detected L3 correctable error\n");
+ dump_err_reg(L3_CE, L3, errxstatus, errxmisc,
+ edev_ctl);
+ }
+
+ clear_errxstatus_valid(errxstatus);
+ spin_unlock_irqrestore(&armv8_edac_lock, flags);
+ return true;
+ }
+ spin_unlock_irqrestore(&armv8_edac_lock, flags);
+ return false;
+}
+
+static void armv8_check_l1_l2_ecc_helper(void *info)
+{
+ armv8_check_l1_l2_ecc(info);
+}
+
+void armv8_poll_cache_errors(struct edac_device_ctl_info *edev_ctl)
+{
+ int cpu;
+
+ if (!edev_ctl)
+ edev_ctl = panic_handler_drvdata->edev_ctl;
+
+ armv8_check_l3_scu_error(edev_ctl);
+ for_each_possible_cpu(cpu) {
+ smp_call_function_single(cpu, armv8_check_l1_l2_ecc_helper,
+ edev_ctl, 0);
+ }
+}
+
+static irqreturn_t armv8_l1_l2_handler(int irq, void *drvdata)
+{
+ if (armv8_check_l1_l2_ecc(panic_handler_drvdata->edev_ctl))
+ return IRQ_HANDLED;
+ return IRQ_NONE;
+}
+
+static irqreturn_t armv8_l3_scu_handler(int irq, void *drvdata)
+{
+ struct erp_drvdata *drv = drvdata;
+ struct edac_device_ctl_info *edev_ctl = drv->edev_ctl;
+
+ if (armv8_check_l3_scu_error(edev_ctl))
+ return IRQ_HANDLED;
+ return IRQ_NONE;
+}
+
+static void initialize_registers(void *info)
+{
+ set_errxctlr_el1();
+ set_errxmisc_overflow();
+}
+
+static void init_regs_on_cpu(bool all_cpus)
+{
+ int cpu;
+
+ write_errselr_el1(0);
+ if (all_cpus) {
+ for_each_possible_cpu(cpu)
+ smp_call_function_single(cpu, initialize_registers,
+ NULL, 1);
+ } else {
+ initialize_registers(NULL);
+ }
+
+ write_errselr_el1(1);
+ initialize_registers(NULL);
+}
+
+static int armv8_pmu_cpu_pm_notify(struct notifier_block *self,
+ unsigned long action, void *v)
+{
+ switch (action) {
+ case CPU_PM_EXIT:
+ init_regs_on_cpu(false);
+ armv8_check_l3_scu_error(panic_handler_drvdata->edev_ctl);
+ armv8_check_l1_l2_ecc(panic_handler_drvdata->edev_ctl);
+ break;
+ }
+
+ return NOTIFY_OK;
+}
+
+static int armv8_cpu_erp_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct erp_drvdata *drv;
+ int rc = 0;
+ int fail = 0;
+
+ init_regs_on_cpu(true);
+
+ drv = devm_kzalloc(dev, sizeof(*drv), GFP_KERNEL);
+
+ if (!drv)
+ return -ENOMEM;
+
+ drv->edev_ctl = edac_device_alloc_ctl_info(0, "cpu",
+ num_possible_cpus(), "L", 3, 1, NULL, 0,
+ edac_device_alloc_index());
+
+ if (!drv->edev_ctl)
+ return -ENOMEM;
+
+ if (IS_ENABLED(CONFIG_EDAC_ARMV8_POLL)) {
+ drv->edev_ctl->edac_check = armv8_poll_cache_errors;
+ drv->edev_ctl->poll_msec = poll_msec;
+ }
+
+ drv->edev_ctl->dev = dev;
+ drv->edev_ctl->mod_name = dev_name(dev);
+ drv->edev_ctl->dev_name = dev_name(dev);
+ drv->edev_ctl->ctl_name = "cache";
+ drv->edev_ctl->panic_on_ue = panic_on_ue;
+ drv->nb_pm.notifier_call = armv8_pmu_cpu_pm_notify;
+ platform_set_drvdata(pdev, drv);
+
+ rc = edac_device_add_device(drv->edev_ctl);
+ if (rc)
+ goto out_mem;
+
+ panic_handler_drvdata = drv;
+
+ if (!IS_ENABLED(CONFIG_EDAC_ARMV8_POLL)) {
+ fail += request_erp_irq(pdev, "l1-l2-irq",
+ "l1_l2_irq",
+ armv8_l1_l2_handler, drv, 1);
+
+ fail += request_erp_irq(pdev, "l3-scu-irq",
+ "l3_scu_irq",
+ armv8_l3_scu_handler, drv, 0);
+
+ if (fail == of_irq_count(dev->of_node)) {
+ pr_err("ERP: Could not request any IRQs. Giving up.\n");
+ rc = -ENODEV;
+ goto out_dev;
+ }
+ }
+
+ cpu_pm_register_notifier(&(drv->nb_pm));
+
+ return 0;
+
+out_dev:
+ edac_device_del_device(dev);
+out_mem:
+ edac_device_free_ctl_info(drv->edev_ctl);
+ return rc;
+}
+
+static int armv8_cpu_erp_remove(struct platform_device *pdev)
+{
+ struct erp_drvdata *drv = dev_get_drvdata(&pdev->dev);
+ struct edac_device_ctl_info *edac_ctl = drv->edev_ctl;
+
+ if (drv->erp_cpu_drvdata) {
+ free_percpu_irq(drv->ppi, drv->erp_cpu_drvdata);
+ free_percpu(drv->erp_cpu_drvdata);
+ }
+
+ edac_device_del_device(edac_ctl->dev);
+ edac_device_free_ctl_info(edac_ctl);
+
+ return 0;
+}
+
+static const struct of_device_id armv8_cpu_erp_match_table[] = {
+ { .compatible = "arm,armv8-cpu-erp" },
+ { }
+};
+
+static struct platform_driver armv8_cpu_erp_driver = {
+ .probe = armv8_cpu_erp_probe,
+ .remove = armv8_cpu_erp_remove,
+ .driver = {
+ .name = "armv8_cpu_cache_erp",
+ .of_match_table = of_match_ptr(armv8_cpu_erp_match_table),
+ },
+};
+module_platform_driver(armv8_cpu_erp_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("ARMv8 EDAC driver");
--
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
a Linux Foundation Collaborative Project
^ permalink raw reply related
* [RFC PATCH 0/2] drivers: clk: Add ZynqMP clock driver support
From: Jolly Shah @ 2018-01-12 23:52 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <bb8d1c16-f10e-5e5e-8a7b-54851e5d9de7@arm.com>
Hi Sudeep,
> -----Original Message-----
> From: Sudeep Holla [mailto:sudeep.holla at arm.com]
> Sent: Friday, January 12, 2018 3:47 AM
> To: Jolly Shah <JOLLYS@xilinx.com>; michal.simek at xilinx.com; linux-
> clk at vger.kernel.org
> Cc: Sudeep Holla <sudeep.holla@arm.com>; mturquette at baylibre.com;
> sboyd at codeaurora.org; linux-kernel at vger.kernel.org; linux-arm-
> kernel at lists.infradead.org
> Subject: Re: [RFC PATCH 0/2] drivers: clk: Add ZynqMP clock driver support
>
>
>
> On 11/01/18 20:29, Jolly Shah wrote:
> > Hi Sudeep,
> >
> > There are 3 things:
> >
> > 1> IPI mailbox transport - series is already posted
> > https://patchwork.kernel.org/patch/10145795/
> > 2> EEMI - Posted by me below
> > https://patchwork.kernel.org/patch/10150665/
> > 3> EEMI Users
> > Currently posted as RFC as they are dependent on #2.
> > OK, can #2 and #3 can be put together until initial review ?
>
> > #2 doesn?t use mailbox so it is a posted as a separate series.
>
> OK, does it use smc ? If so, the point is treat it as smc mailbox so that smc is just
> a transport and EEMI can be still used with other transport technically.
>
> --
> Regards,
> Sudeep
I am not sure if I get you correctly.
Yes, EEMI uses SMC interface to communicate to PMU. Do you suggest to use Andre's SMC mailbox driver as transport for EEMI?
Thanks,
Jolly Shah
^ permalink raw reply
* [PATCH] ARM: dts: n900: Add aliases for lcd and tvout displays
From: Tony Lindgren @ 2018-01-12 23:34 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1514931434-13192-1-git-send-email-ivo.g.dimitrov.75@gmail.com>
* Ivaylo Dimitrov <ivo.g.dimitrov.75@gmail.com> [180102 14:20]:
> When both lcd and tv are enabled, the order in which they will be probed is
> unknown, so it might happen (and it happens in reality) that tv is
> configured as display0 and lcd as display1, which results in nothing
> displayed on lcd, as display1 is disabled by default.
>
> Fix that by providing correct aliases for lcd and tv
Applying into omap-for-v4.16/dt-clk thanks.
Regards,
Tony
^ permalink raw reply
* [PATCH v2 04/12] drm/bridge/synopsys: dw-hdmi: Export some PHY related functions
From: Laurent Pinchart @ 2018-01-12 23:04 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20180110192512.19684-5-jernej.skrabec@siol.net>
Hi Jernej,
Thank you for th epatch.
On Wednesday, 10 January 2018 21:25:04 EET Jernej Skrabec wrote:
> Parts of PHY code could be useful also for custom PHYs. For example,
> Allwinner A83T has custom PHY which is probably Synopsys gen2 PHY
> with few additional memory mapped registers, so most of the Synopsys PHY
> related code could be reused.
>
> Functions exported here are actually not specific to Synopsys PHYs but
> to DWC HDMI controller PHY interface. This means that even if the PHY is
> completely custom, i.e. not designed by Synopsys, exported functions can
> be useful.
>
> Signed-off-by: Jernej Skrabec <jernej.skrabec@siol.net>
> ---
> drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 44 ++++++++++++++++++++--------
> include/drm/bridge/dw_hdmi.h | 11 ++++++++
> 2 files changed, 41 insertions(+), 14 deletions(-)
>
> diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
> b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c index
> 7ca14d7325b5..7d80f4b56683 100644
> --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
> +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
> @@ -1037,19 +1037,21 @@ static void dw_hdmi_phy_enable_svsret(struct dw_hdmi
> *hdmi, u8 enable) HDMI_PHY_CONF0_SVSRET_MASK);
> }
>
> -static void dw_hdmi_phy_gen2_pddq(struct dw_hdmi *hdmi, u8 enable)
> +void dw_hdmi_phy_gen2_pddq(struct dw_hdmi *hdmi, u8 enable)
> {
> hdmi_mask_writeb(hdmi, enable, HDMI_PHY_CONF0,
> HDMI_PHY_CONF0_GEN2_PDDQ_OFFSET,
> HDMI_PHY_CONF0_GEN2_PDDQ_MASK);
> }
> +EXPORT_SYMBOL_GPL(dw_hdmi_phy_gen2_pddq);
>
> -static void dw_hdmi_phy_gen2_txpwron(struct dw_hdmi *hdmi, u8 enable)
> +void dw_hdmi_phy_gen2_txpwron(struct dw_hdmi *hdmi, u8 enable)
> {
> hdmi_mask_writeb(hdmi, enable, HDMI_PHY_CONF0,
> HDMI_PHY_CONF0_GEN2_TXPWRON_OFFSET,
> HDMI_PHY_CONF0_GEN2_TXPWRON_MASK);
> }
> +EXPORT_SYMBOL_GPL(dw_hdmi_phy_gen2_txpwron);
>
> static void dw_hdmi_phy_sel_data_en_pol(struct dw_hdmi *hdmi, u8 enable)
> {
> @@ -1065,6 +1067,22 @@ static void dw_hdmi_phy_sel_interface_control(struct
> dw_hdmi *hdmi, u8 enable) HDMI_PHY_CONF0_SELDIPIF_MASK);
> }
>
> +void dw_hdmi_phy_reset(struct dw_hdmi *hdmi)
> +{
> + /* PHY reset. The reset signal is active high on Gen2 PHYs. */
> + hdmi_writeb(hdmi, HDMI_MC_PHYRSTZ_PHYRSTZ, HDMI_MC_PHYRSTZ);
> + hdmi_writeb(hdmi, 0, HDMI_MC_PHYRSTZ);
> +}
> +EXPORT_SYMBOL_GPL(dw_hdmi_phy_reset);
> +
> +void dw_hdmi_phy_i2c_set_addr(struct dw_hdmi *hdmi, u8 address)
> +{
> + hdmi_phy_test_clear(hdmi, 1);
> + hdmi_writeb(hdmi, address, HDMI_PHY_I2CM_SLAVE_ADDR);
> + hdmi_phy_test_clear(hdmi, 0);
> +}
> +EXPORT_SYMBOL_GPL(dw_hdmi_phy_i2c_set_addr);
> +
> static void dw_hdmi_phy_power_off(struct dw_hdmi *hdmi)
> {
> const struct dw_hdmi_phy_data *phy = hdmi->phy.data;
> @@ -1203,16 +1221,11 @@ static int hdmi_phy_configure(struct dw_hdmi *hdmi)
> if (phy->has_svsret)
> dw_hdmi_phy_enable_svsret(hdmi, 1);
>
> - /* PHY reset. The reset signal is active high on Gen2 PHYs. */
> - hdmi_writeb(hdmi, HDMI_MC_PHYRSTZ_PHYRSTZ, HDMI_MC_PHYRSTZ);
> - hdmi_writeb(hdmi, 0, HDMI_MC_PHYRSTZ);
> + dw_hdmi_phy_reset(hdmi);
>
> hdmi_writeb(hdmi, HDMI_MC_HEACPHY_RST_ASSERT, HDMI_MC_HEACPHY_RST);
>
> - hdmi_phy_test_clear(hdmi, 1);
> - hdmi_writeb(hdmi, HDMI_PHY_I2CM_SLAVE_ADDR_PHY_GEN2,
> - HDMI_PHY_I2CM_SLAVE_ADDR);
> - hdmi_phy_test_clear(hdmi, 0);
> + dw_hdmi_phy_i2c_set_addr(hdmi, HDMI_PHY_I2CM_SLAVE_ADDR_PHY_GEN2);
>
> /* Write to the PHY as configured by the platform */
> if (pdata->configure_phy)
> @@ -1251,15 +1264,16 @@ static void dw_hdmi_phy_disable(struct dw_hdmi
> *hdmi, void *data) dw_hdmi_phy_power_off(hdmi);
> }
>
> -static enum drm_connector_status dw_hdmi_phy_read_hpd(struct dw_hdmi *hdmi,
> - void *data)
> +enum drm_connector_status dw_hdmi_phy_read_hpd(struct dw_hdmi *hdmi,
> + void *data)
> {
> return hdmi_readb(hdmi, HDMI_PHY_STAT0) & HDMI_PHY_HPD ?
> connector_status_connected : connector_status_disconnected;
> }
> +EXPORT_SYMBOL_GPL(dw_hdmi_phy_read_hpd);
>
> -static void dw_hdmi_phy_update_hpd(struct dw_hdmi *hdmi, void *data,
> - bool force, bool disabled, bool rxsense)
> +void dw_hdmi_phy_update_hpd(struct dw_hdmi *hdmi, void *data,
> + bool force, bool disabled, bool rxsense)
> {
> u8 old_mask = hdmi->phy_mask;
>
> @@ -1271,8 +1285,9 @@ static void dw_hdmi_phy_update_hpd(struct dw_hdmi
> *hdmi, void *data, if (old_mask != hdmi->phy_mask)
> hdmi_writeb(hdmi, hdmi->phy_mask, HDMI_PHY_MASK0);
> }
> +EXPORT_SYMBOL_GPL(dw_hdmi_phy_update_hpd);
>
> -static void dw_hdmi_phy_setup_hpd(struct dw_hdmi *hdmi, void *data)
> +void dw_hdmi_phy_setup_hpd(struct dw_hdmi *hdmi, void *data)
> {
> /*
> * Configure the PHY RX SENSE and HPD interrupts polarities and clear
> @@ -1291,6 +1306,7 @@ static void dw_hdmi_phy_setup_hpd(struct dw_hdmi
> *hdmi, void *data) hdmi_writeb(hdmi, ~(HDMI_IH_PHY_STAT0_HPD |
> HDMI_IH_PHY_STAT0_RX_SENSE), HDMI_IH_MUTE_PHY_STAT0);
> }
> +EXPORT_SYMBOL_GPL(dw_hdmi_phy_setup_hpd);
>
> static const struct dw_hdmi_phy_ops dw_hdmi_synopsys_phy_ops = {
> .init = dw_hdmi_phy_init,
> diff --git a/include/drm/bridge/dw_hdmi.h b/include/drm/bridge/dw_hdmi.h
> index 182f83283e24..4a35e5065f6f 100644
> --- a/include/drm/bridge/dw_hdmi.h
> +++ b/include/drm/bridge/dw_hdmi.h
> @@ -157,7 +157,18 @@ void dw_hdmi_audio_enable(struct dw_hdmi *hdmi);
> void dw_hdmi_audio_disable(struct dw_hdmi *hdmi);
>
> /* PHY configuration */
> +void dw_hdmi_phy_i2c_set_addr(struct dw_hdmi *hdmi, u8 address);
> void dw_hdmi_phy_i2c_write(struct dw_hdmi *hdmi, unsigned short data,
> unsigned char addr);
>
> +enum drm_connector_status dw_hdmi_phy_read_hpd(struct dw_hdmi *hdmi,
> + void *data);
> +void dw_hdmi_phy_update_hpd(struct dw_hdmi *hdmi, void *data,
> + bool force, bool disabled, bool rxsense);
> +void dw_hdmi_phy_setup_hpd(struct dw_hdmi *hdmi, void *data);
> +
> +void dw_hdmi_phy_gen2_pddq(struct dw_hdmi *hdmi, u8 enable);
> +void dw_hdmi_phy_gen2_txpwron(struct dw_hdmi *hdmi, u8 enable);
> +void dw_hdmi_phy_reset(struct dw_hdmi *hdmi);
Nitpicking, could you move these last three functions between the I2C and HPD
functions ? They are, as the I2C functions, related to controlling the
interface between the HDMI controller and the PHY (the internal I2C bus and
direct signals), so it would make sense to group them together.
With that fixed,
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Regarding HPD I think we could refactor the code in a cleaner why that
wouldn't require the three functions to be exported, but that shouldn't block
this patch series, it can always be done later.
> #endif /* __IMX_HDMI_H__ */
--
Regards,
Laurent Pinchart
^ permalink raw reply
* PM regression in next
From: Mark Brown @ 2018-01-12 22:59 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20180112224959.GH4821@atomide.com>
On Fri, Jan 12, 2018 at 02:49:59PM -0800, Tony Lindgren wrote:
> * Mark Brown <broonie@kernel.org> [180112 22:11]:
> > Most devices have one regmap per device which can be retrieved with
> > dev_get_regmap(), it's the attempt to use that which I suspect is
> > broken. Like I said snd_soc_codec_init_regmap() ought to fix things if
> > that's the issue.
> OK. Adding Peter to loop as it's his driver after all. Not sure
> how well mixing regmap register access to the same module with
> cached twl4030_read() would work :)
Yes, that local cache is not a super good idea any more and hopefully
redundant.
> Maybe there should also be some big warning happening if
> snd_soc_codec_init_regmap() is now needed and no regmap is
> found?
Some devices just plain don't have registers at all (perhaps GPIOs or
just stub drivers providing capability information). However we should
be screaming loudly about the fact that the I/O we tried to do fails,
that clearly shouldn't be being ignored.
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 488 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20180112/170fff1f/attachment.sig>
^ permalink raw reply
* PM regression in next
From: Tony Lindgren @ 2018-01-12 22:49 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20180112221126.GK21458@sirena.org.uk>
* Mark Brown <broonie@kernel.org> [180112 22:11]:
> On Fri, Jan 12, 2018 at 01:50:10PM -0800, Tony Lindgren wrote:
>
> > > I rather suspect someone would've noticed by now TBH - I suspect this is
> > > more likely to be an issue with the rather baroque code that the TWL
> > > drivers have possibly coupled with the whole multiple I2C devices issue.
>
> > Hmm these calls are in sound/soc/soc-core.c though? But yeah, sure
> > it could be some legacy code issue somewhere..
>
> Most devices have one regmap per device which can be retrieved with
> dev_get_regmap(), it's the attempt to use that which I suspect is
> broken. Like I said snd_soc_codec_init_regmap() ought to fix things if
> that's the issue.
OK. Adding Peter to loop as it's his driver after all. Not sure
how well mixing regmap register access to the same module with
cached twl4030_read() would work :)
Maybe there should also be some big warning happening if
snd_soc_codec_init_regmap() is now needed and no regmap is
found?
> > sound/soc/codecs/cx20442.c
> > sound/soc/codecs/tlv320dac33.c
> > sound/soc/codecs/twl4030.c
> > sound/soc/codecs/twl6040.c
> > sound/soc/codecs/uda1380.c
> > sound/soc/fsl/fsl_ssi.c
>
> > Can you confirm that some of these are still working?
>
> Most of the non-TWL ones have no active users I'm aware of but fsl_ssi.c
> is under active development at the minute, one series was getting some
> successful testing earlier today so if it's broken I'd hope someone
> would have noticed although since it doesn't use DAPM or anything
> snd_soc_read()/write() will never get used. cx20442 was only in a
> single archaic consumer product, uda1380 has been legacy since before I
> was ever involved in ASoC.
OK. sounds like we should just revert the read and write removal
patches for now as they are now known to be incomplete for
non-regmap cases and cause regressions. That way we can deal with
them properly after the merge window and test them.
I'm not sure if adding back just the .read and .write is a safe
revert in all cases but it might be doable.
Regards,
Tony
^ permalink raw reply
* [PATCH 2/4 v7] drm/bridge: Provide a way to embed timing info in bridges
From: Laurent Pinchart @ 2018-01-12 22:41 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20180112074854.9560-2-linus.walleij@linaro.org>
Hi Linus,
Thank you for the patch.
On Friday, 12 January 2018 09:48:52 EET Linus Walleij wrote:
> After some discussion and failed patch sets trying to convey
> the right timing information between the display engine and
> a bridge using the connector, I try instead to use an optional
> timing information container in the bridge itself, so that
> display engines can retrieve it from any bridge and use it to
> determine how to drive outputs.
>
> Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> ---
> ChangeLog v6->v7:
> - Fix the comment style to use the new inline type of kerneldoc
> for struct members.
> - Need an explicit ACK/review by someone on this patch to continue
> with the series...
> ChangeLog v5->v6:
> - Sort forward struct declarations alphabetically
> - Switch to using DRM_BUS_FLAG_PIXDATA_[POS|NEG]EDGE to indicate
> positive or negatice clock samling edge
> ChangeLog ->v5:
> - New patch
> ---
> include/drm/drm_bridge.h | 33 +++++++++++++++++++++++++++++++++
> 1 file changed, 33 insertions(+)
>
> diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h
> index 682d01ba920c..d3c2eea0bb63 100644
> --- a/include/drm/drm_bridge.h
> +++ b/include/drm/drm_bridge.h
> @@ -29,6 +29,7 @@
> #include <drm/drm_modes.h>
>
> struct drm_bridge;
> +struct drm_bridge_timings;
> struct drm_panel;
>
> /**
> @@ -222,6 +223,35 @@ struct drm_bridge_funcs {
> void (*enable)(struct drm_bridge *bridge);
> };
>
> +/**
> + * struct drm_bridge_timings - timing information for the bridge
> + */
> +struct drm_bridge_timings {
> + /**
> + * @sampling_edge:
> + *
> + * Tells whether the bridge samples the digital input signal
> + * from the display engine on the positive or negative edge of the clock,
> + * this should reuse the DRM_BUS_FLAG_PIXDATA_[POS|NEG]EDGE bitwise
> + * flags from the DRM connector (bit 2 and 3 valid).
> + */
> + u32 sampling_edge;
> + /**
> + * @setup_time_ps:
> + *
> + * Defines the time in picoseconds the input data lines must be
> + * stable before the clock edge.
> + */
> + u32 setup_time_ps;
> + /**
> + * @hold_time_ps:
> + *
> + * Defines the time in picoseconds taken for the bridge to sample the
> + * input signal after the clock edge.
> + */
> + u32 hold_time_ps;
> +};
> +
> /**
> * struct drm_bridge - central DRM bridge control structure
> * @dev: DRM device this bridge belongs to
> @@ -229,6 +259,8 @@ struct drm_bridge_funcs {
> * @next: the next bridge in the encoder chain
> * @of_node: device node pointer to the bridge
> * @list: to keep track of all added bridges
> + * @timings: the timing specification for the bridge, if any (may
> + * be NULL)
> * @funcs: control functions
> * @driver_private: pointer to the bridge driver's internal context
> */
> @@ -240,6 +272,7 @@ struct drm_bridge {
> struct device_node *of_node;
> #endif
> struct list_head list;
> + const struct drm_bridge_timings *timings;
>
> const struct drm_bridge_funcs *funcs;
> void *driver_private;
--
Regards,
Laurent Pinchart
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox