diff options
author | Russell King <rmk+kernel@arm.linux.org.uk> | 2013-06-24 11:20:47 -0400 |
---|---|---|
committer | Russell King <rmk+kernel@arm.linux.org.uk> | 2013-06-24 11:21:02 -0400 |
commit | 03ad0025c36f19762595ff9583d2affda9b14852 (patch) | |
tree | d832833f3217e04eac90b0be1c9359ace9763d7a /arch | |
parent | fd8957a96d535a992067b2c206672323a885454a (diff) | |
parent | 7604537bbb5720376e8c9e6bc74a8e6305e3094d (diff) |
Merge branch 'mpidr-updates-for-rmk' of git://linux-arm.org/linux-2.6-lp into devel-stable
This patch series that implements MPIDR linearization through a simple
hashing algorithm and updates current cpu_{suspend}/{resume} code to use
the newly created hash structures to retrieve context pointers. It
represents a stepping stone for the implementation of power management code
on forthcoming multi-cluster ARM systems.
It has been tested on TC2 (dual cluster A15xA7 system), iMX6q, OMAP4 and
Tegra, with processors hitting low-power states requiring warm-boot resume
through the cpu_resume code path.
Diffstat (limited to 'arch')
-rw-r--r-- | arch/arm/include/asm/smp_plat.h | 18 | ||||
-rw-r--r-- | arch/arm/include/asm/suspend.h | 5 | ||||
-rw-r--r-- | arch/arm/kernel/asm-offsets.c | 6 | ||||
-rw-r--r-- | arch/arm/kernel/setup.c | 67 | ||||
-rw-r--r-- | arch/arm/kernel/sleep.S | 97 | ||||
-rw-r--r-- | arch/arm/kernel/suspend.c | 20 |
6 files changed, 195 insertions, 18 deletions
diff --git a/arch/arm/include/asm/smp_plat.h b/arch/arm/include/asm/smp_plat.h index 1c7b6f8101ae..6e63f29f41b7 100644 --- a/arch/arm/include/asm/smp_plat.h +++ b/arch/arm/include/asm/smp_plat.h | |||
@@ -70,4 +70,22 @@ static inline int get_logical_index(u32 mpidr) | |||
70 | return -EINVAL; | 70 | return -EINVAL; |
71 | } | 71 | } |
72 | 72 | ||
73 | /* | ||
74 | * NOTE ! Assembly code relies on the following | ||
75 | * structure memory layout in order to carry out load | ||
76 | * multiple from its base address. For more | ||
77 | * information check arch/arm/kernel/sleep.S | ||
78 | */ | ||
79 | struct mpidr_hash { | ||
80 | u32 mask; /* used by sleep.S */ | ||
81 | u32 shift_aff[3]; /* used by sleep.S */ | ||
82 | u32 bits; | ||
83 | }; | ||
84 | |||
85 | extern struct mpidr_hash mpidr_hash; | ||
86 | |||
87 | static inline u32 mpidr_hash_size(void) | ||
88 | { | ||
89 | return 1 << mpidr_hash.bits; | ||
90 | } | ||
73 | #endif | 91 | #endif |
diff --git a/arch/arm/include/asm/suspend.h b/arch/arm/include/asm/suspend.h index 1c0a551ae375..cd20029bcd94 100644 --- a/arch/arm/include/asm/suspend.h +++ b/arch/arm/include/asm/suspend.h | |||
@@ -1,6 +1,11 @@ | |||
1 | #ifndef __ASM_ARM_SUSPEND_H | 1 | #ifndef __ASM_ARM_SUSPEND_H |
2 | #define __ASM_ARM_SUSPEND_H | 2 | #define __ASM_ARM_SUSPEND_H |
3 | 3 | ||
4 | struct sleep_save_sp { | ||
5 | u32 *save_ptr_stash; | ||
6 | u32 save_ptr_stash_phys; | ||
7 | }; | ||
8 | |||
4 | extern void cpu_resume(void); | 9 | extern void cpu_resume(void); |
5 | extern int cpu_suspend(unsigned long, int (*)(unsigned long)); | 10 | extern int cpu_suspend(unsigned long, int (*)(unsigned long)); |
6 | 11 | ||
diff --git a/arch/arm/kernel/asm-offsets.c b/arch/arm/kernel/asm-offsets.c index ee68cce6b48e..ded041711beb 100644 --- a/arch/arm/kernel/asm-offsets.c +++ b/arch/arm/kernel/asm-offsets.c | |||
@@ -23,6 +23,7 @@ | |||
23 | #include <asm/thread_info.h> | 23 | #include <asm/thread_info.h> |
24 | #include <asm/memory.h> | 24 | #include <asm/memory.h> |
25 | #include <asm/procinfo.h> | 25 | #include <asm/procinfo.h> |
26 | #include <asm/suspend.h> | ||
26 | #include <asm/hardware/cache-l2x0.h> | 27 | #include <asm/hardware/cache-l2x0.h> |
27 | #include <linux/kbuild.h> | 28 | #include <linux/kbuild.h> |
28 | 29 | ||
@@ -145,6 +146,11 @@ int main(void) | |||
145 | #ifdef MULTI_CACHE | 146 | #ifdef MULTI_CACHE |
146 | DEFINE(CACHE_FLUSH_KERN_ALL, offsetof(struct cpu_cache_fns, flush_kern_all)); | 147 | DEFINE(CACHE_FLUSH_KERN_ALL, offsetof(struct cpu_cache_fns, flush_kern_all)); |
147 | #endif | 148 | #endif |
149 | #ifdef CONFIG_ARM_CPU_SUSPEND | ||
150 | DEFINE(SLEEP_SAVE_SP_SZ, sizeof(struct sleep_save_sp)); | ||
151 | DEFINE(SLEEP_SAVE_SP_PHYS, offsetof(struct sleep_save_sp, save_ptr_stash_phys)); | ||
152 | DEFINE(SLEEP_SAVE_SP_VIRT, offsetof(struct sleep_save_sp, save_ptr_stash)); | ||
153 | #endif | ||
148 | BLANK(); | 154 | BLANK(); |
149 | DEFINE(DMA_BIDIRECTIONAL, DMA_BIDIRECTIONAL); | 155 | DEFINE(DMA_BIDIRECTIONAL, DMA_BIDIRECTIONAL); |
150 | DEFINE(DMA_TO_DEVICE, DMA_TO_DEVICE); | 156 | DEFINE(DMA_TO_DEVICE, DMA_TO_DEVICE); |
diff --git a/arch/arm/kernel/setup.c b/arch/arm/kernel/setup.c index ca34224f891f..9048513cbe0d 100644 --- a/arch/arm/kernel/setup.c +++ b/arch/arm/kernel/setup.c | |||
@@ -478,6 +478,72 @@ void __init smp_setup_processor_id(void) | |||
478 | printk(KERN_INFO "Booting Linux on physical CPU 0x%x\n", mpidr); | 478 | printk(KERN_INFO "Booting Linux on physical CPU 0x%x\n", mpidr); |
479 | } | 479 | } |
480 | 480 | ||
481 | struct mpidr_hash mpidr_hash; | ||
482 | #ifdef CONFIG_SMP | ||
483 | /** | ||
484 | * smp_build_mpidr_hash - Pre-compute shifts required at each affinity | ||
485 | * level in order to build a linear index from an | ||
486 | * MPIDR value. Resulting algorithm is a collision | ||
487 | * free hash carried out through shifting and ORing | ||
488 | */ | ||
489 | static void __init smp_build_mpidr_hash(void) | ||
490 | { | ||
491 | u32 i, affinity; | ||
492 | u32 fs[3], bits[3], ls, mask = 0; | ||
493 | /* | ||
494 | * Pre-scan the list of MPIDRS and filter out bits that do | ||
495 | * not contribute to affinity levels, ie they never toggle. | ||
496 | */ | ||
497 | for_each_possible_cpu(i) | ||
498 | mask |= (cpu_logical_map(i) ^ cpu_logical_map(0)); | ||
499 | pr_debug("mask of set bits 0x%x\n", mask); | ||
500 | /* | ||
501 | * Find and stash the last and first bit set at all affinity levels to | ||
502 | * check how many bits are required to represent them. | ||
503 | */ | ||
504 | for (i = 0; i < 3; i++) { | ||
505 | affinity = MPIDR_AFFINITY_LEVEL(mask, i); | ||
506 | /* | ||
507 | * Find the MSB bit and LSB bits position | ||
508 | * to determine how many bits are required | ||
509 | * to express the affinity level. | ||
510 | */ | ||
511 | ls = fls(affinity); | ||
512 | fs[i] = affinity ? ffs(affinity) - 1 : 0; | ||
513 | bits[i] = ls - fs[i]; | ||
514 | } | ||
515 | /* | ||
516 | * An index can be created from the MPIDR by isolating the | ||
517 | * significant bits at each affinity level and by shifting | ||
518 | * them in order to compress the 24 bits values space to a | ||
519 | * compressed set of values. This is equivalent to hashing | ||
520 | * the MPIDR through shifting and ORing. It is a collision free | ||
521 | * hash though not minimal since some levels might contain a number | ||
522 | * of CPUs that is not an exact power of 2 and their bit | ||
523 | * representation might contain holes, eg MPIDR[7:0] = {0x2, 0x80}. | ||
524 | */ | ||
525 | mpidr_hash.shift_aff[0] = fs[0]; | ||
526 | mpidr_hash.shift_aff[1] = MPIDR_LEVEL_BITS + fs[1] - bits[0]; | ||
527 | mpidr_hash.shift_aff[2] = 2*MPIDR_LEVEL_BITS + fs[2] - | ||
528 | (bits[1] + bits[0]); | ||
529 | mpidr_hash.mask = mask; | ||
530 | mpidr_hash.bits = bits[2] + bits[1] + bits[0]; | ||
531 | pr_debug("MPIDR hash: aff0[%u] aff1[%u] aff2[%u] mask[0x%x] bits[%u]\n", | ||
532 | mpidr_hash.shift_aff[0], | ||
533 | mpidr_hash.shift_aff[1], | ||
534 | mpidr_hash.shift_aff[2], | ||
535 | mpidr_hash.mask, | ||
536 | mpidr_hash.bits); | ||
537 | /* | ||
538 | * 4x is an arbitrary value used to warn on a hash table much bigger | ||
539 | * than expected on most systems. | ||
540 | */ | ||
541 | if (mpidr_hash_size() > 4 * num_possible_cpus()) | ||
542 | pr_warn("Large number of MPIDR hash buckets detected\n"); | ||
543 | sync_cache_w(&mpidr_hash); | ||
544 | } | ||
545 | #endif | ||
546 | |||
481 | static void __init setup_processor(void) | 547 | static void __init setup_processor(void) |
482 | { | 548 | { |
483 | struct proc_info_list *list; | 549 | struct proc_info_list *list; |
@@ -825,6 +891,7 @@ void __init setup_arch(char **cmdline_p) | |||
825 | smp_set_ops(mdesc->smp); | 891 | smp_set_ops(mdesc->smp); |
826 | } | 892 | } |
827 | smp_init_cpus(); | 893 | smp_init_cpus(); |
894 | smp_build_mpidr_hash(); | ||
828 | } | 895 | } |
829 | #endif | 896 | #endif |
830 | 897 | ||
diff --git a/arch/arm/kernel/sleep.S b/arch/arm/kernel/sleep.S index 987dcf33415c..db1536b8b30b 100644 --- a/arch/arm/kernel/sleep.S +++ b/arch/arm/kernel/sleep.S | |||
@@ -7,6 +7,49 @@ | |||
7 | .text | 7 | .text |
8 | 8 | ||
9 | /* | 9 | /* |
10 | * Implementation of MPIDR hash algorithm through shifting | ||
11 | * and OR'ing. | ||
12 | * | ||
13 | * @dst: register containing hash result | ||
14 | * @rs0: register containing affinity level 0 bit shift | ||
15 | * @rs1: register containing affinity level 1 bit shift | ||
16 | * @rs2: register containing affinity level 2 bit shift | ||
17 | * @mpidr: register containing MPIDR value | ||
18 | * @mask: register containing MPIDR mask | ||
19 | * | ||
20 | * Pseudo C-code: | ||
21 | * | ||
22 | *u32 dst; | ||
23 | * | ||
24 | *compute_mpidr_hash(u32 rs0, u32 rs1, u32 rs2, u32 mpidr, u32 mask) { | ||
25 | * u32 aff0, aff1, aff2; | ||
26 | * u32 mpidr_masked = mpidr & mask; | ||
27 | * aff0 = mpidr_masked & 0xff; | ||
28 | * aff1 = mpidr_masked & 0xff00; | ||
29 | * aff2 = mpidr_masked & 0xff0000; | ||
30 | * dst = (aff0 >> rs0 | aff1 >> rs1 | aff2 >> rs2); | ||
31 | *} | ||
32 | * Input registers: rs0, rs1, rs2, mpidr, mask | ||
33 | * Output register: dst | ||
34 | * Note: input and output registers must be disjoint register sets | ||
35 | (eg: a macro instance with mpidr = r1 and dst = r1 is invalid) | ||
36 | */ | ||
37 | .macro compute_mpidr_hash dst, rs0, rs1, rs2, mpidr, mask | ||
38 | and \mpidr, \mpidr, \mask @ mask out MPIDR bits | ||
39 | and \dst, \mpidr, #0xff @ mask=aff0 | ||
40 | ARM( mov \dst, \dst, lsr \rs0 ) @ dst=aff0>>rs0 | ||
41 | THUMB( lsr \dst, \dst, \rs0 ) | ||
42 | and \mask, \mpidr, #0xff00 @ mask = aff1 | ||
43 | ARM( orr \dst, \dst, \mask, lsr \rs1 ) @ dst|=(aff1>>rs1) | ||
44 | THUMB( lsr \mask, \mask, \rs1 ) | ||
45 | THUMB( orr \dst, \dst, \mask ) | ||
46 | and \mask, \mpidr, #0xff0000 @ mask = aff2 | ||
47 | ARM( orr \dst, \dst, \mask, lsr \rs2 ) @ dst|=(aff2>>rs2) | ||
48 | THUMB( lsr \mask, \mask, \rs2 ) | ||
49 | THUMB( orr \dst, \dst, \mask ) | ||
50 | .endm | ||
51 | |||
52 | /* | ||
10 | * Save CPU state for a suspend. This saves the CPU general purpose | 53 | * Save CPU state for a suspend. This saves the CPU general purpose |
11 | * registers, and allocates space on the kernel stack to save the CPU | 54 | * registers, and allocates space on the kernel stack to save the CPU |
12 | * specific registers and some other data for resume. | 55 | * specific registers and some other data for resume. |
@@ -29,12 +72,18 @@ ENTRY(__cpu_suspend) | |||
29 | mov r1, r4 @ size of save block | 72 | mov r1, r4 @ size of save block |
30 | mov r2, r5 @ virtual SP | 73 | mov r2, r5 @ virtual SP |
31 | ldr r3, =sleep_save_sp | 74 | ldr r3, =sleep_save_sp |
32 | #ifdef CONFIG_SMP | 75 | ldr r3, [r3, #SLEEP_SAVE_SP_VIRT] |
33 | ALT_SMP(mrc p15, 0, lr, c0, c0, 5) | 76 | ALT_SMP(mrc p15, 0, r9, c0, c0, 5) |
34 | ALT_UP(mov lr, #0) | 77 | ALT_UP_B(1f) |
35 | and lr, lr, #15 | 78 | ldr r8, =mpidr_hash |
79 | /* | ||
80 | * This ldmia relies on the memory layout of the mpidr_hash | ||
81 | * struct mpidr_hash. | ||
82 | */ | ||
83 | ldmia r8, {r4-r7} @ r4 = mpidr mask (r5,r6,r7) = l[0,1,2] shifts | ||
84 | compute_mpidr_hash lr, r5, r6, r7, r9, r4 | ||
36 | add r3, r3, lr, lsl #2 | 85 | add r3, r3, lr, lsl #2 |
37 | #endif | 86 | 1: |
38 | bl __cpu_suspend_save | 87 | bl __cpu_suspend_save |
39 | adr lr, BSYM(cpu_suspend_abort) | 88 | adr lr, BSYM(cpu_suspend_abort) |
40 | ldmfd sp!, {r0, pc} @ call suspend fn | 89 | ldmfd sp!, {r0, pc} @ call suspend fn |
@@ -81,15 +130,23 @@ ENDPROC(cpu_resume_after_mmu) | |||
81 | .data | 130 | .data |
82 | .align | 131 | .align |
83 | ENTRY(cpu_resume) | 132 | ENTRY(cpu_resume) |
84 | #ifdef CONFIG_SMP | 133 | mov r1, #0 |
85 | adr r0, sleep_save_sp | 134 | ALT_SMP(mrc p15, 0, r0, c0, c0, 5) |
86 | ALT_SMP(mrc p15, 0, r1, c0, c0, 5) | 135 | ALT_UP_B(1f) |
87 | ALT_UP(mov r1, #0) | 136 | adr r2, mpidr_hash_ptr |
88 | and r1, r1, #15 | 137 | ldr r3, [r2] |
89 | ldr r0, [r0, r1, lsl #2] @ stack phys addr | 138 | add r2, r2, r3 @ r2 = struct mpidr_hash phys address |
90 | #else | 139 | /* |
91 | ldr r0, sleep_save_sp @ stack phys addr | 140 | * This ldmia relies on the memory layout of the mpidr_hash |
92 | #endif | 141 | * struct mpidr_hash. |
142 | */ | ||
143 | ldmia r2, { r3-r6 } @ r3 = mpidr mask (r4,r5,r6) = l[0,1,2] shifts | ||
144 | compute_mpidr_hash r1, r4, r5, r6, r0, r3 | ||
145 | 1: | ||
146 | adr r0, _sleep_save_sp | ||
147 | ldr r0, [r0, #SLEEP_SAVE_SP_PHYS] | ||
148 | ldr r0, [r0, r1, lsl #2] | ||
149 | |||
93 | setmode PSR_I_BIT | PSR_F_BIT | SVC_MODE, r1 @ set SVC, irqs off | 150 | setmode PSR_I_BIT | PSR_F_BIT | SVC_MODE, r1 @ set SVC, irqs off |
94 | @ load phys pgd, stack, resume fn | 151 | @ load phys pgd, stack, resume fn |
95 | ARM( ldmia r0!, {r1, sp, pc} ) | 152 | ARM( ldmia r0!, {r1, sp, pc} ) |
@@ -98,7 +155,11 @@ THUMB( mov sp, r2 ) | |||
98 | THUMB( bx r3 ) | 155 | THUMB( bx r3 ) |
99 | ENDPROC(cpu_resume) | 156 | ENDPROC(cpu_resume) |
100 | 157 | ||
101 | sleep_save_sp: | 158 | .align 2 |
102 | .rept CONFIG_NR_CPUS | 159 | mpidr_hash_ptr: |
103 | .long 0 @ preserve stack phys ptr here | 160 | .long mpidr_hash - . @ mpidr_hash struct offset |
104 | .endr | 161 | |
162 | .type sleep_save_sp, #object | ||
163 | ENTRY(sleep_save_sp) | ||
164 | _sleep_save_sp: | ||
165 | .space SLEEP_SAVE_SP_SZ @ struct sleep_save_sp | ||
diff --git a/arch/arm/kernel/suspend.c b/arch/arm/kernel/suspend.c index 38a50676213b..41cf3cbf756d 100644 --- a/arch/arm/kernel/suspend.c +++ b/arch/arm/kernel/suspend.c | |||
@@ -1,9 +1,12 @@ | |||
1 | #include <linux/init.h> | 1 | #include <linux/init.h> |
2 | #include <linux/slab.h> | ||
2 | 3 | ||
4 | #include <asm/cacheflush.h> | ||
3 | #include <asm/idmap.h> | 5 | #include <asm/idmap.h> |
4 | #include <asm/pgalloc.h> | 6 | #include <asm/pgalloc.h> |
5 | #include <asm/pgtable.h> | 7 | #include <asm/pgtable.h> |
6 | #include <asm/memory.h> | 8 | #include <asm/memory.h> |
9 | #include <asm/smp_plat.h> | ||
7 | #include <asm/suspend.h> | 10 | #include <asm/suspend.h> |
8 | #include <asm/tlbflush.h> | 11 | #include <asm/tlbflush.h> |
9 | 12 | ||
@@ -82,3 +85,20 @@ void __cpu_suspend_save(u32 *ptr, u32 ptrsz, u32 sp, u32 *save_ptr) | |||
82 | outer_clean_range(virt_to_phys(save_ptr), | 85 | outer_clean_range(virt_to_phys(save_ptr), |
83 | virt_to_phys(save_ptr) + sizeof(*save_ptr)); | 86 | virt_to_phys(save_ptr) + sizeof(*save_ptr)); |
84 | } | 87 | } |
88 | |||
89 | extern struct sleep_save_sp sleep_save_sp; | ||
90 | |||
91 | static int cpu_suspend_alloc_sp(void) | ||
92 | { | ||
93 | void *ctx_ptr; | ||
94 | /* ctx_ptr is an array of physical addresses */ | ||
95 | ctx_ptr = kcalloc(mpidr_hash_size(), sizeof(u32), GFP_KERNEL); | ||
96 | |||
97 | if (WARN_ON(!ctx_ptr)) | ||
98 | return -ENOMEM; | ||
99 | sleep_save_sp.save_ptr_stash = ctx_ptr; | ||
100 | sleep_save_sp.save_ptr_stash_phys = virt_to_phys(ctx_ptr); | ||
101 | sync_cache_w(&sleep_save_sp); | ||
102 | return 0; | ||
103 | } | ||
104 | early_initcall(cpu_suspend_alloc_sp); | ||