forked from mirrors/linux
		
	 c55b51a06b
			
		
	
	
		c55b51a06b
		
	
	
	
	
		
			
			In some cases it may be useful to specify an exit latency limit for the idle state to be used during CPU idle time injection. Instead of duplicating the information in struct cpuidle_device or propagating the latency limit in the call stack, replace the use_deepest_state field with forced_latency_limit_ns to represent that limit, so that the deepest idle state with exit latency within that limit is forced (i.e. no governors) when it is set. A zero exit latency limit for forced idle means to use governors in the usual way (analogous to use_deepest_state equal to "false" before this change). Additionally, add play_idle_precise() taking two arguments, the duration of forced idle and the idle state exit latency limit, both in nanoseconds, and redefine play_idle() as a wrapper around that new function. This change is preparatory, no functional impact is expected. Suggested-by: Rafael J. Wysocki <rafael@kernel.org> Signed-off-by: Daniel Lezcano <daniel.lezcano@linaro.org> [ rjw: Subject, changelog, cpuidle_use_deepest_state() kerneldoc, whitespace ] Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
		
			
				
	
	
		
			313 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			313 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * cpuidle.h - a generic framework for CPU idle power management
 | |
|  *
 | |
|  * (C) 2007 Venkatesh Pallipadi <venkatesh.pallipadi@intel.com>
 | |
|  *          Shaohua Li <shaohua.li@intel.com>
 | |
|  *          Adam Belay <abelay@novell.com>
 | |
|  *
 | |
|  * This code is licenced under the GPL.
 | |
|  */
 | |
| 
 | |
| #ifndef _LINUX_CPUIDLE_H
 | |
| #define _LINUX_CPUIDLE_H
 | |
| 
 | |
| #include <linux/percpu.h>
 | |
| #include <linux/list.h>
 | |
| #include <linux/hrtimer.h>
 | |
| 
 | |
| #define CPUIDLE_STATE_MAX	10
 | |
| #define CPUIDLE_NAME_LEN	16
 | |
| #define CPUIDLE_DESC_LEN	32
 | |
| 
 | |
| struct module;
 | |
| 
 | |
| struct cpuidle_device;
 | |
| struct cpuidle_driver;
 | |
| 
 | |
| 
 | |
| /****************************
 | |
|  * CPUIDLE DEVICE INTERFACE *
 | |
|  ****************************/
 | |
| 
 | |
| #define CPUIDLE_STATE_DISABLED_BY_USER		BIT(0)
 | |
| #define CPUIDLE_STATE_DISABLED_BY_DRIVER	BIT(1)
 | |
| 
 | |
| struct cpuidle_state_usage {
 | |
| 	unsigned long long	disable;
 | |
| 	unsigned long long	usage;
 | |
| 	u64			time_ns;
 | |
| 	unsigned long long	above; /* Number of times it's been too deep */
 | |
| 	unsigned long long	below; /* Number of times it's been too shallow */
 | |
| #ifdef CONFIG_SUSPEND
 | |
| 	unsigned long long	s2idle_usage;
 | |
| 	unsigned long long	s2idle_time; /* in US */
 | |
| #endif
 | |
| };
 | |
| 
 | |
| struct cpuidle_state {
 | |
| 	char		name[CPUIDLE_NAME_LEN];
 | |
| 	char		desc[CPUIDLE_DESC_LEN];
 | |
| 
 | |
| 	u64		exit_latency_ns;
 | |
| 	u64		target_residency_ns;
 | |
| 	unsigned int	flags;
 | |
| 	unsigned int	exit_latency; /* in US */
 | |
| 	int		power_usage; /* in mW */
 | |
| 	unsigned int	target_residency; /* in US */
 | |
| 	bool		disabled; /* disabled on all CPUs */
 | |
| 
 | |
| 	int (*enter)	(struct cpuidle_device *dev,
 | |
| 			struct cpuidle_driver *drv,
 | |
| 			int index);
 | |
| 
 | |
| 	int (*enter_dead) (struct cpuidle_device *dev, int index);
 | |
| 
 | |
| 	/*
 | |
| 	 * CPUs execute ->enter_s2idle with the local tick or entire timekeeping
 | |
| 	 * suspended, so it must not re-enable interrupts at any point (even
 | |
| 	 * temporarily) or attempt to change states of clock event devices.
 | |
| 	 */
 | |
| 	void (*enter_s2idle) (struct cpuidle_device *dev,
 | |
| 			      struct cpuidle_driver *drv,
 | |
| 			      int index);
 | |
| };
 | |
| 
 | |
| /* Idle State Flags */
 | |
