Message ID | 20190827023557.7071-1-qiangqing.zhang@nxp.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | [V7,1/3] perf: imx8_ddr_perf: add AXI ID filter support | expand |
Hi Joakim, On Tue, Aug 27, 2019 at 02:39:32AM +0000, Joakim Zhang wrote: > AXI filtering is used by CSV modes 0x41 and 0x42 to count reads or > writes with an ARID or AWID matching filter setting. Granularity is at > subsystem level. Implementation does not allow filtring between masters > within a subsystem. Filter is defined with 2 configuration parameters. > > --AXI_ID defines AxID matching value > --AXI_MASKING defines which bits of AxID are meaningful for the matching > 0:corresponding bit is masked > 1: corresponding bit is not masked, i.e. used to do the matching > > When non-masked bits are matching corresponding AXI_ID bits then counter > is incremented. This filter allows counting read or write access from a > subsystem or multiple subsystems. > > Perf counter is incremented if AxID && AXI_MASKING == AXI_ID && AXI_MASKING > > AXI_ID and AXI_MASKING are mapped on DPCR1 register in performance counter. > > Read and write AXI ID filter should write same value to DPCR1 if want to > specify at the same time as this filter is shared between counters. > > e.g. > perf stat -a -e imx8_ddr0/axid-read,axi_id=0xMMMMDDDD/,imx8_ddr0/axid-write,axi_id=0xMMMMDDDD/ cmd > MMMM: AXI_MASKING DDDD: AXI_ID > perf stat -a -e imx8_ddr0/axid-read,axi_id=0x12/ cmd, which will monitor ARID=0x12 > > NOTE: AXI_MASKING is inverted at driver(i.e. set bits are bits to mask), so > that the user can just specify axi_id to monitor a specific id, rather than > having to specify axi_id=0xffff<id>. [...] > @@ -138,9 +156,11 @@ static struct attribute_group ddr_perf_events_attr_group = { > }; > > PMU_FORMAT_ATTR(event, "config:0-7"); > +PMU_FORMAT_ATTR(axi_id, "config1:0-31"); I still don't think this is quite what Mark was suggesting. My understanding of his email [1] was that you would do something like: PMU_FORMAT_ATTR(axi_id, "config1:0-15"); PMU_FORMAT_ATTR(axi_mask, "config1:16-31"); and then if the user omits to specify axi_mask, it has a value of 0 which means that all of the axi_id bits are matched (i.e. the driver inverts the mask internally). I think that's actually what your code is doing: > @@ -288,6 +337,21 @@ static int ddr_perf_event_add(struct perf_event *event, int flags) > struct hw_perf_event *hwc = &event->hw; > int counter; > int cfg = event->attr.config; > + int cfg1 = event->attr.config1; > + > + if (pmu->devtype_data->quirks & DDR_CAP_AXI_ID_FILTER) { > + int i; > + > + for (i = 1; i < NUM_COUNTERS; i++) { > + if (pmu->events[i] && > + !ddr_perf_filters_compatible(event, pmu->events[i])) > + return -EINVAL; > + } > + > + /* revert axi_id masking value */ > + cfg1 ^= AXI_MASKING_REVERT; it's just that the user ABI should probably separate these two fields out as above. I was going to make the change when merging this patch, but you need to update the Documentation in the second patch too. Will [1] https://lkml.kernel.org/r/20190823125719.GD55480@lakrids.cambridge.arm.com
> -----Original Message----- > From: Will Deacon <will@kernel.org> > Sent: 2019年8月28日 2:31 > To: Joakim Zhang <qiangqing.zhang@nxp.com> > Cc: mark.rutland@arm.com; robin.murphy@arm.com; Frank Li > <frank.li@nxp.com>; dl-linux-imx <linux-imx@nxp.com>; > linux-arm-kernel@lists.infradead.org > Subject: Re: [PATCH V7 1/3] perf: imx8_ddr_perf: add AXI ID filter support > > Hi Joakim, > > On Tue, Aug 27, 2019 at 02:39:32AM +0000, Joakim Zhang wrote: > > AXI filtering is used by CSV modes 0x41 and 0x42 to count reads or > > writes with an ARID or AWID matching filter setting. Granularity is at > > subsystem level. Implementation does not allow filtring between > > masters within a subsystem. Filter is defined with 2 configuration > parameters. > > > > --AXI_ID defines AxID matching value > > --AXI_MASKING defines which bits of AxID are meaningful for the matching > > 0:corresponding bit is masked > > 1: corresponding bit is not masked, i.e. used to do the matching > > > > When non-masked bits are matching corresponding AXI_ID bits then > > counter is incremented. This filter allows counting read or write > > access from a subsystem or multiple subsystems. > > > > Perf counter is incremented if AxID && AXI_MASKING == AXI_ID && > > AXI_MASKING > > > > AXI_ID and AXI_MASKING are mapped on DPCR1 register in performance > counter. > > > > Read and write AXI ID filter should write same value to DPCR1 if want > > to specify at the same time as this filter is shared between counters. > > > > e.g. > > perf stat -a -e > > > imx8_ddr0/axid-read,axi_id=0xMMMMDDDD/,imx8_ddr0/axid-write,axi_id=0x > M > > MMMDDDD/ cmd > > MMMM: AXI_MASKING DDDD: AXI_ID > > perf stat -a -e imx8_ddr0/axid-read,axi_id=0x12/ cmd, which will > > monitor ARID=0x12 > > > > NOTE: AXI_MASKING is inverted at driver(i.e. set bits are bits to > > mask), so that the user can just specify axi_id to monitor a specific > > id, rather than having to specify axi_id=0xffff<id>. > > [...] > > > @@ -138,9 +156,11 @@ static struct attribute_group > > ddr_perf_events_attr_group = { }; > > > > PMU_FORMAT_ATTR(event, "config:0-7"); > > +PMU_FORMAT_ATTR(axi_id, "config1:0-31"); > > I still don't think this is quite what Mark was suggesting. My understanding of > his email [1] was that you would do something like: > > PMU_FORMAT_ATTR(axi_id, "config1:0-15"); > PMU_FORMAT_ATTR(axi_mask, "config1:16-31"); > > and then if the user omits to specify axi_mask, it has a value of 0 which means > that all of the axi_id bits are matched (i.e. the driver inverts the mask > internally). I think that's actually what your code is doing: > > > @@ -288,6 +337,21 @@ static int ddr_perf_event_add(struct perf_event > *event, int flags) > > struct hw_perf_event *hwc = &event->hw; > > int counter; > > int cfg = event->attr.config; > > + int cfg1 = event->attr.config1; > > + > > + if (pmu->devtype_data->quirks & DDR_CAP_AXI_ID_FILTER) { > > + int i; > > + > > + for (i = 1; i < NUM_COUNTERS; i++) { > > + if (pmu->events[i] && > > + !ddr_perf_filters_compatible(event, pmu->events[i])) > > + return -EINVAL; > > + } > > + > > + /* revert axi_id masking value */ > > + cfg1 ^= AXI_MASKING_REVERT; > > it's just that the user ABI should probably separate these two fields out as > above. > > I was going to make the change when merging this patch, but you need to > update the Documentation in the second patch too. [Joakim] Thanks Will, I have updated the driver and commit message in the first patch, also updated doc in the second patch. I am going to send out a V8, thank you for your kindly review. Best Regards, Joakim Zhang > Will > > [1] > https://eur01.safelinks.protection.outlook.com/?url=https%3A%2F%2Flkml.ker > nel.org%2Fr%2F20190823125719.GD55480%40lakrids.cambridge.arm.com&a > mp;data=02%7C01%7Cqiangqing.zhang%40nxp.com%7Cfa6af1320ed345c2de3 > f08d72b1cae85%7C686ea1d3bc2b4c6fa92cd99c5c301635%7C0%7C0%7C6370 > 25274507822105&sdata=mwLua%2BInlqd83Z3h7DTkYz2Wb0VxbizrroWY > h83l%2B0g%3D&reserved=0
diff --git a/drivers/perf/fsl_imx8_ddr_perf.c b/drivers/perf/fsl_imx8_ddr_perf.c index 0e3310dbb145..ec2120fc3207 100644 --- a/drivers/perf/fsl_imx8_ddr_perf.c +++ b/drivers/perf/fsl_imx8_ddr_perf.c @@ -35,6 +35,8 @@ #define EVENT_CYCLES_COUNTER 0 #define NUM_COUNTERS 4 +#define AXI_MASKING_REVERT 0xffff0000 /* AXI_MASKING(MSB 16bits) + AXI_ID(LSB 16bits) */ + #define to_ddr_pmu(p) container_of(p, struct ddr_pmu, pmu) #define DDR_PERF_DEV_NAME "imx8_ddr" @@ -42,9 +44,22 @@ static DEFINE_IDA(ddr_ida); +/* DDR Perf hardware feature */ +#define DDR_CAP_AXI_ID_FILTER 0x1 /* support AXI ID filter */ + +struct fsl_ddr_devtype_data { + unsigned int quirks; /* quirks needed for different DDR Perf core */ +}; + +static const struct fsl_ddr_devtype_data imx8_devtype_data; + +static const struct fsl_ddr_devtype_data imx8m_devtype_data = { + .quirks = DDR_CAP_AXI_ID_FILTER, +}; + static const struct of_device_id imx_ddr_pmu_dt_ids[] = { - { .compatible = "fsl,imx8-ddr-pmu",}, - { .compatible = "fsl,imx8m-ddr-pmu",}, + { .compatible = "fsl,imx8-ddr-pmu", .data = &imx8_devtype_data}, + { .compatible = "fsl,imx8m-ddr-pmu", .data = &imx8m_devtype_data}, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, imx_ddr_pmu_dt_ids); @@ -58,6 +73,7 @@ struct ddr_pmu { struct perf_event *events[NUM_COUNTERS]; int active_events; enum cpuhp_state cpuhp_state; + const struct fsl_ddr_devtype_data *devtype_data; int irq; int id; }; @@ -129,6 +145,8 @@ static struct attribute *ddr_perf_events_attrs[] = { IMX8_DDR_PMU_EVENT_ATTR(refresh, 0x37), IMX8_DDR_PMU_EVENT_ATTR(write, 0x38), IMX8_DDR_PMU_EVENT_ATTR(raw-hazard, 0x39), + IMX8_DDR_PMU_EVENT_ATTR(axid-read, 0x41), + IMX8_DDR_PMU_EVENT_ATTR(axid-write, 0x42), NULL, }; @@ -138,9 +156,11 @@ static struct attribute_group ddr_perf_events_attr_group = { }; PMU_FORMAT_ATTR(event, "config:0-7"); +PMU_FORMAT_ATTR(axi_id, "config1:0-31"); static struct attribute *ddr_perf_format_attrs[] = { &format_attr_event.attr, + &format_attr_axi_id.attr, NULL, }; @@ -190,6 +210,26 @@ static u32 ddr_perf_read_counter(struct ddr_pmu *pmu, int counter) return readl_relaxed(pmu->base + COUNTER_READ + counter * 4); } +static bool ddr_perf_is_filtered(struct perf_event *event) +{ + return event->attr.config == 0x41 || event->attr.config == 0x42; +} + +static u32 ddr_perf_filter_val(struct perf_event *event) +{ + return event->attr.config1; +} + +static bool ddr_perf_filters_compatible(struct perf_event *a, + struct perf_event *b) +{ + if (!ddr_perf_is_filtered(a)) + return true; + if (!ddr_perf_is_filtered(b)) + return true; + return ddr_perf_filter_val(a) == ddr_perf_filter_val(b); +} + static int ddr_perf_event_init(struct perf_event *event) { struct ddr_pmu *pmu = to_ddr_pmu(event->pmu); @@ -216,6 +256,15 @@ static int ddr_perf_event_init(struct perf_event *event) !is_software_event(event->group_leader)) return -EINVAL; + if (pmu->devtype_data->quirks & DDR_CAP_AXI_ID_FILTER) { + if (!ddr_perf_filters_compatible(event, event->group_leader)) + return -EINVAL; + for_each_sibling_event(sibling, event->group_leader) { + if (!ddr_perf_filters_compatible(event, sibling)) + return -EINVAL; + } + } + for_each_sibling_event(sibling, event->group_leader) { if (sibling->pmu != event->pmu && !is_software_event(sibling)) @@ -288,6 +337,21 @@ static int ddr_perf_event_add(struct perf_event *event, int flags) struct hw_perf_event *hwc = &event->hw; int counter; int cfg = event->attr.config; + int cfg1 = event->attr.config1; + + if (pmu->devtype_data->quirks & DDR_CAP_AXI_ID_FILTER) { + int i; + + for (i = 1; i < NUM_COUNTERS; i++) { + if (pmu->events[i] && + !ddr_perf_filters_compatible(event, pmu->events[i])) + return -EINVAL; + } + + /* revert axi_id masking value */ + cfg1 ^= AXI_MASKING_REVERT; + writel(cfg1, pmu->base + COUNTER_DPCR1); + } counter = ddr_perf_alloc_counter(pmu, cfg); if (counter < 0) { @@ -473,6 +537,8 @@ static int ddr_perf_probe(struct platform_device *pdev) if (!name) return -ENOMEM; + pmu->devtype_data = of_device_get_match_data(&pdev->dev); + pmu->cpu = raw_smp_processor_id(); ret = cpuhp_setup_state_multi(CPUHP_AP_ONLINE_DYN, DDR_CPUHP_CB_NAME,
AXI filtering is used by CSV modes 0x41 and 0x42 to count reads or writes with an ARID or AWID matching filter setting. Granularity is at subsystem level. Implementation does not allow filtring between masters within a subsystem. Filter is defined with 2 configuration parameters. --AXI_ID defines AxID matching value --AXI_MASKING defines which bits of AxID are meaningful for the matching 0:corresponding bit is masked 1: corresponding bit is not masked, i.e. used to do the matching When non-masked bits are matching corresponding AXI_ID bits then counter is incremented. This filter allows counting read or write access from a subsystem or multiple subsystems. Perf counter is incremented if AxID && AXI_MASKING == AXI_ID && AXI_MASKING AXI_ID and AXI_MASKING are mapped on DPCR1 register in performance counter. Read and write AXI ID filter should write same value to DPCR1 if want to specify at the same time as this filter is shared between counters. e.g. perf stat -a -e imx8_ddr0/axid-read,axi_id=0xMMMMDDDD/,imx8_ddr0/axid-write,axi_id=0xMMMMDDDD/ cmd MMMM: AXI_MASKING DDDD: AXI_ID perf stat -a -e imx8_ddr0/axid-read,axi_id=0x12/ cmd, which will monitor ARID=0x12 NOTE: AXI_MASKING is inverted at driver(i.e. set bits are bits to mask), so that the user can just specify axi_id to monitor a specific id, rather than having to specify axi_id=0xffff<id>. ChangeLog: V1 -> V2: * add error log if user specifies read/write AXI ID filter at the same time. * of_device_get_match_data() instead of of_match_device(), and remove the check of return value. V2 -> V3: * move the AXI ID check to event_add(). * add support for same value of axi_id. V3 -> V4: * move the AXI ID check to event_init(). V4 -> V5: * reject event group if AXI ID not consistent in event_init(). V5 -> V6: * change the event name: axi-id-read->axid-read; axi-id-write->axid-write * add another helper: ddr_perf_filters_compatible() * drop the dev_dbg() V6 -> V7: * revert AXI_MASKING at driver. Signed-off-by: Joakim Zhang <qiangqing.zhang@nxp.com> --- drivers/perf/fsl_imx8_ddr_perf.c | 70 +++++++++++++++++++++++++++++++- 1 file changed, 68 insertions(+), 2 deletions(-)