diff options
Diffstat (limited to 'drivers/acpi/processor_idle.c')
| -rw-r--r-- | drivers/acpi/processor_idle.c | 251 | 
1 files changed, 207 insertions, 44 deletions
diff --git a/drivers/acpi/processor_idle.c b/drivers/acpi/processor_idle.c index 9b88f9828d8..73b2909dddf 100644 --- a/drivers/acpi/processor_idle.c +++ b/drivers/acpi/processor_idle.c @@ -741,22 +741,25 @@ static inline void acpi_idle_do_entry(struct acpi_processor_cx *cx)  /**   * acpi_idle_enter_c1 - enters an ACPI C1 state-type   * @dev: the target CPU - * @state: the state data + * @drv: cpuidle driver containing cpuidle state info + * @index: index of target state   *   * This is equivalent to the HALT instruction.   */  static int acpi_idle_enter_c1(struct cpuidle_device *dev, -			      struct cpuidle_state *state) +		struct cpuidle_driver *drv, int index)  {  	ktime_t  kt1, kt2;  	s64 idle_time;  	struct acpi_processor *pr; -	struct acpi_processor_cx *cx = cpuidle_get_statedata(state); +	struct cpuidle_state_usage *state_usage = &dev->states_usage[index]; +	struct acpi_processor_cx *cx = cpuidle_get_statedata(state_usage);  	pr = __this_cpu_read(processors); +	dev->last_residency = 0;  	if (unlikely(!pr)) -		return 0; +		return -EINVAL;  	local_irq_disable(); @@ -764,7 +767,7 @@ static int acpi_idle_enter_c1(struct cpuidle_device *dev,  	if (acpi_idle_suspend) {  		local_irq_enable();  		cpu_relax(); -		return 0; +		return -EINVAL;  	}  	lapic_timer_state_broadcast(pr, cx, 1); @@ -773,37 +776,47 @@ static int acpi_idle_enter_c1(struct cpuidle_device *dev,  	kt2 = ktime_get_real();  	idle_time =  ktime_to_us(ktime_sub(kt2, kt1)); +	/* Update device last_residency*/ +	dev->last_residency = (int)idle_time; +  	local_irq_enable();  	cx->usage++;  	lapic_timer_state_broadcast(pr, cx, 0); -	return idle_time; +	return index;  }  /**   * acpi_idle_enter_simple - enters an ACPI state without BM handling   * @dev: the target CPU - * @state: the state data + * @drv: cpuidle driver with cpuidle state information + * @index: the index of suggested state   */  static int acpi_idle_enter_simple(struct cpuidle_device *dev, -				  struct cpuidle_state *state) +		struct cpuidle_driver *drv, int index)  {  	struct acpi_processor *pr; -	struct acpi_processor_cx *cx = cpuidle_get_statedata(state); +	struct cpuidle_state_usage *state_usage = &dev->states_usage[index]; +	struct acpi_processor_cx *cx = cpuidle_get_statedata(state_usage);  	ktime_t  kt1, kt2;  	s64 idle_time_ns;  	s64 idle_time;  	pr = __this_cpu_read(processors); +	dev->last_residency = 0;  	if (unlikely(!pr)) -		return 0; - -	if (acpi_idle_suspend) -		return(acpi_idle_enter_c1(dev, state)); +		return -EINVAL;  	local_irq_disable(); +	if (acpi_idle_suspend) { +		local_irq_enable(); +		cpu_relax(); +		return -EINVAL; +	} + +  	if (cx->entry_method != ACPI_CSTATE_FFH) {  		current_thread_info()->status &= ~TS_POLLING;  		/* @@ -815,7 +828,7 @@ static int acpi_idle_enter_simple(struct cpuidle_device *dev,  		if (unlikely(need_resched())) {  			current_thread_info()->status |= TS_POLLING;  			local_irq_enable(); -			return 0; +			return -EINVAL;  		}  	} @@ -837,6 +850,9 @@ static int acpi_idle_enter_simple(struct cpuidle_device *dev,  	idle_time = idle_time_ns;  	do_div(idle_time, NSEC_PER_USEC); +	/* Update device last_residency*/ +	dev->last_residency = (int)idle_time; +  	/* Tell the scheduler how much we idled: */  	sched_clock_idle_wakeup_event(idle_time_ns); @@ -848,7 +864,7 @@ static int acpi_idle_enter_simple(struct cpuidle_device *dev,  	lapic_timer_state_broadcast(pr, cx, 0);  	cx->time += idle_time; -	return idle_time; +	return index;  }  static int c3_cpu_count; @@ -857,37 +873,43 @@ static DEFINE_RAW_SPINLOCK(c3_lock);  /**   * acpi_idle_enter_bm - enters C3 with proper BM handling   * @dev: the target CPU - * @state: the state data + * @drv: cpuidle driver containing state data + * @index: the index of suggested state   *   * If BM is detected, the deepest non-C3 idle state is entered instead.   */  static int acpi_idle_enter_bm(struct cpuidle_device *dev, -			      struct cpuidle_state *state) +		struct cpuidle_driver *drv, int index)  {  	struct acpi_processor *pr; -	struct acpi_processor_cx *cx = cpuidle_get_statedata(state); +	struct cpuidle_state_usage *state_usage = &dev->states_usage[index]; +	struct acpi_processor_cx *cx = cpuidle_get_statedata(state_usage);  	ktime_t  kt1, kt2;  	s64 idle_time_ns;  	s64 idle_time;  	pr = __this_cpu_read(processors); +	dev->last_residency = 0;  	if (unlikely(!pr)) -		return 0; +		return -EINVAL; + -	if (acpi_idle_suspend) -		return(acpi_idle_enter_c1(dev, state)); +	if (acpi_idle_suspend) { +		cpu_relax(); +		return -EINVAL; +	}  	if (!cx->bm_sts_skip && acpi_idle_bm_check()) { -		if (dev->safe_state) { -			dev->last_state = dev->safe_state; -			return dev->safe_state->enter(dev, dev->safe_state); +		if (drv->safe_state_index >= 0) { +			return drv->states[drv->safe_state_index].enter(dev, +						drv, drv->safe_state_index);  		} else {  			local_irq_disable();  			acpi_safe_halt();  			local_irq_enable(); -			return 0; +			return -EINVAL;  		}  	} @@ -904,7 +926,7 @@ static int acpi_idle_enter_bm(struct cpuidle_device *dev,  		if (unlikely(need_resched())) {  			current_thread_info()->status |= TS_POLLING;  			local_irq_enable(); -			return 0; +			return -EINVAL;  		}  	} @@ -954,6 +976,9 @@ static int acpi_idle_enter_bm(struct cpuidle_device *dev,  	idle_time = idle_time_ns;  	do_div(idle_time, NSEC_PER_USEC); +	/* Update device last_residency*/ +	dev->last_residency = (int)idle_time; +  	/* Tell the scheduler how much we idled: */  	sched_clock_idle_wakeup_event(idle_time_ns); @@ -965,7 +990,7 @@ static int acpi_idle_enter_bm(struct cpuidle_device *dev,  	lapic_timer_state_broadcast(pr, cx, 0);  	cx->time += idle_time; -	return idle_time; +	return index;  }  struct cpuidle_driver acpi_idle_driver = { @@ -974,14 +999,16 @@ struct cpuidle_driver acpi_idle_driver = {  };  /** - * acpi_processor_setup_cpuidle - prepares and configures CPUIDLE + * acpi_processor_setup_cpuidle_cx - prepares and configures CPUIDLE + * device i.e. per-cpu data + *   * @pr: the ACPI processor   */ -static int acpi_processor_setup_cpuidle(struct acpi_processor *pr) +static int acpi_processor_setup_cpuidle_cx(struct acpi_processor *pr)  {  	int i, count = CPUIDLE_DRIVER_STATE_START;  	struct acpi_processor_cx *cx; -	struct cpuidle_state *state; +	struct cpuidle_state_usage *state_usage;  	struct cpuidle_device *dev = &pr->power.dev;  	if (!pr->flags.power_setup_done) @@ -992,9 +1019,62 @@ static int acpi_processor_setup_cpuidle(struct acpi_processor *pr)  	}  	dev->cpu = pr->id; + +	if (max_cstate == 0) +		max_cstate = 1; + +	for (i = 1; i < ACPI_PROCESSOR_MAX_POWER && i <= max_cstate; i++) { +		cx = &pr->power.states[i]; +		state_usage = &dev->states_usage[count]; + +		if (!cx->valid) +			continue; + +#ifdef CONFIG_HOTPLUG_CPU +		if ((cx->type != ACPI_STATE_C1) && (num_online_cpus() > 1) && +		    !pr->flags.has_cst && +		    !(acpi_gbl_FADT.flags & ACPI_FADT_C2_MP_SUPPORTED)) +			continue; +#endif + +		cpuidle_set_statedata(state_usage, cx); + +		count++; +		if (count == CPUIDLE_STATE_MAX) +			break; +	} + +	dev->state_count = count; + +	if (!count) +		return -EINVAL; + +	return 0; +} + +/** + * acpi_processor_setup_cpuidle states- prepares and configures cpuidle + * global state data i.e. idle routines + * + * @pr: the ACPI processor + */ +static int acpi_processor_setup_cpuidle_states(struct acpi_processor *pr) +{ +	int i, count = CPUIDLE_DRIVER_STATE_START; +	struct acpi_processor_cx *cx; +	struct cpuidle_state *state; +	struct cpuidle_driver *drv = &acpi_idle_driver; + +	if (!pr->flags.power_setup_done) +		return -EINVAL; + +	if (pr->flags.power == 0) +		return -EINVAL; + +	drv->safe_state_index = -1;  	for (i = 0; i < CPUIDLE_STATE_MAX; i++) { -		dev->states[i].name[0] = '\0'; -		dev->states[i].desc[0] = '\0'; +		drv->states[i].name[0] = '\0'; +		drv->states[i].desc[0] = '\0';  	}  	if (max_cstate == 0) @@ -1002,7 +1082,6 @@ static int acpi_processor_setup_cpuidle(struct acpi_processor *pr)  	for (i = 1; i < ACPI_PROCESSOR_MAX_POWER && i <= max_cstate; i++) {  		cx = &pr->power.states[i]; -		state = &dev->states[count];  		if (!cx->valid)  			continue; @@ -1013,8 +1092,8 @@ static int acpi_processor_setup_cpuidle(struct acpi_processor *pr)  		    !(acpi_gbl_FADT.flags & ACPI_FADT_C2_MP_SUPPORTED))  			continue;  #endif -		cpuidle_set_statedata(state, cx); +		state = &drv->states[count];  		snprintf(state->name, CPUIDLE_NAME_LEN, "C%d", i);  		strncpy(state->desc, cx->desc, CPUIDLE_DESC_LEN);  		state->exit_latency = cx->latency; @@ -1027,13 +1106,13 @@ static int acpi_processor_setup_cpuidle(struct acpi_processor *pr)  				state->flags |= CPUIDLE_FLAG_TIME_VALID;  			state->enter = acpi_idle_enter_c1; -			dev->safe_state = state; +			drv->safe_state_index = count;  			break;  			case ACPI_STATE_C2:  			state->flags |= CPUIDLE_FLAG_TIME_VALID;  			state->enter = acpi_idle_enter_simple; -			dev->safe_state = state; +			drv->safe_state_index = count;  			break;  			case ACPI_STATE_C3: @@ -1049,7 +1128,7 @@ static int acpi_processor_setup_cpuidle(struct acpi_processor *pr)  			break;  	} -	dev->state_count = count; +	drv->state_count = count;  	if (!count)  		return -EINVAL; @@ -1057,7 +1136,7 @@ static int acpi_processor_setup_cpuidle(struct acpi_processor *pr)  	return 0;  } -int acpi_processor_cst_has_changed(struct acpi_processor *pr) +int acpi_processor_hotplug(struct acpi_processor *pr)  {  	int ret = 0; @@ -1078,7 +1157,7 @@ int acpi_processor_cst_has_changed(struct acpi_processor *pr)  	cpuidle_disable_device(&pr->power.dev);  	acpi_processor_get_power_info(pr);  	if (pr->flags.power) { -		acpi_processor_setup_cpuidle(pr); +		acpi_processor_setup_cpuidle_cx(pr);  		ret = cpuidle_enable_device(&pr->power.dev);  	}  	cpuidle_resume_and_unlock(); @@ -1086,10 +1165,72 @@ int acpi_processor_cst_has_changed(struct acpi_processor *pr)  	return ret;  } +int acpi_processor_cst_has_changed(struct acpi_processor *pr) +{ +	int cpu; +	struct acpi_processor *_pr; + +	if (disabled_by_idle_boot_param()) +		return 0; + +	if (!pr) +		return -EINVAL; + +	if (nocst) +		return -ENODEV; + +	if (!pr->flags.power_setup_done) +		return -ENODEV; + +	/* +	 * FIXME:  Design the ACPI notification to make it once per +	 * system instead of once per-cpu.  This condition is a hack +	 * to make the code that updates C-States be called once. +	 */ + +	if (smp_processor_id() == 0 && +			cpuidle_get_driver() == &acpi_idle_driver) { + +		cpuidle_pause_and_lock(); +		/* Protect against cpu-hotplug */ +		get_online_cpus(); + +		/* Disable all cpuidle devices */ +		for_each_online_cpu(cpu) { +			_pr = per_cpu(processors, cpu); +			if (!_pr || !_pr->flags.power_setup_done) +				continue; +			cpuidle_disable_device(&_pr->power.dev); +		} + +		/* Populate Updated C-state information */ +		acpi_processor_setup_cpuidle_states(pr); + +		/* Enable all cpuidle devices */ +		for_each_online_cpu(cpu) { +			_pr = per_cpu(processors, cpu); +			if (!_pr || !_pr->flags.power_setup_done) +				continue; +			acpi_processor_get_power_info(_pr); +			if (_pr->flags.power) { +				acpi_processor_setup_cpuidle_cx(_pr); +				cpuidle_enable_device(&_pr->power.dev); +			} +		} +		put_online_cpus(); +		cpuidle_resume_and_unlock(); +	} + +	return 0; +} + +static int acpi_processor_registered; +  int __cpuinit acpi_processor_power_init(struct acpi_processor *pr,  			      struct acpi_device *device)  {  	acpi_status status = 0; +	int retval;  	static int first_run;  	if (disabled_by_idle_boot_param()) @@ -1126,9 +1267,26 @@ int __cpuinit acpi_processor_power_init(struct acpi_processor *pr,  	 * platforms that only support C1.  	 */  	if (pr->flags.power) { -		acpi_processor_setup_cpuidle(pr); -		if (cpuidle_register_device(&pr->power.dev)) -			return -EIO; +		/* Register acpi_idle_driver if not already registered */ +		if (!acpi_processor_registered) { +			acpi_processor_setup_cpuidle_states(pr); +			retval = cpuidle_register_driver(&acpi_idle_driver); +			if (retval) +				return retval; +			printk(KERN_DEBUG "ACPI: %s registered with cpuidle\n", +					acpi_idle_driver.name); +		} +		/* Register per-cpu cpuidle_device. Cpuidle driver +		 * must already be registered before registering device +		 */ +		acpi_processor_setup_cpuidle_cx(pr); +		retval = cpuidle_register_device(&pr->power.dev); +		if (retval) { +			if (acpi_processor_registered == 0) +				cpuidle_unregister_driver(&acpi_idle_driver); +			return retval; +		} +		acpi_processor_registered++;  	}  	return 0;  } @@ -1139,8 +1297,13 @@ int acpi_processor_power_exit(struct acpi_processor *pr,  	if (disabled_by_idle_boot_param())  		return 0; -	cpuidle_unregister_device(&pr->power.dev); -	pr->flags.power_setup_done = 0; +	if (pr->flags.power) { +		cpuidle_unregister_device(&pr->power.dev); +		acpi_processor_registered--; +		if (acpi_processor_registered == 0) +			cpuidle_unregister_driver(&acpi_idle_driver); +	} +	pr->flags.power_setup_done = 0;  	return 0;  }  |