| #define CPUIDLE_FLAG_NONE       (0x00)
 | |
| #define CPUIDLE_FLAG_POLLING	BIT(0) /* polling state */
 | |
| #define CPUIDLE_FLAG_COUPLED	BIT(1) /* state applies to multiple cpus */
 | |
| #define CPUIDLE_FLAG_TIMER_STOP BIT(2) /* timer is stopped on this state */
 | |
| 
 | |
| struct cpuidle_device_kobj;
 | |
| struct cpuidle_state_kobj;
 | |
| struct cpuidle_driver_kobj;
 | |
| 
 | |
| struct cpuidle_device {
 | |
| 	unsigned int		registered:1;
 | |
| 	unsigned int		enabled:1;
 | |
| 	unsigned int		poll_time_limit:1;
 | |
| 	unsigned int		cpu;
 | |
| 	ktime_t			next_hrtimer;
 | |
| 
 | |
| 	int			last_state_idx;
 | |
| 	u64			last_residency_ns;
 | |
| 	u64			poll_limit_ns;
 | |
| 	u64			forced_idle_latency_limit_ns;
 | |
| 	struct cpuidle_state_usage	states_usage[CPUIDLE_STATE_MAX];
 | |
| 	struct cpuidle_state_kobj *kobjs[CPUIDLE_STATE_MAX];
 | |
| 	struct cpuidle_driver_kobj *kobj_driver;
 | |
| 	struct cpuidle_device_kobj *kobj_dev;
 | |
| 	struct list_head 	device_list;
 | |
| 
 | |
| #ifdef CONFIG_ARCH_NEEDS_CPU_IDLE_COUPLED
 | |
| 	cpumask_t		coupled_cpus;
 | |
| 	struct cpuidle_coupled	*coupled;
 | |
| #endif
 | |
| };
 | |
| 
 | |
| DECLARE_PER_CPU(struct cpuidle_device *, cpuidle_devices);
 | |
| DECLARE_PER_CPU(struct cpuidle_device, cpuidle_dev);
 | |
| 
 | |
| /****************************
 | |
|  * CPUIDLE DRIVER INTERFACE *
 | |
|  ****************************/
 | |
| 
 | |
| struct cpuidle_driver {
 | |
| 	const char		*name;
 | |
| 	struct module 		*owner;
 | |
| 	int                     refcnt;
 | |
| 
 | |
|         /* used by the cpuidle framework to setup the broadcast timer */
 | |
| 	unsigned int            bctimer:1;
 | |
| 	/* states array must be ordered in decreasing power consumption */
 | |
| 	struct cpuidle_state	states[CPUIDLE_STATE_MAX];
 | |
| 	int			state_count;
 | |
| 	int			safe_state_index;
 | |
| 
 | |
| 	/* the driver handles the cpus in cpumask */
 | |
| 	struct cpumask		*cpumask;
 | |
| 
 | |
| 	/* preferred governor to switch at register time */
 | |
| 	const char		*governor;
 | |
| };
 | |
| 
 | |
| #ifdef CONFIG_CPU_IDLE
 | |
| extern void disable_cpuidle(void);
 | |
| extern bool cpuidle_not_available(struct cpuidle_driver *drv,
 | |
| 				  struct cpuidle_device *dev);
 | |
| 
 | |
| extern int cpuidle_select(struct cpuidle_driver *drv,
 | |
| 			  struct cpuidle_device *dev,
 | |
| 			  bool *stop_tick);
 | |
| extern int cpuidle_enter(struct cpuidle_driver *drv,
 | |
| 			 struct cpuidle_device *dev, int index);
 | |
| extern void cpuidle_reflect(struct cpuidle_device *dev, int index);
 | |
| extern u64 cpuidle_poll_time(struct cpuidle_driver *drv,
 | |
| 			     struct cpuidle_device *dev);
 | |
| 
 | |
| extern int cpuidle_register_driver(struct cpuidle_driver *drv);
 | |
| extern struct cpuidle_driver *cpuidle_get_driver(void);
 | |
| extern struct cpuidle_driver *cpuidle_driver_ref(void);
 | |
| extern void cpuidle_driver_unref(void);
 | |
| extern void cpuidle_driver_state_disabled(struct cpuidle_driver *drv, int idx,
 | |
| 					bool disable);
 | |
| extern void cpuidle_unregister_driver(struct cpuidle_driver *drv);
 | |
| extern int cpuidle_register_device(struct cpuidle_device *dev);
 | |
| extern void cpuidle_unregister_device(struct cpuidle_device *dev);
 | |
| extern int cpuidle_register(struct cpuidle_driver *drv,
 | |
| 			    const struct cpumask *const coupled_cpus);
 | |
