Search code examples
linuxarmraspberry-piraspberry-pi2

ARM Cortex A7 returning PMCCNTR = 0 in kernel mode, and Illegal instruction in user mode (even after PMUSERENR = 1)


I want to read the cycle count register (PMCCNTR) on a Raspberry Pi 2, which has an ARM Cortex A7 core. I compile a kernel module for it as follows:

#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");

and, after insmod, I observe the following in /var/log/kern.log:

PMCR = 41072000
PMUSERENR = 0
PMUSERENR = 1
PMCCNTR = 0

When I try to read PMCCNTR from user-mode, I get illegal instruction, even after PMUSERENR has been set to 1.

Why does PMCCNTR read as 0 in kernel mode, and an illegal instruction in user-mode? Is there something else I need to do that I'm not doing to enable the PMCCNTR?

Update 1

Partly solved. The solution to the multi-core issue is to call on_each_cpu like so:

#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");

I can now read PMCCNTR from userland:

#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;
}

To run a userland program on a specific core you can use taskset like so (example, run on core 2):

$ taskset -c 2 ./ccnt_read
0

The PMCCNTR are still not incrementing. They need to be "switched on" somehow.


Solution

  • What you have done is to enable User level access of the counter. You have not enabled the counter as such. In addition to enabling access you have to program 31st bit (C-bit) of PMCNTENSET to enable counting. This along with your on_each_cpu() changes should enable the functionality you are looking for.

    A word of caution: your measurements will be messed up, if a process migrates to a different core between CCNT reads.