ARM Cortex A7 在内核模式下返回 PMCCNTR = 0,在用户模式下返回非法指令(即使在 PMUSERENR = 1 之后)
ARM Cortex A7 returning PMCCNTR = 0 in kernel mode, and Illegal instruction in user mode (even after PMUSERENR = 1)
我想读取具有 ARM Cortex A7 内核的 Raspberry Pi 2 上的周期计数寄存器 (PMCCNTR)。我为它编译了一个内核模块如下:
#include <linux/module.h>
#include <linux/kernel.h>
int init_module()
{
volatile u32 PMCR, PMUSERENR, PMCCNTR;
// READ PMCR
PMCR = 0xDEADBEEF;
asm volatile ("mrc p15, 0, %0, c9, c12, 0\n\t" : "=r" (PMCR));
printk (KERN_INFO "PMCR = %x\n", PMCR);
// READ PMUSERENR
PMUSERENR = 0xDEADBEEF;
asm volatile ("mrc p15, 0, %0, c9, c14, 0\n\t" : "=r" (PMUSERENR));
printk (KERN_INFO "PMUSERENR = %x\n", PMUSERENR);
// WRITE PMUSERENR = 1
asm volatile ("mcr p15, 0, %0, c9, c14, 0\n\t" : : "r" (1));
// READ PWMUSERENR AGAIN
asm volatile ("mrc p15, 0, %0, c9, c14, 0\n\t" : "=r" (PMUSERENR));
printk (KERN_INFO "PMUSERENR = %x\n", PMUSERENR);
// READ PMCCNTR
PMCCNTR = 0xDEADBEEF;
asm volatile ("mrc p15, 0, %0, c9, c13, 0\n\t" : "=r" (PMCCNTR));
printk (KERN_ALERT "PMCCNTR = %x\n", PMCCNTR);
return 0;
}
void cleanup_module()
{
}
MODULE_LICENSE("GPL");
并且在 insmod
之后,我在 /var/log/kern.log
中观察到以下内容:
PMCR = 41072000
PMUSERENR = 0
PMUSERENR = 1
PMCCNTR = 0
当我尝试从用户模式读取 PMCCNTR 时,我收到了非法指令,即使 PMUSERENR 已设置为 1。
为什么PMCCNTR在内核态读为0,在用户态读为非法指令?为了启用 PMCCNTR,还有什么我需要做但我没有做的吗?
更新 1
部分解决。多核问题的解决方案是这样调用on_each_cpu
:
#include <linux/module.h>
#include <linux/kernel.h>
static void enable_ccnt_read(void* data)
{
// WRITE PMUSERENR = 1
asm volatile ("mcr p15, 0, %0, c9, c14, 0\n\t" : : "r" (1));
}
int init_module()
{
on_each_cpu(enable_ccnt_read, NULL, 1);
return 0;
}
void cleanup_module()
{
}
MODULE_LICENSE("GPL");
我现在可以从用户区读取 PMCCNTR:
#include <iostream>
unsigned ccnt_read ()
{
volatile unsigned cc;
asm volatile ("mrc p15, 0, %0, c9, c13, 0" : "=r" (cc));
return cc;
}
int main() {
std::cout << ccnt_read() << std::endl;
}
要运行一个特定内核上的用户空间程序,你可以像这样使用taskset
(例如,运行在内核2上):
$ taskset -c 2 ./ccnt_read
0
PMCCNTR 仍然没有递增。他们需要以某种方式 "switched on"。
您所做的是启用计数器的用户级别访问权限。您还没有启用计数器本身。除了启用访问之外,您还必须对 PMCNTENSET 的第 31 位(C 位)进行编程以启用计数。这连同您的 on_each_cpu() 更改应该启用您正在寻找的功能。
请注意:如果一个进程在 CCNT 读取之间迁移到不同的核心,您的测量结果将会一团糟。
这是后代的工作解决方案:
内核模块:
#include <linux/module.h>
#include <linux/kernel.h>
static void enable_ccnt_read(void* data)
{
// PMUSERENR = 1
asm volatile ("mcr p15, 0, %0, c9, c14, 0" :: "r"(1));
// PMCR.E (bit 0) = 1
asm volatile ("mcr p15, 0, %0, c9, c12, 0" :: "r"(1));
// PMCNTENSET.C (bit 31) = 1
asm volatile ("mcr p15, 0, %0, c9, c12, 1" :: "r"(1 << 31));
}
int init_module()
{
on_each_cpu(enable_ccnt_read, NULL, 1);
return 0;
}
void cleanup_module()
{
}
MODULE_LICENSE("GPL");
客户端程序:
#include <iostream>
unsigned ccnt_read ()
{
volatile unsigned cc;
asm volatile ("mrc p15, 0, %0, c9, c13, 0" : "=r" (cc));
return cc;
}
int main() {
std::cout << ccnt_read() << std::endl;
}
我正在运行这个芯片上仿真,发现了比上面描述的更进一步的问题。性能计数器在启用时必须重置,否则将从未定义的值生成断言。这意味着PMCR寄存器应该设置如下:
// PMCR.E (bit 0) = 1, PMCR.C (bit 2) = 1
asm volatile ("mcr p15, 0, %0, c9, c12, 0" :: "r"(5));
我想读取具有 ARM Cortex A7 内核的 Raspberry Pi 2 上的周期计数寄存器 (PMCCNTR)。我为它编译了一个内核模块如下:
#include <linux/module.h>
#include <linux/kernel.h>
int init_module()
{
volatile u32 PMCR, PMUSERENR, PMCCNTR;
// READ PMCR
PMCR = 0xDEADBEEF;
asm volatile ("mrc p15, 0, %0, c9, c12, 0\n\t" : "=r" (PMCR));
printk (KERN_INFO "PMCR = %x\n", PMCR);
// READ PMUSERENR
PMUSERENR = 0xDEADBEEF;
asm volatile ("mrc p15, 0, %0, c9, c14, 0\n\t" : "=r" (PMUSERENR));
printk (KERN_INFO "PMUSERENR = %x\n", PMUSERENR);
// WRITE PMUSERENR = 1
asm volatile ("mcr p15, 0, %0, c9, c14, 0\n\t" : : "r" (1));
// READ PWMUSERENR AGAIN
asm volatile ("mrc p15, 0, %0, c9, c14, 0\n\t" : "=r" (PMUSERENR));
printk (KERN_INFO "PMUSERENR = %x\n", PMUSERENR);
// READ PMCCNTR
PMCCNTR = 0xDEADBEEF;
asm volatile ("mrc p15, 0, %0, c9, c13, 0\n\t" : "=r" (PMCCNTR));
printk (KERN_ALERT "PMCCNTR = %x\n", PMCCNTR);
return 0;
}
void cleanup_module()
{
}
MODULE_LICENSE("GPL");
并且在 insmod
之后,我在 /var/log/kern.log
中观察到以下内容:
PMCR = 41072000
PMUSERENR = 0
PMUSERENR = 1
PMCCNTR = 0
当我尝试从用户模式读取 PMCCNTR 时,我收到了非法指令,即使 PMUSERENR 已设置为 1。
为什么PMCCNTR在内核态读为0,在用户态读为非法指令?为了启用 PMCCNTR,还有什么我需要做但我没有做的吗?
更新 1
部分解决。多核问题的解决方案是这样调用on_each_cpu
:
#include <linux/module.h>
#include <linux/kernel.h>
static void enable_ccnt_read(void* data)
{
// WRITE PMUSERENR = 1
asm volatile ("mcr p15, 0, %0, c9, c14, 0\n\t" : : "r" (1));
}
int init_module()
{
on_each_cpu(enable_ccnt_read, NULL, 1);
return 0;
}
void cleanup_module()
{
}
MODULE_LICENSE("GPL");
我现在可以从用户区读取 PMCCNTR:
#include <iostream>
unsigned ccnt_read ()
{
volatile unsigned cc;
asm volatile ("mrc p15, 0, %0, c9, c13, 0" : "=r" (cc));
return cc;
}
int main() {
std::cout << ccnt_read() << std::endl;
}
要运行一个特定内核上的用户空间程序,你可以像这样使用taskset
(例如,运行在内核2上):
$ taskset -c 2 ./ccnt_read
0
PMCCNTR 仍然没有递增。他们需要以某种方式 "switched on"。
您所做的是启用计数器的用户级别访问权限。您还没有启用计数器本身。除了启用访问之外,您还必须对 PMCNTENSET 的第 31 位(C 位)进行编程以启用计数。这连同您的 on_each_cpu() 更改应该启用您正在寻找的功能。
请注意:如果一个进程在 CCNT 读取之间迁移到不同的核心,您的测量结果将会一团糟。
这是后代的工作解决方案:
内核模块:
#include <linux/module.h>
#include <linux/kernel.h>
static void enable_ccnt_read(void* data)
{
// PMUSERENR = 1
asm volatile ("mcr p15, 0, %0, c9, c14, 0" :: "r"(1));
// PMCR.E (bit 0) = 1
asm volatile ("mcr p15, 0, %0, c9, c12, 0" :: "r"(1));
// PMCNTENSET.C (bit 31) = 1
asm volatile ("mcr p15, 0, %0, c9, c12, 1" :: "r"(1 << 31));
}
int init_module()
{
on_each_cpu(enable_ccnt_read, NULL, 1);
return 0;
}
void cleanup_module()
{
}
MODULE_LICENSE("GPL");
客户端程序:
#include <iostream>
unsigned ccnt_read ()
{
volatile unsigned cc;
asm volatile ("mrc p15, 0, %0, c9, c13, 0" : "=r" (cc));
return cc;
}
int main() {
std::cout << ccnt_read() << std::endl;
}
我正在运行这个芯片上仿真,发现了比上面描述的更进一步的问题。性能计数器在启用时必须重置,否则将从未定义的值生成断言。这意味着PMCR寄存器应该设置如下:
// PMCR.E (bit 0) = 1, PMCR.C (bit 2) = 1
asm volatile ("mcr p15, 0, %0, c9, c12, 0" :: "r"(5));