Android

Overclocking an Android phone running with an MSM core

Introduction

This article describes the over-clocking process of an Android phone (specifically the Sony Arc running an MSM chip at the 1Ghz default speed). It can be done without the need of recompiling the kernel, meaning that you don’t have to unlock the bootloader.

The frequencies / voltages / PLL rates are hardcoded into the kernel upon compiling time – so that the operating system could benefit from CPU governors that change the phone speed upon demand, thus helping battery life but still allowing fast operation when needed.
I decided to write this article to describe the step by step process of allowing the phone to go to a higher frequency by altering these hardcoded values by the use of a kernel module at runtime.

The first person (that I’ve found) to successfully do this documented the process here – it is meant for OMAP2 processors. His insight was right, it can be done for other type of processors also!

DISCLAIMER OF LIABILITIES: I have posted this tutorial for academic purposes – over-clocking can lead to many bad things and I shall not be held responsible for any damage direct or indirect if you proceed.

Identifing the kernel structures

The first thing when trying to overclock is to write a CPU governor that tries to set a higher than maximum frequency. However, as it will be seen below, there are validations in place that forbid values higher than what the kernel knows. Our goal is to manipulate kernel limits within kmem (the kernel memory) so that … the validation passes.

cpufreq_policy

Having compiled the cpufreq_smartass.c governor for different Sony phones (X10 / Arc) I already had an idea of at least one check:

static void smartass_update_min_max(struct smartass_info_s *this_smartass, struct cpufreq_policy *policy, int suspend) {
        if (suspend) {
                this_smartass->min_speed = policy->min;
                this_smartass->max_speed = // sleep_max_freq; but make sure it obeys the policy min/max
                        policy->max > sleep_max_freq ? (sleep_max_freq > policy->min ? sleep_max_freq : policy->min) : policy->max;
...

As you can see, there’s this policy object that seems to know about the maximum frequency of the phone. Upon checking over governors and searching kernel source code there’s a method that returns the policy object for each CPU, method is called cpufreq_cpu_get and luckily, the kernel exposes that method:

root@android:/mnt/sdcard # cat /proc/kallsyms | grep cpufreq_cpu_get
803c266c T cpufreq_cpu_get

We write a simple module that retrieves the global pointer and then changes the MAX values to allow higher validations of frequencies. Checking the structure cpufreq_policy we can identify its members involved into the msm_cpufreq_verify

Module snippet:

for(i = 0; i < cpus; i++){
        policy = cpufreq_cpu_get(i);

        policy->cpuinfo.max_freq = 1200000;
        policy->user_policy.max = 1200000;
        policy->max = 1200000;
...

However, this is not enough. It only modifies the global values used in validations, there’s still a long way to go until the frequency really changes.

cpufreq_frequency_table

Going back to the governor – we can identify which method applies the new frequencies (policy):

if (new_freq != policy->cur) {
... 
        __cpufreq_driver_target(policy, new_freq, relation);
...
}

Since now the governors will be allowed higher frequencies, let’s begin at __cpufreq_driver_target.

The see the call invocation path, please check kernel source code by clicking on the method above:
a) governor computes new frequencies and calls __cpufreq_driver_target to set the new speed.
b) which passes the values to cpufreq_driver->target(...)
c) target(...) is a pointer to method msm_cpufreq_target
d) which then calls cpufreq_frequency_table_target to retrieve the frequency indexes in the global frequency table.

Ooops.

There is thus a global frequency *table, and cpufreq_frequency_table_target job is to match the policy against this table and return an index into it.

*table is passed as argument and is retrieved just before the call to cpufreq_frequency_table_target within the msm_cpufreq_target method. It is retrieved using method cpufreq_frequency_get_table.

We’re lucky again, the kernel exposes this method too:

root@android:/mnt/sdcard # cat /proc/kallsyms | grep cpufreq_frequency_get_table
803c64e0 T cpufreq_frequency_get_table

It means we can also get a reference to the struct cpufreq_frequency_table *table and patch the heck out of it :). I’ll skip the boring details – technically the table should be processed and displayed to see what values we have, also taking into account that some of the values could be invalid. To see a real example of how to iterate over the table, the method show_available_freqs could be checked.

for (i = 0; (table[i].frequency != CPUFREQ_TABLE_END); i++) {
        if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
            continue;
        count += sprintf(&buf[count], "%d ", table[i].frequency);
}

