从零打造高通平台hexagon dsp profiling性能分析工具-2
前言
做事情要know how,知其然知其所以然。
高通pmu介绍比较吝啬,我专门问过高通资深CE也问不出更多信息,不过这块可以参考arm pmu的定义,相互比对这看,道理都是相通的。
多于8个pmu寄存器的周期性读取是很底层的东西,也需要一些工程化能力,这也是itrace工具的价值,但它有致命bug,因此不得不再往下挖,用qurt api来实现pmu寄存器的读取。
关于pmu event的详细文档资料
sdk中itrace相关说明文档
docs/doxygen/itrace/index.html
itrace_dsp_events_pmu.h
html文档也是由代码通过doxygen生成的,本质内容是一样的。
这里的pmu event index相比pdf文档上的不一样,有一定的对应关系。
标量核与向量核RM文档
怎么读取pmu原始数据
一个系统上只能有单pmu client端。
我们运行SDP 分析工具时,如果再开一个窗口运行sysMon,会提示注册失败,SDP关闭后,sysMon也无法运行,这是因为SDP的deregister逻辑有问题,需要重启安卓系统才行,这是高通软件的bug。
运行itrace的同时,如果再运行sysMon监控则也会报错。
无论是SDP,sysMon,itrace等profiling工具,他们本质上都是通过qurt api接口获取pmu,进行后处理及可视化,硬件最多支持8个pmu为一组获取,如果监控多余8个pmu event,则需要一个loop,来回切换pmu cfg并获取新配置下的pmu val,正是这个原因导致pmu消费者只能有一个。
真实系统下,pmu会受系统中其他进程的影响,无法识别是哪个进程贡献了pmu count value(比如cache及总线相关的pmu),分析时需要特别注意这点,否则会很confuse。
方法1:HAP_user_pmu.h提供的api接口
typedef struct {
int contextId;
/**< Return value after registering the PMU group via HAP_register_pmu_group. */
unsigned int num_events;
/**< Input parameter specifying the number of PMU events register.*/
unsigned short pmu_events[4];
/**< Input parameter specifying the list of PMU events to register.*/
unsigned int pmu_value[4];
/**< Output parameter containing values of PMU events registered. */
} HAP_pmu_group_config_t;
int __attribute__((weak)) __HAP_register_pmu_group(HAP_pmu_group_config_t* pmu_config);
int __attribute__((weak)) __HAP_deregister_pmu_group(int contextId);
int __attribute__((weak)) __HAP_read_pmu_group(HAP_pmu_group_config_t* pmu_config);
int __attribute__((weak)) __HAP_register_pmu_event(unsigned short pmu_event);
int __attribute__((weak)) __HAP_deregister_pmu_event(unsigned short pmu_event);
unsigned int __attribute__((weak)) __HAP_read_pmu_event(unsigned short pmu_event);
这些HAP api接口的最大问题虽然能用,但接口比较老,只能支持4个pmu event的get/config,实际上硬件是支持最大8个pmu一组来同时获取的。
hexagon dsp sdk中 HAP_user_pmu.md文档有以下描述:
- The HAP PMU APIs are not accessible from unsigned PD.
- HAP PMU APIs only work on debug-enabled devices.
- aDSP and cDSP DCVS relies on a set of PMU events to monitor DSP statistics and make necessary decisions.Using these HAP APIs to register PMU events results in DCVS no longer being able to track these events. This might lead DCVS to making incorrect decisions.
市面上买的高通设备,普通用户没有root权限,设备debug-fuse也断了,设备无法正常签名,无法使用HAP PMU API,但设备原厂是不受影响的。
在有root权限的设备上,如果用户自己获取pmu导致dcvs功能受影响,可以通过主动调频机制来弥补,没有工作负载时,设置比较低的主频,有工作负载时,vote比较高的频率,程序主动去控制比dcvs预测机制去调频要更加高效,所以这个限制也影响有限。
方法2:基于pmu_ctrs_test demo
可以参考hexagon dsp sdk中/tools/HEXAGON_Tools/8.7.06/Examples/Profiling/pmu_ctrs_test这个 demo,通过汇编接口来直接配置、读写pmu寄存器,但问题是这个demo也太老了,而且s44-s47这几个寄存器标量核文档是是搜不到的,pmucnt0-pmucnt3这4个寄存器是能从文档查到的。
/* configure the PMU with a threadmask and 4 events to observe */
static forceinline void pmu_config( int tmask, char event0, char event1,
char event2, char event3 )
{
unsigned int pmuevtcfg = (event3<<24) + (event2<<16) + (event1<<8) + event0;
asm volatile(
"pmucfg = %0\n"
"pmuevtcfg = %1\n"
:
: "r"(tmask), "r"(pmuevtcfg)
: "r0" );
}
/* configure the PMU with a threadmask and 4 events to observe */
static forceinline void pmu_config1( int tmask, char event0, char event1,
char event2, char event3 )
{
unsigned int pmuevtcfg1 = (event3<<24) + (event2<<16) + (event1<<8) + event0;
asm volatile(
"pmucfg = %0\n"
"s54 = %1\n"
:
: "r"(tmask), "r"(pmuevtcfg1)
: "r0" );
}
/* get PMU counter 0, 1, 2, 3 */
static forceinline unsigned int get_pmu_counter( int event )
{
unsigned int counter;
switch (event) {
case 0:
asm volatile ("%0 = pmucnt0\n" : "=r"(counter));
break;
case 1:
asm volatile ("%0 = pmucnt1\n" : "=r"(counter));
break;
case 2:
asm volatile ("%0 = pmucnt2\n" : "=r"(counter));
break;
case 3:
asm volatile ("%0 = pmucnt3\n" : "=r"(counter));
break;
case 4:
asm volatile ("%0 = s44\n" : "=r"(counter));
break;
case 5:
asm volatile ("%0 = s45\n" : "=r"(counter));
break;
case 6:
asm volatile ("%0 = s46\n" : "=r"(counter));
break;
case 7:
asm volatile ("%0 = s47\n" : "=r"(counter));
break;
}
return counter;
}
方法3:基于itrace提供的接口
见sdk下libs/itrace目录及examples/itrace
- itrace提供了统一的针对cpu/dsp的 add pmu events/register pmu events/创建sampler/add marker/start section/stop section api接口
- 即使没有dsp源码,也能从cpu fastrpc侧注册dsp pmu events并进行监控,这也是最方便也最常使用的方法
- itrace可以注册一个sampler,指定轮寻周期,实现对一组pmu的持续监控
- itrace也可以每次切换一组pmu cfg配置来读取,实现对多余8个pmu的分组读取,因此分组时比较有技巧,也需要取舍。
- itrace cpu端的logger负责对pmu event val进行后处理,存json,csv等格式的文件,cpu代码及dsp代码也能直接通过以下api函数来直接获取注册的pmu val
itrace_return_t itrace_start_section(itrace_profiler_handle_t profiler_handle, const char* section_name, itrace_measured_events_t* measured_events);
itrace_return_t itrace_end_section(itrace_profiler_handle_t profiler_handle, itrace_measured_events_t* measured_events);
itrace_return_t itrace_read_events(itrace_profiler_handle_t profiler_handle, itrace_measured_events_t* measured_events);
- itrace events are measured when either of these scenarios occur:
- the user specifies the start or end of a section with @ref itrace_start_section or @ref itrace_end_section
- the user read events with @ref itrace_read_events
- a sampler initiated with @ref itrace_start_periodic_events_reader reads events
sdk 6.0.0.2中的itrace demo在v69,v75两个dsp上测试有bug,表现为ITRACE_PM_DELTA模式及ITRACE_PM_NORMALIZED模式没有生效,可视化trace文件上pmu呈现效果有问题。因此需要通过上面介绍的itrace api接口自己读出来pmu value进行后处理。
typedef enum {
ITRACE_PM_DEFAULT, /*!< Use ITRACE_PM_DELTA for running counters and ITRACE_PM_RAW otherwise */
ITRACE_PM_RAW, /*!< Event value unmodified. */
ITRACE_PM_DELTA, /*!< Difference with previously measured event value */
ITRACE_PM_NORMALIZED /*!< Difference with previously measured event value, normalized by elapsed time */
} itrace_processing_mode_t;
跑itrace demo应该有的效果如下(Qualcomm/Hexagon_SDK/5.5.0.1/docs/doxygen/itrace/index.html)
但实际上测出了的是
方法4:基于qurt os api提供的接口
hexagon dsp sdk rtos/qurt/computev69/include/qurt/qurt_pmu.h
根据需要监控的pmu的id,设置到对应的QURT_PMUEVTCFG/QURT_PMUEVTCFG1寄存器中,每个event占据8bit,然后通过qurt_pmu_get_pmucnt 接口一次读回8个pmu即可,非常方便,但多余8个pmu的自动周期性sample逻辑就需要用户自己做了,没有itrace那么方便。其实高通如果能把irace的bug解了那个工具也很好用,是首选,那个bug不解,itrace是看pmu信息有严重功能缺陷的!!!
/**@ingroup func_qurt_pmu_set
Sets the value of the specified PMU register.
@note1hang Setting PMUEVTCFG automatically clears the PMU registers PMUCNT0
through PMUCNT3.
@param[in] reg_id PMU register. Values:
- #QURT_PMUCNT0
- #QURT_PMUCNT1
- #QURT_PMUCNT2
- #QURT_PMUCNT3
- #QURT_PMUCFG
- #QURT_PMUEVTCFG
- #QURT_PMUCNT4
- #QURT_PMUCNT5
- #QURT_PMUCNT6
- #QURT_PMUCNT7
- #QURT_PMUEVTCFG1 @tablebulletend
@param[in] reg_value Register value.
@return
None.
@dependencies
None.
*/
void qurt_pmu_set (int reg_id, unsigned int reg_value);
/**@ingroup func_qurt_pmu_get
Gets the PMU register.\n
Returns the current value of the specified PMU register.
@param[in] reg_id PMU register. Values:
- #QURT_PMUCNT0
- #QURT_PMUCNT1
- #QURT_PMUCNT2
- #QURT_PMUCNT3
- #QURT_PMUCFG
- #QURT_PMUEVTCFG
- #QURT_PMUCNT4
- #QURT_PMUCNT5
- #QURT_PMUCNT6
- #QURT_PMUCNT7
- #QURT_PMUEVTCFG1 @tablebulletend
@return
Integer -- Current value of the specified PMU register.
@dependencies
None.
*/
unsigned int qurt_pmu_get (int reg_id);
/**@ingroup qurt_pmu_get_pmucnt
Reads PMU counters in a single trap.
@param[out] buf - buffer to save values read from PMU counters.
buffer size should be atleast 32 bytes to read all 8 PMU counters.
@return
QURT_EOK -- On successful read.
QURT_EFATAL -- On failure.
@dependencies
None.
*/
int qurt_pmu_get_pmucnt (void * buf);
总结
本文介绍了高通dsp sdk中怎么获取pmu寄存器定义,介绍了几种配置/获取pmu的方法及存在的问题及解决方法,欢迎留言交流。