| extern void cpuidle_unregister(struct cpuidle_driver *drv);
 | |
| extern void cpuidle_pause_and_lock(void);
 | |
| extern void cpuidle_resume_and_unlock(void);
 | |
| extern void cpuidle_pause(void);
 | |
| extern void cpuidle_resume(void);
 | |
| extern int cpuidle_enable_device(struct cpuidle_device *dev);
 | |
| extern void cpuidle_disable_device(struct cpuidle_device *dev);
 | |
| extern int cpuidle_play_dead(void);
 | |
| 
 | |
| extern struct cpuidle_driver *cpuidle_get_cpu_driver(struct cpuidle_device *dev);
 | |
| static inline struct cpuidle_device *cpuidle_get_device(void)
 | |
| {return __this_cpu_read(cpuidle_devices); }
 | |
| #else
 | |
| static inline void disable_cpuidle(void) { }
 | |
| static inline bool cpuidle_not_available(struct cpuidle_driver *drv,
 | |
| 					 struct cpuidle_device *dev)
 | |
| {return true; }
 | |
| static inline int cpuidle_select(struct cpuidle_driver *drv,
 | |
| 				 struct cpuidle_device *dev, bool *stop_tick)
 | |
| {return -ENODEV; }
 | |
| static inline int cpuidle_enter(struct cpuidle_driver *drv,
 | |
| 				struct cpuidle_device *dev, int index)
 | |
| {return -ENODEV; }
 | |
| static inline void cpuidle_reflect(struct cpuidle_device *dev, int index) { }
 | |
| static inline u64 cpuidle_poll_time(struct cpuidle_driver *drv,
 | |
| 			     struct cpuidle_device *dev)
 | |
| {return 0; }
 | |
| static inline int cpuidle_register_driver(struct cpuidle_driver *drv)
 | |
| {return -ENODEV; }
 | |
| static inline struct cpuidle_driver *cpuidle_get_driver(void) {return NULL; }
 | |
| static inline struct cpuidle_driver *cpuidle_driver_ref(void) {return NULL; }
 | |
| static inline void cpuidle_driver_unref(void) {}
 | |
| static inline void cpuidle_driver_state_disabled(struct cpuidle_driver *drv,
 | |
| 					       int idx, bool disable) { }
 | |
| static inline void cpuidle_unregister_driver(struct cpuidle_driver *drv) { }
 | |
| static inline int cpuidle_register_device(struct cpuidle_device *dev)
 | |
| {return -ENODEV; }
 | |
| static inline void cpuidle_unregister_device(struct cpuidle_device *dev) { }
 | |
| static inline int cpuidle_register(struct cpuidle_driver *drv,
 | |
| 				   const struct cpumask *const coupled_cpus)
 | |
| {return -ENODEV; }
 | |
| static inline void cpuidle_unregister(struct cpuidle_driver *drv) { }
 | |
| static inline void cpuidle_pause_and_lock(void) { }
 | |
| static inline void cpuidle_resume_and_unlock(void) { }
 | |
| static inline void cpuidle_pause(void) { }
 | |
| static inline void cpuidle_resume(void) { }
 | |
| static inline int cpuidle_enable_device(struct cpuidle_device *dev)
 | |
| {return -ENODEV; }
 | |
| static inline void cpuidle_disable_device(struct cpuidle_device *dev) { }
 | |
| static inline int cpuidle_play_dead(void) {return -ENODEV; }
 | |
| static inline struct cpuidle_driver *cpuidle_get_cpu_driver(
 | |
| 	struct cpuidle_device *dev) {return NULL; }
 | |
| static inline struct cpuidle_device *cpuidle_get_device(void) {return NULL; }
 | |
| #endif
 | |
| 
 | |
| #ifdef CONFIG_CPU_IDLE
 | |
| extern int cpuidle_find_deepest_state(struct cpuidle_driver *drv,
 | |
| 				      struct cpuidle_device *dev);
 | |
| extern int cpuidle_enter_s2idle(struct cpuidle_driver *drv,
 | |
| 				struct cpuidle_device *dev);
 | |
| extern void cpuidle_use_deepest_state(u64 latency_limit_ns);
 | |
| #else
 | |
| static inline int cpuidle_find_deepest_state(struct cpuidle_driver *drv,
 | |
| 					     struct cpuidle_device *dev)
 | |
| {return -ENODEV; }
 | |
| static inline int cpuidle_enter_s2idle(struct cpuidle_driver *drv,
 | |
| 				       struct cpuidle_device *dev)
 | |
| {return -ENODEV; }
 | |
