|  | /* SPDX-License-Identifier: GPL-2.0-only */ | 
|  | /* | 
|  | * arch/arm/common/mcpm_head.S -- kernel entry point for multi-cluster PM | 
|  | * | 
|  | * Created by:  Nicolas Pitre, March 2012 | 
|  | * Copyright:   (C) 2012-2013  Linaro Limited | 
|  | * | 
|  | * Refer to Documentation/arm/cluster-pm-race-avoidance.rst | 
|  | * for details of the synchronisation algorithms used here. | 
|  | */ | 
|  |  | 
|  | #include <linux/linkage.h> | 
|  | #include <asm/mcpm.h> | 
|  | #include <asm/assembler.h> | 
|  |  | 
|  | #include "vlock.h" | 
|  |  | 
|  | .arch armv7-a | 
|  |  | 
|  | .if MCPM_SYNC_CLUSTER_CPUS | 
|  | .error "cpus must be the first member of struct mcpm_sync_struct" | 
|  | .endif | 
|  |  | 
|  | .macro	pr_dbg	string | 
|  | #if defined(CONFIG_DEBUG_LL) && defined(DEBUG) | 
|  | b	1901f | 
|  | 1902:	.asciz	"CPU" | 
|  | 1903:	.asciz	" cluster" | 
|  | 1904:	.asciz	": \string" | 
|  | .align | 
|  | 1901:	adr	r0, 1902b | 
|  | bl	printascii | 
|  | mov	r0, r9 | 
|  | bl	printhex2 | 
|  | adr	r0, 1903b | 
|  | bl	printascii | 
|  | mov	r0, r10 | 
|  | bl	printhex2 | 
|  | adr	r0, 1904b | 
|  | bl	printascii | 
|  | #endif | 
|  | .endm | 
|  |  | 
|  | .arm | 
|  | .align | 
|  |  | 
|  | ENTRY(mcpm_entry_point) | 
|  |  | 
|  | ARM_BE8(setend        be) | 
|  | THUMB(	badr	r12, 1f		) | 
|  | THUMB(	bx	r12		) | 
|  | THUMB(	.thumb			) | 
|  | 1: | 
|  | mrc	p15, 0, r0, c0, c0, 5		@ MPIDR | 
|  | ubfx	r9, r0, #0, #8			@ r9 = cpu | 
|  | ubfx	r10, r0, #8, #8			@ r10 = cluster | 
|  | mov	r3, #MAX_CPUS_PER_CLUSTER | 
|  | mla	r4, r3, r10, r9			@ r4 = canonical CPU index | 
|  | cmp	r4, #(MAX_CPUS_PER_CLUSTER * MAX_NR_CLUSTERS) | 
|  | blo	2f | 
|  |  | 
|  | /* We didn't expect this CPU.  Try to cheaply make it quiet. */ | 
|  | 1:	wfi | 
|  | wfe | 
|  | b	1b | 
|  |  | 
|  | 2:	pr_dbg	"kernel mcpm_entry_point\n" | 
|  |  | 
|  | /* | 
|  | * MMU is off so we need to get to various variables in a | 
|  | * position independent way. | 
|  | */ | 
|  | adr	r5, 3f | 
|  | ldmia	r5, {r0, r6, r7, r8, r11} | 
|  | add	r0, r5, r0			@ r0 = mcpm_entry_early_pokes | 
|  | add	r6, r5, r6			@ r6 = mcpm_entry_vectors | 
|  | ldr	r7, [r5, r7]			@ r7 = mcpm_power_up_setup_phys | 
|  | add	r8, r5, r8			@ r8 = mcpm_sync | 
|  | add	r11, r5, r11			@ r11 = first_man_locks | 
|  |  | 
|  | @ Perform an early poke, if any | 
|  | add	r0, r0, r4, lsl #3 | 
|  | ldmia	r0, {r0, r1} | 
|  | teq	r0, #0 | 
|  | strne	r1, [r0] | 
|  |  | 
|  | mov	r0, #MCPM_SYNC_CLUSTER_SIZE | 
|  | mla	r8, r0, r10, r8			@ r8 = sync cluster base | 
|  |  | 
|  | @ Signal that this CPU is coming UP: | 
|  | mov	r0, #CPU_COMING_UP | 
|  | mov	r5, #MCPM_SYNC_CPU_SIZE | 
|  | mla	r5, r9, r5, r8			@ r5 = sync cpu address | 
|  | strb	r0, [r5] | 
|  |  | 
|  | @ At this point, the cluster cannot unexpectedly enter the GOING_DOWN | 
|  | @ state, because there is at least one active CPU (this CPU). | 
|  |  | 
|  | mov	r0, #VLOCK_SIZE | 
|  | mla	r11, r0, r10, r11		@ r11 = cluster first man lock | 
|  | mov	r0, r11 | 
|  | mov	r1, r9				@ cpu | 
|  | bl	vlock_trylock			@ implies DMB | 
|  |  | 
|  | cmp	r0, #0				@ failed to get the lock? | 
|  | bne	mcpm_setup_wait		@ wait for cluster setup if so | 
|  |  | 
|  | ldrb	r0, [r8, #MCPM_SYNC_CLUSTER_CLUSTER] | 
|  | cmp	r0, #CLUSTER_UP			@ cluster already up? | 
|  | bne	mcpm_setup			@ if not, set up the cluster | 
|  |  | 
|  | @ Otherwise, release the first man lock and skip setup: | 
|  | mov	r0, r11 | 
|  | bl	vlock_unlock | 
|  | b	mcpm_setup_complete | 
|  |  | 
|  | mcpm_setup: | 
|  | @ Control dependency implies strb not observable before previous ldrb. | 
|  |  | 
|  | @ Signal that the cluster is being brought up: | 
|  | mov	r0, #INBOUND_COMING_UP | 
|  | strb	r0, [r8, #MCPM_SYNC_CLUSTER_INBOUND] | 
|  | dmb | 
|  |  | 
|  | @ Any CPU trying to take the cluster into CLUSTER_GOING_DOWN from this | 
|  | @ point onwards will observe INBOUND_COMING_UP and abort. | 
|  |  | 
|  | @ Wait for any previously-pending cluster teardown operations to abort | 
|  | @ or complete: | 
|  | mcpm_teardown_wait: | 
|  | ldrb	r0, [r8, #MCPM_SYNC_CLUSTER_CLUSTER] | 
|  | cmp	r0, #CLUSTER_GOING_DOWN | 
|  | bne	first_man_setup | 
|  | wfe | 
|  | b	mcpm_teardown_wait | 
|  |  | 
|  | first_man_setup: | 
|  | dmb | 
|  |  | 
|  | @ If the outbound gave up before teardown started, skip cluster setup: | 
|  |  | 
|  | cmp	r0, #CLUSTER_UP | 
|  | beq	mcpm_setup_leave | 
|  |  | 
|  | @ power_up_setup is now responsible for setting up the cluster: | 
|  |  | 
|  | cmp	r7, #0 | 
|  | mov	r0, #1		@ second (cluster) affinity level | 
|  | blxne	r7		@ Call power_up_setup if defined | 
|  | dmb | 
|  |  | 
|  | mov	r0, #CLUSTER_UP | 
|  | strb	r0, [r8, #MCPM_SYNC_CLUSTER_CLUSTER] | 
|  | dmb | 
|  |  | 
|  | mcpm_setup_leave: | 
|  | @ Leave the cluster setup critical section: | 
|  |  | 
|  | mov	r0, #INBOUND_NOT_COMING_UP | 
|  | strb	r0, [r8, #MCPM_SYNC_CLUSTER_INBOUND] | 
|  | dsb	st | 
|  | sev | 
|  |  | 
|  | mov	r0, r11 | 
|  | bl	vlock_unlock	@ implies DMB | 
|  | b	mcpm_setup_complete | 
|  |  | 
|  | @ In the contended case, non-first men wait here for cluster setup | 
|  | @ to complete: | 
|  | mcpm_setup_wait: | 
|  | ldrb	r0, [r8, #MCPM_SYNC_CLUSTER_CLUSTER] | 
|  | cmp	r0, #CLUSTER_UP | 
|  | wfene | 
|  | bne	mcpm_setup_wait | 
|  | dmb | 
|  |  | 
|  | mcpm_setup_complete: | 
|  | @ If a platform-specific CPU setup hook is needed, it is | 
|  | @ called from here. | 
|  |  | 
|  | cmp	r7, #0 | 
|  | mov	r0, #0		@ first (CPU) affinity level | 
|  | blxne	r7		@ Call power_up_setup if defined | 
|  | dmb | 
|  |  | 
|  | @ Mark the CPU as up: | 
|  |  | 
|  | mov	r0, #CPU_UP | 
|  | strb	r0, [r5] | 
|  |  | 
|  | @ Observability order of CPU_UP and opening of the gate does not matter. | 
|  |  | 
|  | mcpm_entry_gated: | 
|  | ldr	r5, [r6, r4, lsl #2]		@ r5 = CPU entry vector | 
|  | cmp	r5, #0 | 
|  | wfeeq | 
|  | beq	mcpm_entry_gated | 
|  | dmb | 
|  |  | 
|  | pr_dbg	"released\n" | 
|  | bx	r5 | 
|  |  | 
|  | .align	2 | 
|  |  | 
|  | 3:	.word	mcpm_entry_early_pokes - . | 
|  | .word	mcpm_entry_vectors - 3b | 
|  | .word	mcpm_power_up_setup_phys - 3b | 
|  | .word	mcpm_sync - 3b | 
|  | .word	first_man_locks - 3b | 
|  |  | 
|  | ENDPROC(mcpm_entry_point) | 
|  |  | 
|  | .bss | 
|  |  | 
|  | .align	CACHE_WRITEBACK_ORDER | 
|  | .type	first_man_locks, #object | 
|  | first_man_locks: | 
|  | .space	VLOCK_SIZE * MAX_NR_CLUSTERS | 
|  | .align	CACHE_WRITEBACK_ORDER | 
|  |  | 
|  | .type	mcpm_entry_vectors, #object | 
|  | ENTRY(mcpm_entry_vectors) | 
|  | .space	4 * MAX_NR_CLUSTERS * MAX_CPUS_PER_CLUSTER | 
|  |  | 
|  | .type	mcpm_entry_early_pokes, #object | 
|  | ENTRY(mcpm_entry_early_pokes) | 
|  | .space	8 * MAX_NR_CLUSTERS * MAX_CPUS_PER_CLUSTER | 
|  |  | 
|  | .type	mcpm_power_up_setup_phys, #object | 
|  | ENTRY(mcpm_power_up_setup_phys) | 
|  | .space  4		@ set by mcpm_sync_init() |