18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci
38c2ecf20Sopenharmony_ci#include <linux/version.h>
48c2ecf20Sopenharmony_ci#include <linux/ptrace.h>
58c2ecf20Sopenharmony_ci#include <uapi/linux/bpf.h>
68c2ecf20Sopenharmony_ci#include <bpf/bpf_helpers.h>
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci/*
98c2ecf20Sopenharmony_ci * The CPU number, cstate number and pstate number are based
108c2ecf20Sopenharmony_ci * on 96boards Hikey with octa CA53 CPUs.
118c2ecf20Sopenharmony_ci *
128c2ecf20Sopenharmony_ci * Every CPU have three idle states for cstate:
138c2ecf20Sopenharmony_ci *   WFI, CPU_OFF, CLUSTER_OFF
148c2ecf20Sopenharmony_ci *
158c2ecf20Sopenharmony_ci * Every CPU have 5 operating points:
168c2ecf20Sopenharmony_ci *   208MHz, 432MHz, 729MHz, 960MHz, 1200MHz
178c2ecf20Sopenharmony_ci *
188c2ecf20Sopenharmony_ci * This code is based on these assumption and other platforms
198c2ecf20Sopenharmony_ci * need to adjust these definitions.
208c2ecf20Sopenharmony_ci */
218c2ecf20Sopenharmony_ci#define MAX_CPU			8
228c2ecf20Sopenharmony_ci#define MAX_PSTATE_ENTRIES	5
238c2ecf20Sopenharmony_ci#define MAX_CSTATE_ENTRIES	3
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_cistatic int cpu_opps[] = { 208000, 432000, 729000, 960000, 1200000 };
268c2ecf20Sopenharmony_ci
278c2ecf20Sopenharmony_ci/*
288c2ecf20Sopenharmony_ci * my_map structure is used to record cstate and pstate index and
298c2ecf20Sopenharmony_ci * timestamp (Idx, Ts), when new event incoming we need to update
308c2ecf20Sopenharmony_ci * combination for new state index and timestamp (Idx`, Ts`).
318c2ecf20Sopenharmony_ci *
328c2ecf20Sopenharmony_ci * Based on (Idx, Ts) and (Idx`, Ts`) we can calculate the time
338c2ecf20Sopenharmony_ci * interval for the previous state: Duration(Idx) = Ts` - Ts.
348c2ecf20Sopenharmony_ci *
358c2ecf20Sopenharmony_ci * Every CPU has one below array for recording state index and
368c2ecf20Sopenharmony_ci * timestamp, and record for cstate and pstate saperately:
378c2ecf20Sopenharmony_ci *
388c2ecf20Sopenharmony_ci * +--------------------------+
398c2ecf20Sopenharmony_ci * | cstate timestamp         |
408c2ecf20Sopenharmony_ci * +--------------------------+
418c2ecf20Sopenharmony_ci * | cstate index             |
428c2ecf20Sopenharmony_ci * +--------------------------+
438c2ecf20Sopenharmony_ci * | pstate timestamp         |
448c2ecf20Sopenharmony_ci * +--------------------------+
458c2ecf20Sopenharmony_ci * | pstate index             |
468c2ecf20Sopenharmony_ci * +--------------------------+
478c2ecf20Sopenharmony_ci */
488c2ecf20Sopenharmony_ci#define MAP_OFF_CSTATE_TIME	0
498c2ecf20Sopenharmony_ci#define MAP_OFF_CSTATE_IDX	1
508c2ecf20Sopenharmony_ci#define MAP_OFF_PSTATE_TIME	2
518c2ecf20Sopenharmony_ci#define MAP_OFF_PSTATE_IDX	3
528c2ecf20Sopenharmony_ci#define MAP_OFF_NUM		4
538c2ecf20Sopenharmony_ci
548c2ecf20Sopenharmony_cistruct {
558c2ecf20Sopenharmony_ci	__uint(type, BPF_MAP_TYPE_ARRAY);
568c2ecf20Sopenharmony_ci	__type(key, u32);
578c2ecf20Sopenharmony_ci	__type(value, u64);
588c2ecf20Sopenharmony_ci	__uint(max_entries, MAX_CPU * MAP_OFF_NUM);
598c2ecf20Sopenharmony_ci} my_map SEC(".maps");
608c2ecf20Sopenharmony_ci
618c2ecf20Sopenharmony_ci/* cstate_duration records duration time for every idle state per CPU */
628c2ecf20Sopenharmony_cistruct {
638c2ecf20Sopenharmony_ci	__uint(type, BPF_MAP_TYPE_ARRAY);
648c2ecf20Sopenharmony_ci	__type(key, u32);
658c2ecf20Sopenharmony_ci	__type(value, u64);
668c2ecf20Sopenharmony_ci	__uint(max_entries, MAX_CPU * MAX_CSTATE_ENTRIES);
678c2ecf20Sopenharmony_ci} cstate_duration SEC(".maps");
688c2ecf20Sopenharmony_ci
698c2ecf20Sopenharmony_ci/* pstate_duration records duration time for every operating point per CPU */
708c2ecf20Sopenharmony_cistruct {
718c2ecf20Sopenharmony_ci	__uint(type, BPF_MAP_TYPE_ARRAY);
728c2ecf20Sopenharmony_ci	__type(key, u32);
738c2ecf20Sopenharmony_ci	__type(value, u64);
748c2ecf20Sopenharmony_ci	__uint(max_entries, MAX_CPU * MAX_PSTATE_ENTRIES);
758c2ecf20Sopenharmony_ci} pstate_duration SEC(".maps");
768c2ecf20Sopenharmony_ci
778c2ecf20Sopenharmony_ci/*
788c2ecf20Sopenharmony_ci * The trace events for cpu_idle and cpu_frequency are taken from:
798c2ecf20Sopenharmony_ci * /sys/kernel/debug/tracing/events/power/cpu_idle/format
808c2ecf20Sopenharmony_ci * /sys/kernel/debug/tracing/events/power/cpu_frequency/format
818c2ecf20Sopenharmony_ci *
828c2ecf20Sopenharmony_ci * These two events have same format, so define one common structure.
838c2ecf20Sopenharmony_ci */
848c2ecf20Sopenharmony_cistruct cpu_args {
858c2ecf20Sopenharmony_ci	u64 pad;
868c2ecf20Sopenharmony_ci	u32 state;
878c2ecf20Sopenharmony_ci	u32 cpu_id;
888c2ecf20Sopenharmony_ci};
898c2ecf20Sopenharmony_ci
908c2ecf20Sopenharmony_ci/* calculate pstate index, returns MAX_PSTATE_ENTRIES for failure */
918c2ecf20Sopenharmony_cistatic u32 find_cpu_pstate_idx(u32 frequency)
928c2ecf20Sopenharmony_ci{
938c2ecf20Sopenharmony_ci	u32 i;
948c2ecf20Sopenharmony_ci
958c2ecf20Sopenharmony_ci	for (i = 0; i < sizeof(cpu_opps) / sizeof(u32); i++) {
968c2ecf20Sopenharmony_ci		if (frequency == cpu_opps[i])
978c2ecf20Sopenharmony_ci			return i;
988c2ecf20Sopenharmony_ci	}
998c2ecf20Sopenharmony_ci
1008c2ecf20Sopenharmony_ci	return i;
1018c2ecf20Sopenharmony_ci}
1028c2ecf20Sopenharmony_ci
1038c2ecf20Sopenharmony_ciSEC("tracepoint/power/cpu_idle")
1048c2ecf20Sopenharmony_ciint bpf_prog1(struct cpu_args *ctx)
1058c2ecf20Sopenharmony_ci{
1068c2ecf20Sopenharmony_ci	u64 *cts, *pts, *cstate, *pstate, prev_state, cur_ts, delta;
1078c2ecf20Sopenharmony_ci	u32 key, cpu, pstate_idx;
1088c2ecf20Sopenharmony_ci	u64 *val;
1098c2ecf20Sopenharmony_ci
1108c2ecf20Sopenharmony_ci	if (ctx->cpu_id > MAX_CPU)
1118c2ecf20Sopenharmony_ci		return 0;
1128c2ecf20Sopenharmony_ci
1138c2ecf20Sopenharmony_ci	cpu = ctx->cpu_id;
1148c2ecf20Sopenharmony_ci
1158c2ecf20Sopenharmony_ci	key = cpu * MAP_OFF_NUM + MAP_OFF_CSTATE_TIME;
1168c2ecf20Sopenharmony_ci	cts = bpf_map_lookup_elem(&my_map, &key);
1178c2ecf20Sopenharmony_ci	if (!cts)
1188c2ecf20Sopenharmony_ci		return 0;
1198c2ecf20Sopenharmony_ci
1208c2ecf20Sopenharmony_ci	key = cpu * MAP_OFF_NUM + MAP_OFF_CSTATE_IDX;
1218c2ecf20Sopenharmony_ci	cstate = bpf_map_lookup_elem(&my_map, &key);
1228c2ecf20Sopenharmony_ci	if (!cstate)
1238c2ecf20Sopenharmony_ci		return 0;
1248c2ecf20Sopenharmony_ci
1258c2ecf20Sopenharmony_ci	key = cpu * MAP_OFF_NUM + MAP_OFF_PSTATE_TIME;
1268c2ecf20Sopenharmony_ci	pts = bpf_map_lookup_elem(&my_map, &key);
1278c2ecf20Sopenharmony_ci	if (!pts)
1288c2ecf20Sopenharmony_ci		return 0;
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_ci	key = cpu * MAP_OFF_NUM + MAP_OFF_PSTATE_IDX;
1318c2ecf20Sopenharmony_ci	pstate = bpf_map_lookup_elem(&my_map, &key);
1328c2ecf20Sopenharmony_ci	if (!pstate)
1338c2ecf20Sopenharmony_ci		return 0;
1348c2ecf20Sopenharmony_ci
1358c2ecf20Sopenharmony_ci	prev_state = *cstate;
1368c2ecf20Sopenharmony_ci	*cstate = ctx->state;
1378c2ecf20Sopenharmony_ci
1388c2ecf20Sopenharmony_ci	if (!*cts) {
1398c2ecf20Sopenharmony_ci		*cts = bpf_ktime_get_ns();
1408c2ecf20Sopenharmony_ci		return 0;
1418c2ecf20Sopenharmony_ci	}
1428c2ecf20Sopenharmony_ci
1438c2ecf20Sopenharmony_ci	cur_ts = bpf_ktime_get_ns();
1448c2ecf20Sopenharmony_ci	delta = cur_ts - *cts;
1458c2ecf20Sopenharmony_ci	*cts = cur_ts;
1468c2ecf20Sopenharmony_ci
1478c2ecf20Sopenharmony_ci	/*
1488c2ecf20Sopenharmony_ci	 * When state doesn't equal to (u32)-1, the cpu will enter
1498c2ecf20Sopenharmony_ci	 * one idle state; for this case we need to record interval
1508c2ecf20Sopenharmony_ci	 * for the pstate.
1518c2ecf20Sopenharmony_ci	 *
1528c2ecf20Sopenharmony_ci	 *                 OPP2
1538c2ecf20Sopenharmony_ci	 *            +---------------------+
1548c2ecf20Sopenharmony_ci	 *     OPP1   |                     |
1558c2ecf20Sopenharmony_ci	 *   ---------+                     |
1568c2ecf20Sopenharmony_ci	 *                                  |  Idle state
1578c2ecf20Sopenharmony_ci	 *                                  +---------------
1588c2ecf20Sopenharmony_ci	 *
1598c2ecf20Sopenharmony_ci	 *            |<- pstate duration ->|
1608c2ecf20Sopenharmony_ci	 *            ^                     ^
1618c2ecf20Sopenharmony_ci	 *           pts                  cur_ts
1628c2ecf20Sopenharmony_ci	 */
1638c2ecf20Sopenharmony_ci	if (ctx->state != (u32)-1) {
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_ci		/* record pstate after have first cpu_frequency event */
1668c2ecf20Sopenharmony_ci		if (!*pts)
1678c2ecf20Sopenharmony_ci			return 0;
1688c2ecf20Sopenharmony_ci
1698c2ecf20Sopenharmony_ci		delta = cur_ts - *pts;
1708c2ecf20Sopenharmony_ci
1718c2ecf20Sopenharmony_ci		pstate_idx = find_cpu_pstate_idx(*pstate);
1728c2ecf20Sopenharmony_ci		if (pstate_idx >= MAX_PSTATE_ENTRIES)
1738c2ecf20Sopenharmony_ci			return 0;
1748c2ecf20Sopenharmony_ci
1758c2ecf20Sopenharmony_ci		key = cpu * MAX_PSTATE_ENTRIES + pstate_idx;
1768c2ecf20Sopenharmony_ci		val = bpf_map_lookup_elem(&pstate_duration, &key);
1778c2ecf20Sopenharmony_ci		if (val)
1788c2ecf20Sopenharmony_ci			__sync_fetch_and_add((long *)val, delta);
1798c2ecf20Sopenharmony_ci
1808c2ecf20Sopenharmony_ci	/*
1818c2ecf20Sopenharmony_ci	 * When state equal to (u32)-1, the cpu just exits from one
1828c2ecf20Sopenharmony_ci	 * specific idle state; for this case we need to record
1838c2ecf20Sopenharmony_ci	 * interval for the pstate.
1848c2ecf20Sopenharmony_ci	 *
1858c2ecf20Sopenharmony_ci	 *       OPP2
1868c2ecf20Sopenharmony_ci	 *   -----------+
1878c2ecf20Sopenharmony_ci	 *              |                          OPP1
1888c2ecf20Sopenharmony_ci	 *              |                     +-----------
1898c2ecf20Sopenharmony_ci	 *              |     Idle state      |
1908c2ecf20Sopenharmony_ci	 *              +---------------------+
1918c2ecf20Sopenharmony_ci	 *
1928c2ecf20Sopenharmony_ci	 *              |<- cstate duration ->|
1938c2ecf20Sopenharmony_ci	 *              ^                     ^
1948c2ecf20Sopenharmony_ci	 *             cts                  cur_ts
1958c2ecf20Sopenharmony_ci	 */
1968c2ecf20Sopenharmony_ci	} else {
1978c2ecf20Sopenharmony_ci
1988c2ecf20Sopenharmony_ci		key = cpu * MAX_CSTATE_ENTRIES + prev_state;
1998c2ecf20Sopenharmony_ci		val = bpf_map_lookup_elem(&cstate_duration, &key);
2008c2ecf20Sopenharmony_ci		if (val)
2018c2ecf20Sopenharmony_ci			__sync_fetch_and_add((long *)val, delta);
2028c2ecf20Sopenharmony_ci	}
2038c2ecf20Sopenharmony_ci
2048c2ecf20Sopenharmony_ci	/* Update timestamp for pstate as new start time */
2058c2ecf20Sopenharmony_ci	if (*pts)
2068c2ecf20Sopenharmony_ci		*pts = cur_ts;
2078c2ecf20Sopenharmony_ci
2088c2ecf20Sopenharmony_ci	return 0;
2098c2ecf20Sopenharmony_ci}
2108c2ecf20Sopenharmony_ci
2118c2ecf20Sopenharmony_ciSEC("tracepoint/power/cpu_frequency")
2128c2ecf20Sopenharmony_ciint bpf_prog2(struct cpu_args *ctx)
2138c2ecf20Sopenharmony_ci{
2148c2ecf20Sopenharmony_ci	u64 *pts, *cstate, *pstate, prev_state, cur_ts, delta;
2158c2ecf20Sopenharmony_ci	u32 key, cpu, pstate_idx;
2168c2ecf20Sopenharmony_ci	u64 *val;
2178c2ecf20Sopenharmony_ci
2188c2ecf20Sopenharmony_ci	cpu = ctx->cpu_id;
2198c2ecf20Sopenharmony_ci
2208c2ecf20Sopenharmony_ci	key = cpu * MAP_OFF_NUM + MAP_OFF_PSTATE_TIME;
2218c2ecf20Sopenharmony_ci	pts = bpf_map_lookup_elem(&my_map, &key);
2228c2ecf20Sopenharmony_ci	if (!pts)
2238c2ecf20Sopenharmony_ci		return 0;
2248c2ecf20Sopenharmony_ci
2258c2ecf20Sopenharmony_ci	key = cpu * MAP_OFF_NUM + MAP_OFF_PSTATE_IDX;
2268c2ecf20Sopenharmony_ci	pstate = bpf_map_lookup_elem(&my_map, &key);
2278c2ecf20Sopenharmony_ci	if (!pstate)
2288c2ecf20Sopenharmony_ci		return 0;
2298c2ecf20Sopenharmony_ci
2308c2ecf20Sopenharmony_ci	key = cpu * MAP_OFF_NUM + MAP_OFF_CSTATE_IDX;
2318c2ecf20Sopenharmony_ci	cstate = bpf_map_lookup_elem(&my_map, &key);
2328c2ecf20Sopenharmony_ci	if (!cstate)
2338c2ecf20Sopenharmony_ci		return 0;
2348c2ecf20Sopenharmony_ci
2358c2ecf20Sopenharmony_ci	prev_state = *pstate;
2368c2ecf20Sopenharmony_ci	*pstate = ctx->state;
2378c2ecf20Sopenharmony_ci
2388c2ecf20Sopenharmony_ci	if (!*pts) {
2398c2ecf20Sopenharmony_ci		*pts = bpf_ktime_get_ns();
2408c2ecf20Sopenharmony_ci		return 0;
2418c2ecf20Sopenharmony_ci	}
2428c2ecf20Sopenharmony_ci
2438c2ecf20Sopenharmony_ci	cur_ts = bpf_ktime_get_ns();
2448c2ecf20Sopenharmony_ci	delta = cur_ts - *pts;
2458c2ecf20Sopenharmony_ci	*pts = cur_ts;
2468c2ecf20Sopenharmony_ci
2478c2ecf20Sopenharmony_ci	/* When CPU is in idle, bail out to skip pstate statistics */
2488c2ecf20Sopenharmony_ci	if (*cstate != (u32)(-1))
2498c2ecf20Sopenharmony_ci		return 0;
2508c2ecf20Sopenharmony_ci
2518c2ecf20Sopenharmony_ci	/*
2528c2ecf20Sopenharmony_ci	 * The cpu changes to another different OPP (in below diagram
2538c2ecf20Sopenharmony_ci	 * change frequency from OPP3 to OPP1), need recording interval
2548c2ecf20Sopenharmony_ci	 * for previous frequency OPP3 and update timestamp as start
2558c2ecf20Sopenharmony_ci	 * time for new frequency OPP1.
2568c2ecf20Sopenharmony_ci	 *
2578c2ecf20Sopenharmony_ci	 *                 OPP3
2588c2ecf20Sopenharmony_ci	 *            +---------------------+
2598c2ecf20Sopenharmony_ci	 *     OPP2   |                     |
2608c2ecf20Sopenharmony_ci	 *   ---------+                     |
2618c2ecf20Sopenharmony_ci	 *                                  |    OPP1
2628c2ecf20Sopenharmony_ci	 *                                  +---------------
2638c2ecf20Sopenharmony_ci	 *
2648c2ecf20Sopenharmony_ci	 *            |<- pstate duration ->|
2658c2ecf20Sopenharmony_ci	 *            ^                     ^
2668c2ecf20Sopenharmony_ci	 *           pts                  cur_ts
2678c2ecf20Sopenharmony_ci	 */
2688c2ecf20Sopenharmony_ci	pstate_idx = find_cpu_pstate_idx(*pstate);
2698c2ecf20Sopenharmony_ci	if (pstate_idx >= MAX_PSTATE_ENTRIES)
2708c2ecf20Sopenharmony_ci		return 0;
2718c2ecf20Sopenharmony_ci
2728c2ecf20Sopenharmony_ci	key = cpu * MAX_PSTATE_ENTRIES + pstate_idx;
2738c2ecf20Sopenharmony_ci	val = bpf_map_lookup_elem(&pstate_duration, &key);
2748c2ecf20Sopenharmony_ci	if (val)
2758c2ecf20Sopenharmony_ci		__sync_fetch_and_add((long *)val, delta);
2768c2ecf20Sopenharmony_ci
2778c2ecf20Sopenharmony_ci	return 0;
2788c2ecf20Sopenharmony_ci}
2798c2ecf20Sopenharmony_ci
2808c2ecf20Sopenharmony_cichar _license[] SEC("license") = "GPL";
2818c2ecf20Sopenharmony_ciu32 _version SEC("version") = LINUX_VERSION_CODE;
282