[RFC PATCH 05/17] ARM: kernel: save/restore kernel IF

Frank Hofmann frank.hofmann at tomtom.com
Fri Jul 8 12:12:22 EDT 2011


Hi Lorenzo,

only a few comments at this stage.

The sr_entry.S code is both exclusively .arm (using conditionals and 
long-distance adr, i.e. not Thumb2-clean), and it uses post-armv5 
instructions (like wfi). Same for the other *.S code in the patch series. 
It's non-generic assembly within arch/arch/kernel/, wouldn't one better 
place this into arch/arm/mm/...-v[67].S ?


Then, sr_suspend/sr_resume; these functions are "C-exported" and are 
directly calling cpu_do_suspend/do_resume to pass a supplied buffer; I've 
done that for one iteration of the hibernation patch, yes, but that was a 
bit sneaky and Russell stated then the interface is cpu_suspend/cpu_resume 
not the proc funcs directly. Unless _those_ have been changed they're also 
unsafe to call from C funcs (clobber all regs). Couldn't you simply use 
cpu_suspend/resume directly ?

How much memory do all the pagedirs require that are being kept around ? 
Why does each core need a separate one, what would happen to just use a 
single "identity table" for all ?
I understand you can't use swapper_pg_dir for idle, so a separate one has 
to be allocated, yet the question remains why per-cpu required ?



