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!
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.
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? ๐ฎ
dmd
@Bernd.Defy
Could you please share that module? ๐
viulian
Hello,
It is difficult for me because I don’t have a phone where I can check out all those values ..
I’m sorry …
Spizzy
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..
unexpected one
๐
rootdefyxt320
Hello, how do you extract the mV values of the CPU for a MSM chipset? Thanks
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!
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