| static inline void cpuidle_use_deepest_state(u64 latency_limit_ns)
 | |
| {
 | |
| }
 | |
| #endif
 | |
| 
 | |
| /* kernel/sched/idle.c */
 | |
| extern void sched_idle_set_state(struct cpuidle_state *idle_state);
 | |
| extern void default_idle_call(void);
 | |
| 
 | |
| #ifdef CONFIG_ARCH_NEEDS_CPU_IDLE_COUPLED
 | |
| void cpuidle_coupled_parallel_barrier(struct cpuidle_device *dev, atomic_t *a);
 | |
| #else
 | |
| static inline void cpuidle_coupled_parallel_barrier(struct cpuidle_device *dev, atomic_t *a)
 | |
| {
 | |
| }
 | |
| #endif
 | |
| 
 | |
| #if defined(CONFIG_CPU_IDLE) && defined(CONFIG_ARCH_HAS_CPU_RELAX)
 | |
| void cpuidle_poll_state_init(struct cpuidle_driver *drv);
 | |
| #else
 | |
| static inline void cpuidle_poll_state_init(struct cpuidle_driver *drv) {}
 | |
| #endif
 | |
| 
 | |
| /******************************
 | |
|  * CPUIDLE GOVERNOR INTERFACE *
 | |
|  ******************************/
 | |
| 
 | |
| struct cpuidle_governor {
 | |
| 	char			name[CPUIDLE_NAME_LEN];
 | |
| 	struct list_head 	governor_list;
 | |
| 	unsigned int		rating;
 | |
| 
 | |
| 	int  (*enable)		(struct cpuidle_driver *drv,
 | |
| 					struct cpuidle_device *dev);
 | |
| 	void (*disable)		(struct cpuidle_driver *drv,
 | |
| 					struct cpuidle_device *dev);
 | |
| 
 | |
| 	int  (*select)		(struct cpuidle_driver *drv,
 | |
| 					struct cpuidle_device *dev,
 | |
| 					bool *stop_tick);
 | |
| 	void (*reflect)		(struct cpuidle_device *dev, int index);
 | |
| };
 | |
| 
 | |
| #ifdef CONFIG_CPU_IDLE
 | |
| extern int cpuidle_register_governor(struct cpuidle_governor *gov);
 | |
| extern s64 cpuidle_governor_latency_req(unsigned int cpu);
 | |
| #else
 | |
| static inline int cpuidle_register_governor(struct cpuidle_governor *gov)
 | |
| {return 0;}
 | |
| #endif
 | |
| 
 | |
| #define __CPU_PM_CPU_IDLE_ENTER(low_level_idle_enter,			\
 | |
| 				idx,					\
 | |
| 				state,					\
 | |
| 				is_retention)				\
 | |
| ({									\
 | |
| 	int __ret = 0;							\
 | |
| 									\
 | |
| 	if (!idx) {							\
 | |
| 		cpu_do_idle();						\
 | |
| 		return idx;						\
 | |
| 	}								\
 | |
| 									\
 | |
| 	if (!is_retention)						\
 | |
| 		__ret =  cpu_pm_enter();				\
 | |
| 	if (!__ret) {							\
 | |
| 		__ret = low_level_idle_enter(state);			\
 | |
| 		if (!is_retention)					\
 | |
| 			cpu_pm_exit();					\
 | |
| 	}								\
 | |
| 									\
 | |
| 	__ret ? -1 : idx;						\
 | |
| })
 | |
| 
 | |
| #define CPU_PM_CPU_IDLE_ENTER(low_level_idle_enter, idx)	\
 | |
| 	__CPU_PM_CPU_IDLE_ENTER(low_level_idle_enter, idx, idx, 0)
 | |
| 
 | |
| #define CPU_PM_CPU_IDLE_ENTER_RETENTION(low_level_idle_enter, idx)	\
 | |
| 	__CPU_PM_CPU_IDLE_ENTER(low_level_idle_enter, idx, idx, 1)
 | |
| 
 | |
| #define CPU_PM_CPU_IDLE_ENTER_PARAM(low_level_idle_enter, idx, state)	\
 | |
| 	__CPU_PM_CPU_IDLE_ENTER(low_level_idle_enter, idx, state, 0)
 | |
| 
 | |
| #define CPU_PM_CPU_IDLE_ENTER_RETENTION_PARAM(low_level_idle_enter, idx, state)	\
 | |
| 	__CPU_PM_CPU_IDLE_ENTER(low_level_idle_enter, idx, state, 1)
 | |
| 
 | |
| #endif /* _LINUX_CPUIDLE_H */
 |