I'm currently transitioning between jobs; will re-subscribe to arm-kernel 
under a different email address soon, this one is likely to stop working 
in August. Sorry the inconvenience and high-latency responses till then :(

FrankH.


On Thu, 7 Jul 2011, Lorenzo Pieralisi wrote:

> In order to define a common idle interface for the kernel
> to enter low power modes, this patch provides include files
> and code that manages OS calls for low power entry and exit.
>
> In ARM world processor HW is categorized as CPU and Cluster.
>
> Corresponding states defined by this common IF are:
>
> C-state [CPU state]:
>
> 0 - RUN MODE
> 1 - STANDBY
> 2 - DORMANT (not supported by this patch)
> 3 - SHUTDOWN
>
> R-state [CLUSTER state]
>
> 0 - RUN
> 1 - STANDBY (not supported by this patch)
> 2 - L2 RAM retention
> 3 - SHUTDOWN
>
> idle modes are entered through
>
> cpu_enter_idle(cstate, rstate, flags) [sr_entry.S]
>
> which could replace the current processor.idle entry in proc info,
> since it just executes wfi for shallow C-states.
>
> Cluster low-power states are reached if and only if all the CPUs in the cluster
> are in low-power mode.
>
> Only one cluster is supported at present, and the kernel infrastructure
> should be improved to allow multiple clusters to be defined and
> enumerated.
>
> Current page table dir and stack pointers are saved using a per-cpu variable;
> this scheme breaks as soon as clusters are added to the kernel.
>
> The code keeps a cpumask of alive CPUs and manages the state transitions
> accordingly.
>
> Most of the variables needed when the CPU is powered down (MMU off)
> are allocated through a platform hook:
>
> platform_context_pointer(unsigned int size)
>
> that returns memory flat-mapped by this patchset as strongly ordered to
> avoid toying with L2 cleaning when a single CPU enters lowpower.
>
> Fully tested on dual-core A9 cluster.
>
> Signed-off-by: Lorenzo Pieralisi <lorenzo.pieralisi at arm.com>
> ---
> arch/arm/include/asm/sr_platform_api.h |   28 ++++
> arch/arm/kernel/sr_api.c               |  197 +++++++++++++++++++++++++++++
> arch/arm/kernel/sr_entry.S             |  213 ++++++++++++++++++++++++++++++++
> 3 files changed, 438 insertions(+), 0 deletions(-)
> create mode 100644 arch/arm/include/asm/sr_platform_api.h
> create mode 100644 arch/arm/kernel/sr_api.c
> create mode 100644 arch/arm/kernel/sr_entry.S
>
> diff --git a/arch/arm/include/asm/sr_platform_api.h b/arch/arm/include/asm/sr_platform_api.h
> new file mode 100644
> index 0000000..32367be
> --- /dev/null
> +++ b/arch/arm/include/asm/sr_platform_api.h
> @@ -0,0 +1,28 @@
> +/*
> + * Copyright (C) 2008-2011 ARM Limited
> + *
> + * Author(s): Jon Callan, Lorenzo Pieralisi
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + *
> + */
> +
> +#ifndef ASMARM_SR_PLATFORM_API_H
> +#define ASMARM_SR_PLATFORM_API_H
> +
> +#define SR_SAVE_L2	(1 << 31)
> +#define SR_SAVE_SCU	(1 << 30)
> +#define SR_SAVE_ALL	(SR_SAVE_L2 | SR_SAVE_SCU)
> +
> +struct lp_state {
> +	u16 cpu;
> +	u16 cluster;
> +};
> +
> +extern void (*sr_sleep)(void);
> +extern void (*arch_reset_handler(void))(void);
> +extern int cpu_enter_idle(unsigned cstate, unsigned rstate, unsigned flags);
> +extern void *platform_context_pointer(unsigned int);
> +#endif
> diff --git a/arch/arm/kernel/sr_api.c b/arch/arm/kernel/sr_api.c
> new file mode 100644
> index 0000000..4e48f60
> --- /dev/null
> +++ b/arch/arm/kernel/sr_api.c
> @@ -0,0 +1,197 @@
> +/*
> + * Copyright (C) 2008-2011 ARM Limited
> + *
> + * Author(s): Jon Callan, Lorenzo Pieralisi
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +
> +#include <linux/slab.h>
> +#include <linux/errno.h>
> +#include <linux/module.h>
> +#include <linux/pm.h>
> +#include <linux/sched.h>
> +#include <linux/cache.h>
> +#include <linux/cpu.h>
> +
> +#include <asm/cacheflush.h>
> +#include <asm/tlbflush.h>
> +#include <asm/system.h>
> +#include <asm/cpu_pm.h>
> +#include <asm/lb_lock.h>
> +#include <asm/sr_platform_api.h>
> +
> +#include "sr_helpers.h"
> +#include "sr.h"
> +
> +
> +struct ____cacheline_aligned sr_main_table main_table = {
> +	.num_clusters	= SR_NR_CLUSTERS,
> +	.cpu_idle_mask	= { { CPU_BITS_NONE }, },
> +};
> +
> +static int late_init(void);
> +
> +int sr_runtime_init(void)
> +{
> +	int ret;
> +
> +	context_memory_uncached =
> +		platform_context_pointer(CONTEXT_SPACE_UNCACHED);
> +
> +	if (!context_memory_uncached)
> +		return -ENOMEM;
> +
> +	ret = linux_sr_setup_translation_tables();
> +
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = sr_context_init();
> +
> +	return ret;
> +}
> +
> +/* return the warm-boot entry point virtual address */
> +void (*arch_reset_handler(void))(void)
> +{
> +	return (void (*)(void)) arch->reset;
> +}
> +
> +static int late_init(void)
> +{
> +	int rc;
> +	struct sr_cluster *cluster;
> +	int cluster_index, cpu_index = sr_platform_get_cpu_index();
> +
> +	cluster_index = sr_platform_get_cluster_index();
> +	cluster = main_table.cluster_table + cluster_index;
> +	main_table.os_mmu_context[cluster_index][cpu_index] =
> +				current->active_mm->pgd;
> +	cpu_switch_mm(main_table.fw_mmu_context, current->active_mm);
> +	rc = sr_platform_init();
> +	cpu_switch_mm(main_table.os_mmu_context[cluster_index][cpu_index],
> +				current->active_mm);
> +	return rc;
> +}
> +
> +void (*sr_sleep)(void) = default_sleep;
> +
> +void enter_idle(unsigned cstate, unsigned rstate, unsigned flags)
> +{
> +	struct sr_cpu *cpu;
> +	struct sr_cluster *cluster;
> +	cpumask_t *cpuidle_mask;
> +	int cpu_index, cluster_index;
> +
> +	cluster_index = sr_platform_get_cluster_index();
> +	cpu_index = sr_platform_get_cpu_index();
> +	cpuidle_mask = &main_table.cpu_idle_mask[cluster_index];
> +	/*
> +	 * WARNING: cluster support will break if multiple clusters are
> +	 * instantiated within the kernel. The current version works
> +	 * with just one cluster and cpu_index is the hardware processor
> +	 * id in cluster index 0.
> +	 */
> +	main_table.os_mmu_context[cluster_index][cpu_index] =
> +				current->active_mm->pgd;
> +	cpu_switch_mm(main_table.fw_mmu_context, current->active_mm);
> +	local_flush_tlb_all();
> +
> +	cluster = main_table.cluster_table + cluster_index;
> +	cpu = cluster->cpu_table + cpu_index;
> +
> +	get_spinlock(cpu_index, cluster->lock);
> +
> +	__cpu_set(cpu_index, cpuidle_mask);
> +
> +	if (cpumask_weight(cpuidle_mask) == num_online_cpus())
> +		cluster->power_state = rstate;
> +
> +	cluster->cluster_down = (cluster->power_state >= 2);
> +
> +	cpu->power_state = cstate;
> +
> +	cpu_pm_enter();
> +
> +	if (cluster->cluster_down)
> +		cpu_complex_pm_enter();
> +
> +	sr_platform_enter_cstate(cpu_index, cpu, cluster);
> +
> +	sr_save_context(cluster, cpu, flags);
> +
> +	release_spinlock(cpu_index, cluster->lock);
> +
> +	/* Point of no return */
> +	(*sr_sleep)();
> +
> +	/*
> +	 * In case we wanted sr_sleep to return
> +	 * here is code to turn MMU off and go
> +	 * the whole hog on the resume path
> +	 */
> +
> +	cpu_reset((virt_to_phys((void *) arch->reset)));
> +}
> +
> +void exit_idle(struct sr_main_table *mt)
> +{
> +	struct sr_cpu *cpu;
> +	struct sr_cluster *cluster;
> +	int cpu_index, cluster_index;
> +
> +	cpu_index = sr_platform_get_cpu_index();
> +
> +	cluster_index = sr_platform_get_cluster_index();
> +
> +	cluster = mt->cluster_table + cluster_index;
> +	cpu = cluster->cpu_table + cpu_index;
> +
> +	PA(get_spinlock)(cpu_index, cluster->lock);
> +
> +	PA(sr_restore_context)(cluster, cpu);
> +
> +	sr_platform_leave_cstate(cpu_index, cpu, cluster);
> +
> +	if (cluster->cluster_down) {
> +		cpu_complex_pm_exit();
> +		cluster->cluster_down = 0;
> +	}
> +
> +	cpu_pm_exit();
> +
> +	cpu_clear(cpu_index, main_table.cpu_idle_mask[cluster_index]);
> +
> +	cpu->power_state = 0;
> +	cluster->power_state = 0;
> +
> +	release_spinlock(cpu_index, cluster->lock);
> +	cpu_switch_mm(main_table.os_mmu_context[cluster_index][cpu_index],
> +					current->active_mm);
> +	local_flush_tlb_all();
> +}
> +
> +
> +int sr_init(void)
> +{
> +	if (lookup_arch()) {
> +		printk(KERN_EMERG "SR INIT: Undetected architecture id\n");
> +		BUG();
> +	}
> +
> +	if (sr_runtime_init()) {
> +		printk(KERN_EMERG "SR INIT: runtime init error\n");
> +		BUG();
> +	}
> +
> +	if (late_init()) {
> +		printk(KERN_EMERG "SR INIT: late init error\n");
> +		BUG();
> +	}
> +
> +	return 0;
> +}
> +arch_initcall(sr_init);
> diff --git a/arch/arm/kernel/sr_entry.S b/arch/arm/kernel/sr_entry.S
> new file mode 100644
> index 0000000..4fa9bef
> --- /dev/null
> +++ b/arch/arm/kernel/sr_entry.S
> @@ -0,0 +1,213 @@
> +/*
> + * Copyright (c) 2008-2011 ARM Ltd
> + *
> + * Author(s):  Jon Callan, Lorenzo Pieralisi
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + *
> + */
> +
> +#include <linux/linkage.h>
> +#include <generated/asm-offsets.h>
> +#include <asm/thread_info.h>
> +#include <asm/memory.h>
> +#include <asm/ptrace.h>
> +#include <asm/glue-proc.h>
> +#include <asm/assembler.h>
> +#include <asm-generic/errno-base.h>
> +#include <mach/entry-macro.S>
> +
> +	.text
> +
> +ENTRY(default_sleep)
> +	b out		@ BTAC allocates branch and enters loop mode
> +idle:			@ power down is entered with GIC CPU IF still on which
> +	dsb		@ might get wfi instruction to complete before the
> +	wfi		@ CPU is shut down -- infinite loop
> +out:
> +    	b idle
> +ENDPROC(default_sleep)
> +
> +
> +ENTRY(sr_suspend)
> +	b cpu_do_suspend
> +ENDPROC(sr_suspend)
> +
> +ENTRY(sr_resume)
> +	add	lr, lr, #(PAGE_OFFSET - PLAT_PHYS_OFFSET)
> +	stmfd	sp!, {r4 - r11, lr}
> +	ldr	lr, =mmu_on
> +	b 	cpu_do_resume
> +mmu_on:
> +	ldmfd	sp!, {r4 - r11, pc}
> +ENDPROC(sr_resume)
> +
> +/*
> + * This code is in the .data section to retrieve stack pointers stored in
> + * platform_cpu_stacks and platform_cpu_nc_stacks with a pc relative load.
> + * It cannot live in .text since that section can be treated as read-only
> + * and would break the code, which requires stack pointers to be saved on
> + * idle entry.
> + */
> +	.data
> +	.align
> +	.global idle_save_context
> +	.global idle_restore_context
> +	.global idle_mt
> +	.global platform_cpu_stacks
> +	.global platform_cpu_nc_stacks
> +
> +/*
> + * idle entry point
> + * Must be called with IRQ disabled
> + * Idle states are differentiated between CPU and Cluster states
> + *
> + * r0 = cstate defines the CPU power state
> + * r1 = rstate defines the Cluster power state
> + * r2 = flags define what has to be saved
> + *
> + * C-STATE mapping
> + * 0 - run
> + * 1 - wfi (aka standby)
> + * 2 - dormant (not supported)
> + * 3 - shutdown
> + *
> + * R-STATE mapping
> + * 0 - run
> + * 1 - not supported
> + * 2 - L2 retention
> + * 3 - Off mode (every platform defines it, e.g. GIC power domain)
> + *
> + * Cluster low-power states might be hit if and only if all the CPUs making up
> + * the clusters are in some deep C-STATE
> + *
> + */
> +
> +ENTRY(cpu_enter_idle)
> +	cmp	r0, #2		@ this function can replace the idle function
> +	wfilt			@ in the processor struct. If targeted power
> +	movlt	r0, #0		@ states are shallow ones it just executes wfi
> +	movlt	pc, lr		@ and returns
> +	cmp	r0, #3
> +	cmpls	r1, #3
> +	mvnhi	r0, #EINVAL
> +	movhi	pc, lr
> +	stmfd	sp!, {r4 - r12, lr}
> +	stmfd	sp, {r0, r1}
> +#ifdef CONFIG_SMP
> +	adr	r0, platform_cpu_stacks
> +	ALT_SMP(mrc p15, 0, r1, c0, c0, 5)
> +	ALT_UP(mov r1, #0)
> +	and	r1, r1, #15
> +	str	sp, [r0, r1, lsl #2]	@ stack phys addr - save it for resume
> +#else
> +	str	sp, platform_cpu_stacks
> +#endif
> +	sub	sp, sp, #8
> +	ldmfd	sp!,{r0, r1}
> +	bl 	enter_idle
> +	mov	r0, #0
> +	ldmfd   sp!, {r4 - r12, pc}
> +ENDPROC(cpu_enter_idle)
> +
> +/*
> + * This hook, though not strictly necessary, provides an entry point where, if
> + * needed, stack pointers can be switched in case it is needed to improve L2
> + * retention management (uncached stack).
> + */
> +ENTRY(sr_save_context)
> +	adr	r12, idle_save_context
> +	ldr	r12, [r12]
> +	bx	r12
> +ENDPROC(sr_save_context)
> +
> +ENTRY(sr_reset_entry_point)
> +	@ This is the entry point from the platform warm start code
> +	@ It runs with MMU off straight from reset
> +	setmode	PSR_I_BIT | PSR_F_BIT | SVC_MODE, r0 @ set SVC, irqs off
> +#ifdef CONFIG_SMP
> +	adr	r0, platform_cpu_nc_stacks
> +	ALT_SMP(mrc p15, 0, r1, c0, c0, 5)
> +	ALT_UP(mov r1, #0)
> +	and	r1, r1, #15
> +	ldr	r0, [r0, r1, lsl #2]		@ stack phys addr
> +#else
> +	ldr	r0, platform_cpu_nc_stacks	@ stack phys addr
> +#endif
> +	mov	sp, r0
> +	adr	r0, idle_mt 	@ get phys address of main table and pass it on
> +	ldr	r0, [r0]
> +	ldr	lr, =return_from_idle
> +	adr	r1, resume
> +	ldr	r1, [r1]
> +	bx	r1
> +return_from_idle:
> +	@ return to enter_idle caller, with success
> +	mov	r0, #0
> +	ldmfd	sp!, {r4 - r12, pc}	@ return from idle - registers saved in
> +ENDPROC(sr_reset_entry_point)		@ cpu_enter_idle() are still there
> +
> +
> +ENTRY(sr_restore_context)
> +	add	lr, lr, #(PAGE_OFFSET - PLAT_PHYS_OFFSET)
> +	stmfd	sp!, {r4, lr}
> +	adr	r12, idle_restore_context
> +	ldr	r12, [r12]
> +	ldr	lr, =switch_stack
> +	bx	r12
> +switch_stack:
> +	@ CPU context restored, time to switch to Linux stack and pop out
> +#ifdef CONFIG_SMP
> +	adr	r0, platform_cpu_stacks
> +	ALT_SMP(mrc p15, 0, r1, c0, c0, 5)
> +	ALT_UP(mov r1, #0)
> +	and	r1, r1, #15
> +	ldr	r0, [r0, r1, lsl #2]	@ top stack addr
> +#else
> +	ldr	r0, platform_cpu_stacks	@ top stack addr
> +#endif
> +	mov	r3, r0
> +#ifdef CONFIG_SMP
> +	adr	r0, platform_cpu_nc_stacks
> +	ALT_SMP(mrc p15, 0, r1, c0, c0, 5)
> +	ALT_UP(mov r1, #0)
> +	and	r1, r1, #15
> +	ldr	r0, [r0, r1, lsl #2]		@ non-cacheable stack phys addr
> +#else
> +	ldr	r0, platform_cpu_nc_stacks	@ non-cacheable stack phys addr
> +#endif
> +	sub	r2, r0, sp
> +	sub	r0, r3, r2
> +	mov	r1, sp
> +	mov	r4, r0
> +	bl	memcpy		@ copy stack used in resume to current stack
> +	mov	sp, r4
> +	bl 	cpu_init	@ init banked registers
> +	ldmfd	sp!, {r4, pc}
> +ENDPROC(sr_restore_context)
> +
> +idle_save_context:
> +	.long 0
> +idle_restore_context:
> +	.long 0
> +
> +idle_mt:
> +	.long main_table - PAGE_OFFSET + PLAT_PHYS_OFFSET
> +
> +resume:
> +	.long exit_idle - PAGE_OFFSET + PLAT_PHYS_OFFSET
> +
> +platform_cpu_stacks:
> +	.rept	CONFIG_NR_CPUS
> +	.long	0				@ preserve stack phys ptr here
> +	.endr
> +
> +platform_cpu_nc_stacks:
> +	.rept	CONFIG_NR_CPUS
> +	.long	0				@ preserve uncached
> +						@ stack phys ptr here
> +	.endr
> +
> +	.end
> -- 
> 1.7.4.4
>
>
>



More information about the linux-arm-kernel mailing list