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