1 // SPDX-License-Identifier: GPL-2.0
2 #include <linux/err.h>
3 #include <linux/zalloc.h>
4 #include <errno.h>
5 #include <sys/types.h>
6 #include <sys/stat.h>
7 #include <fcntl.h>
8 #include <sys/param.h>
9 #include "evlist.h"
10 #include "evsel.h"
11 #include "parse-events.h"
12 #include "parse-events-hybrid.h"
13 #include "debug.h"
14 #include "pmu.h"
15 #include "pmu-hybrid.h"
16 #include "perf.h"
17 
config_hybrid_attr(struct perf_event_attr * attr,int type,int pmu_type)18 static void config_hybrid_attr(struct perf_event_attr *attr,
19 			       int type, int pmu_type)
20 {
21 	/*
22 	 * attr.config layout for type PERF_TYPE_HARDWARE and
23 	 * PERF_TYPE_HW_CACHE
24 	 *
25 	 * PERF_TYPE_HARDWARE:                 0xEEEEEEEE000000AA
26 	 *                                     AA: hardware event ID
27 	 *                                     EEEEEEEE: PMU type ID
28 	 * PERF_TYPE_HW_CACHE:                 0xEEEEEEEE00DDCCBB
29 	 *                                     BB: hardware cache ID
30 	 *                                     CC: hardware cache op ID
31 	 *                                     DD: hardware cache op result ID
32 	 *                                     EEEEEEEE: PMU type ID
33 	 * If the PMU type ID is 0, the PERF_TYPE_RAW will be applied.
34 	 */
35 	attr->type = type;
36 	attr->config = (attr->config & PERF_HW_EVENT_MASK) |
37 			((__u64)pmu_type << PERF_PMU_TYPE_SHIFT);
38 }
39 
create_event_hybrid(__u32 config_type,int * idx,struct list_head * list,struct perf_event_attr * attr,const char * name,const char * metric_id,struct list_head * config_terms,struct perf_pmu * pmu)40 static int create_event_hybrid(__u32 config_type, int *idx,
41 			       struct list_head *list,
42 			       struct perf_event_attr *attr, const char *name,
43 			       const char *metric_id,
44 			       struct list_head *config_terms,
45 			       struct perf_pmu *pmu)
46 {
47 	struct evsel *evsel;
48 	__u32 type = attr->type;
49 	__u64 config = attr->config;
50 
51 	config_hybrid_attr(attr, config_type, pmu->type);
52 
53 	/*
54 	 * Some hybrid hardware cache events are only available on one CPU
55 	 * PMU. For example, the 'L1-dcache-load-misses' is only available
56 	 * on cpu_core, while the 'L1-icache-loads' is only available on
57 	 * cpu_atom. We need to remove "not supported" hybrid cache events.
58 	 */
59 	if (attr->type == PERF_TYPE_HW_CACHE
60 	    && !is_event_supported(attr->type, attr->config))
61 		return 0;
62 
63 	evsel = parse_events__add_event_hybrid(list, idx, attr, name, metric_id,
64 					       pmu, config_terms);
65 	if (evsel) {
66 		evsel->pmu_name = strdup(pmu->name);
67 		if (!evsel->pmu_name)
68 			return -ENOMEM;
69 	} else
70 		return -ENOMEM;
71 	attr->type = type;
72 	attr->config = config;
73 	return 0;
74 }
75 
pmu_cmp(struct parse_events_state * parse_state,struct perf_pmu * pmu)76 static int pmu_cmp(struct parse_events_state *parse_state,
77 		   struct perf_pmu *pmu)
78 {
79 	if (parse_state->evlist && parse_state->evlist->hybrid_pmu_name)
80 		return strcmp(parse_state->evlist->hybrid_pmu_name, pmu->name);
81 
82 	if (parse_state->hybrid_pmu_name)
83 		return strcmp(parse_state->hybrid_pmu_name, pmu->name);
84 
85 	return 0;
86 }
87 
add_hw_hybrid(struct parse_events_state * parse_state,struct list_head * list,struct perf_event_attr * attr,const char * name,const char * metric_id,struct list_head * config_terms)88 static int add_hw_hybrid(struct parse_events_state *parse_state,
89 			 struct list_head *list, struct perf_event_attr *attr,
90 			 const char *name, const char *metric_id,
91 			 struct list_head *config_terms)
92 {
93 	struct perf_pmu *pmu;
94 	int ret;
95 
96 	perf_pmu__for_each_hybrid_pmu(pmu) {
97 		LIST_HEAD(terms);
98 
99 		if (pmu_cmp(parse_state, pmu))
100 			continue;
101 
102 		copy_config_terms(&terms, config_terms);
103 		ret = create_event_hybrid(PERF_TYPE_HARDWARE,
104 					  &parse_state->idx, list, attr, name,
105 					  metric_id, &terms, pmu);
106 		free_config_terms(&terms);
107 		if (ret)
108 			return ret;
109 	}
110 
111 	return 0;
112 }
113 
create_raw_event_hybrid(int * idx,struct list_head * list,struct perf_event_attr * attr,const char * name,const char * metric_id,struct list_head * config_terms,struct perf_pmu * pmu)114 static int create_raw_event_hybrid(int *idx, struct list_head *list,
115 				   struct perf_event_attr *attr,
116 				   const char *name,
117 				   const char *metric_id,
118 				   struct list_head *config_terms,
119 				   struct perf_pmu *pmu)
120 {
121 	struct evsel *evsel;
122 
123 	attr->type = pmu->type;
124 	evsel = parse_events__add_event_hybrid(list, idx, attr, name, metric_id,
125 					       pmu, config_terms);
126 	if (evsel)
127 		evsel->pmu_name = strdup(pmu->name);
128 	else
129 		return -ENOMEM;
130 
131 	return 0;
132 }
133 
add_raw_hybrid(struct parse_events_state * parse_state,struct list_head * list,struct perf_event_attr * attr,const char * name,const char * metric_id,struct list_head * config_terms)134 static int add_raw_hybrid(struct parse_events_state *parse_state,
135 			  struct list_head *list, struct perf_event_attr *attr,
136 			  const char *name, const char *metric_id,
137 			  struct list_head *config_terms)
138 {
139 	struct perf_pmu *pmu;
140 	int ret;
141 
142 	perf_pmu__for_each_hybrid_pmu(pmu) {
143 		LIST_HEAD(terms);
144 
145 		if (pmu_cmp(parse_state, pmu))
146 			continue;
147 
148 		copy_config_terms(&terms, config_terms);
149 		ret = create_raw_event_hybrid(&parse_state->idx, list, attr,
150 					      name, metric_id, &terms, pmu);
151 		free_config_terms(&terms);
152 		if (ret)
153 			return ret;
154 	}
155 
156 	return 0;
157 }
158 
parse_events__add_numeric_hybrid(struct parse_events_state * parse_state,struct list_head * list,struct perf_event_attr * attr,const char * name,const char * metric_id,struct list_head * config_terms,bool * hybrid)159 int parse_events__add_numeric_hybrid(struct parse_events_state *parse_state,
160 				     struct list_head *list,
161 				     struct perf_event_attr *attr,
162 				     const char *name, const char *metric_id,
163 				     struct list_head *config_terms,
164 				     bool *hybrid)
165 {
166 	*hybrid = false;
167 	if (attr->type == PERF_TYPE_SOFTWARE)
168 		return 0;
169 
170 	if (!perf_pmu__has_hybrid())
171 		return 0;
172 
173 	*hybrid = true;
174 	if (attr->type != PERF_TYPE_RAW) {
175 		return add_hw_hybrid(parse_state, list, attr, name, metric_id,
176 				     config_terms);
177 	}
178 
179 	return add_raw_hybrid(parse_state, list, attr, name, metric_id,
180 			      config_terms);
181 }
182 
parse_events__add_cache_hybrid(struct list_head * list,int * idx,struct perf_event_attr * attr,const char * name,const char * metric_id,struct list_head * config_terms,bool * hybrid,struct parse_events_state * parse_state)183 int parse_events__add_cache_hybrid(struct list_head *list, int *idx,
184 				   struct perf_event_attr *attr,
185 				   const char *name,
186 				   const char *metric_id,
187 				   struct list_head *config_terms,
188 				   bool *hybrid,
189 				   struct parse_events_state *parse_state)
190 {
191 	struct perf_pmu *pmu;
192 	int ret;
193 
194 	*hybrid = false;
195 	if (!perf_pmu__has_hybrid())
196 		return 0;
197 
198 	*hybrid = true;
199 	perf_pmu__for_each_hybrid_pmu(pmu) {
200 		LIST_HEAD(terms);
201 
202 		if (pmu_cmp(parse_state, pmu))
203 			continue;
204 
205 		copy_config_terms(&terms, config_terms);
206 		ret = create_event_hybrid(PERF_TYPE_HW_CACHE, idx, list,
207 					  attr, name, metric_id, &terms, pmu);
208 		free_config_terms(&terms);
209 		if (ret)
210 			return ret;
211 	}
212 
213 	return 0;
214 }
215