On Sony Arc phone index holding the 1024000Hz frequency (that we want updated to 1200000Hz) has index number 5 in the global table of frequencies, thus … we can patch this value also:

for(i = 0; i < cpus; i++){
        policy = cpufreq_cpu_get(i);

        policy->cpuinfo.max_freq = 1200000;
        policy->user_policy.max = 1200000;
        policy->max = 1200000;

        table = cpufreq_frequency_get_table(i);
        table[5].frequency = 1200000;
        ...

One word of advice.

We covered the policy and global table of frequencies, however only now to fun begins since the kernel on the phone doesn’t expose any more methods (as you’ll see below) – the only solution will be to hunt for them using disassembly, double pointer dereferences and writing directly back into the kernel memory at specific addresses that have to be identified.
Have a break and return when ready.

acpu_freq_tbl

If we go back to the msm_cpufreq_target and we go pass everything that looks uninteresting, we find the method acpuclk_set_rate. To find it, we need to search the kernel sources in a file named acpuclock-7x30.c:

int acpuclk_set_rate(int cpu, unsigned long rate, enum setrate_reason reason)
{
        struct clkctl_acpu_speed *tgt_s, *strt_s;
        int res, rc = 0;

        if (reason == SETRATE_CPUFREQ)
                mutex_lock(&drv_state.lock);

        strt_s = drv_state.current_speed;

        if (rate == strt_s->acpu_clk_khz)
                goto out;

        for (tgt_s = acpu_freq_tbl; tgt_s->acpu_clk_khz != 0; tgt_s++) {
                if (tgt_s->acpu_clk_khz == rate)
                        break;
        }
...
out:
        if (reason == SETRATE_CPUFREQ)
                mutex_unlock(&drv_state.lock);

        return rc;
}

The for above looks like is doing another validation. If the specified rate doesn’t match any of the frequencies with the acpu_freq_tbl, it refuses to apply the new rates. Which is annoying, another table with frequencies to patch.

This time the kernel is not helpful – table is not exported …

root@android:/mnt/sdcard # cat /proc/kallsyms | grep acpu_freq_tbl
root@android:/mnt/sdcard #

Upon closer examination, the variable is statically defined within the kernel:

static struct clkctl_acpu_speed acpu_freq_tbl[] = {
        { 0, 24576,  SRC_LPXO, 0, 0,  30720000,  900, VDD_RAW(900) },
        { 0, 61440,  PLL_3,    5, 11, 61440000,  900, VDD_RAW(900) },
        { 1, 122880, PLL_3,    5, 5,  61440000,  900, VDD_RAW(900) },
        { 0, 184320, PLL_3,    5, 4,  61440000,  900, VDD_RAW(900) },
        { 0, MAX_AXI_KHZ, SRC_AXI, 1, 0, 61440000, 900, VDD_RAW(900) },
        { 1, 245760, PLL_3,    5, 2,  61440000,  900, VDD_RAW(900) },
        { 1, 368640, PLL_3,    5, 1,  122800000, 900, VDD_RAW(900) },
        /* AXI has MSMC1 implications. See above. */
        { 1, 768000, PLL_1,    2, 0,  153600000, 1050, VDD_RAW(1050) },
        /*
         * AXI has MSMC1 implications. See above.
         */
        { 1, 806400,  PLL_2, 3, 0, UINT_MAX, 1100, VDD_RAW(1100), &pll2_tbl[0]},
        { 1, 1024000, PLL_2, 3, 0, UINT_MAX, 1200, VDD_RAW(1200), &pll2_tbl[1]},
        { 1, 1200000, PLL_2, 3, 0, UINT_MAX, 1200, VDD_RAW(1200), &pll2_tbl[2]},
        { 1, 1401600, PLL_2, 3, 0, UINT_MAX, 1250, VDD_RAW(1250), &pll2_tbl[3]},
        { 0 }
};

The biggest trouble is how to identify its location in memory, and for this … the only way to find out is to decompile the source code.

acpu_freq_tbl hunt

Since the variable is static and the acpuclk_set_rate makes use of it, for sure there have to be a reference to it within the binary version of acpuclk_set_rate. This is where things become kernel specific, a different kernel could have the static defined at a different address …

But anyway – there is a glimmer of hope:

root@android:/mnt/sdcard # cat /proc/kallsyms | grep -A 1 acpuclk_set_rate
800bb324 T acpuclk_set_rate
800bb694 T acpuclk_wait_for_irq

The method is exposed by the kernel and it has 0x800bb694 – 0x800bb324 = 880 (in decimal) binary size.

We have to grab it from kmem and disassemble it on a PC with a cross compiler:

First on the phone:

busybox dd bs=1 count=880 skip=2148250404 if=/dev/kmem of=/sdcard/acpuclk_set_rate

Then on the PC:

arm-linux-objcopy --change-addresses=0x800bb324 -I binary -O elf32-littlearm -B arm acpuclk_set_rate acpuclk_set_rate.elf
arm-linux-objcopy --set-section-flags .data=code acpuclk_set_rate.elf
arm-linux-objdump -D acpuclk_set_rate.elf > acpuclk_set_rate.asm

and the result is this .asm file which I’ve tried to annotate with the C code above:

800bb324 <_binary_acpuclk_set_rate_start>:

// prepare arguments, etc

800bb324:	e92d47f3 	push	{r0, r1, r4, r5, r6, r7, r8, r9, sl, lr}
800bb328:	e2527000 	subs	r7, r2, #0
800bb32c:	e1a06001 	mov	r6, r1

// if (reason == SETRATE_CPUFREQ)

800bb330:	1a000001 	bne	800bb33c <_binary_acpuclk_set_rate_start+0x18>

// mutex_lock(&drv_state.lock);

800bb334:	e59f031c 	ldr	r0, [pc, #796]	; 800bb658 <_binary_acpuclk_set_rate_start+0x334>
800bb338:	eb1288ea 	bl	8055d6e8 <_binary_acpuclk_set_rate_end+0x4a2054>

// strt_s = drv_state.current_speed;

800bb33c:	e59f3318 	ldr	r3, [pc, #792]	; 800bb65c <_binary_acpuclk_set_rate_start+0x338>
800bb340:	e5935000 	ldr	r5, [r3]
800bb344:	e5953004 	ldr	r3, [r5, #4]

// if (rate == strt_s->acpu_clk_khz)

800bb348:	e1560003 	cmp	r6, r3

// in case of false above, prepare the for cycle that comes
// for (tgt_s = acpu_freq_tbl; tgt_s->acpu_clk_khz != 0; tgt_s++) {
// it initializes the 0 loop condition (in r6) and it is clear that
//  r4 now contains the address of the acpu_freq_tbl !!

800bb34c:	03a06000 	moveq	r6, #0
800bb350:	159f4308 	ldrne	r4, [pc, #776]	; 800bb660 <_binary_acpuclk_set_rate_start+0x33c>

....

// referenced acpu_freq_tbl pointer in the ldrne above!!

800bb660:	80750948 	rsbshi	r0, r5, r8, asr #18

Now we know where acpu_freq_tbl is in memory ๐Ÿ™‚ at location 0x80750948.
Here’s the type of each entry in the acpu_freq_tbl table:

struct clkctl_acpu_speed {
        unsigned int    use_for_scaling;
        unsigned int    acpu_clk_khz;
        int             src;
        unsigned int    acpu_src_sel;
        unsigned int    acpu_src_div;
        unsigned int    axi_clk_hz;
        unsigned int    vdd_mv;
        unsigned int    vdd_raw;
        struct pll      *pll_rate;
        unsigned long   lpj; /* loops_per_jiffy */
};

We could just initialize a variable of type struct clkctl_acpu_speed * and … in fact why talking and not doing it ?

for(i = 0; i < cpus; i++){
        policy = cpufreq_cpu_get(i);

        policy->cpuinfo.max_freq = 1200000;
        policy->user_policy.max = 1200000;
        policy->max = 1200000;

        table = cpufreq_frequency_get_table(i);
        table[5].frequency = 1200000;

        acpu_freq_tbl = (struct clkctl_acpu_speed *)0x80750948;
        acpu_freq_tbl[9].acpu_clk_khz = 1200000;
        acpu_freq_tbl[9].pll_rate->l=125;
        acpu_freq_tbl[9].pll_rate->m=0;
        acpu_freq_tbl[9].pll_rate->n=1;
        acpu_freq_tbl[9].pll_rate->pre_div=1;

        ...

If you have the kernel source code, you can see that in this table, the 1024000Hz frequency is at index 9 instead of 5. We just have to replace the frequency as well as the PLL rates with the correct values for frequency 1200000Hz.
We could also activate ALL frequencies (by setting use_for_scaling = 1 for all of the entries) but we could run into issues with the final kernel object that needs to patched …. that is the cpufreq_stats_table table …

cpufreq_stats_table hunt

The cat /sys/devices/system/cpu/cpu0/cpufreq/stats/time_in_state which shows how much the system spent in each frequency is useful for tracking battery usage and is also used by SetCPU / CpuSpy etc… applications to inform the user.

Tracking it down is very similar to the steps above – the place to look for (that uses the table and creates the sysfs file) is the method show_time_in_state:

root@android:/sys/devices/system/cpu/cpu0/cpufreq/stats # cat /proc/kallsyms | grep -A 1 show_time_in_state
803c3bf4 t show_time_in_state
803c3c74 t show_total_trans

busybox dd bs=1 count=128 skip=2151431156 if=/dev/kmem of=/sdcard/show_time_in_state

arm-linux-objcopy --change-addresses=0x803c3bf4 -I binary -O elf32-littlearm -B arm show_time_in_state show_time_in_state.elf
arm-linux-objcopy --set-section-flags .data=code show_time_in_state.elf
arm-linux-objdump -D show_time_in_state.elf > show_time_in_state.asm

there are still few things to notice:

a) this table is not static, but is allocated at runtime based on the frequencies enabled in the acpu_freq_tbl table.

What you get when decompiling is in fact the address where the address of the cpufreq_stats_table table is stored.


803c3bf4:	e59f3070 	ldr	r3, [pc, #112]	; 803c3c6c 
...
803c3c00:	e5935000 	ldr	r5, [r3]                
...
// struct cpufreq_stats *stat = per_cpu(cpufreq_stats_table, policy->cpu);

// r0 holds the cpufreq_stats_table address and in pseudocode the value is r0 = [r5] = [ [r3] ] = [ [803c3c6c] ]

803c3c10:	e5950000 	ldr	r0, [r5]
...
803c3c6c:	80781284 	rsbshi	r1, r8, r4, lsl #5 

What it means is that we need two pointers now to land on its space in kmem:

        stat = *(struct cpufreq_stats **)0x80781284;
        stat->freq_table[5] = 1200000;

Now the times will also be reported correctly. Unfortunately, the code related to keeping track of the time in the states makes use of the frequency itself and not any index. Thus, if this is not correctly set, the time in state for 1200000 won’t be updated, since value 1200000 is not in the table.

b) although it could be interesting of overwriting the pointer with a newly allocated table that keeps track of all possible frequencies, it would make the code less clear …

Maybe in the future I will work on doing just this ..

I hope it helps, and my impression is that this could be applied to any type of phones, just like the Milestone-Overclock team noticed ๐Ÿ™‚ hats off to them!

9 Comments

  1. Bernd.Defy

    Thank you very much! I managed to build a overclock-Module for Motorola Defy Mini. Without your blogentry it would have been impossible.

  2. unexpected one

    Hello, Im really interested in it becuase i cant unlock bootloader on my Arc S, so my question is can you make a file for Arc S with overclock to 1.6-1.7Ghz? I know I ask for much but these codes are magic for me :/ and another minus that my english isnt so good, so its hard to understand this instruction. I can wait for it a lot of time, you can make it 10 minutes everyday but you would be awesome if you help ๐Ÿ˜‰ anyway you made already a module for Arc so is it so hard to make another one for Arc S? ๐Ÿ˜ฎ

  3. dmd

    @Bernd.Defy

    Could you please share that module? ๐Ÿ™‚

  4. Comment by post author

    Hello,

    It is difficult for me because I don’t have a phone where I can check out all those values ..

    I’m sorry …

  5. It’s a shame I don’t have Linux, or I’d look into this for the Xperia Play. I’m assuming this is required?

    How long would it take to modify this to work on the Play? I personally have an Unlocked Bootloader, but I’d imagine the Play community would be very welcoming of such a mod..

  6. unexpected one

    ๐Ÿ™

  7. rootdefyxt320

    Hello, how do you extract the mV values of the CPU for a MSM chipset? Thanks

  8. Creepy guy

    Hey man, could you make module for Xperia Arc S with overclock to 1.6ghz? Just change values from 1.2ghz to 1.6 (because Xperia Arc and Arc S are almost the same) ๐Ÿ˜€ I can be your tester!

  9. raspifari

    Please, can you just recompile the 2 modules with 1.6ghz and 1.8ghz without change any other value. I have the arc s and I can’t unlock the bootloader. And I can’t do that my self because i’m a noob in unix, in C and in english too. I can help you if you need important information about the arc s, just tell me wish command I had to execute in the terminal.
    Best regards, Raspifari

Leave a Reply