diff options
Diffstat (limited to 'kernel/time/tick-common.c')
| -rw-r--r-- | kernel/time/tick-common.c | 257 | 
1 files changed, 122 insertions, 135 deletions
diff --git a/kernel/time/tick-common.c b/kernel/time/tick-common.c index b6b898d2eee..0a0608edeb2 100644 --- a/kernel/time/tick-common.c +++ b/kernel/time/tick-common.c @@ -18,7 +18,7 @@  #include <linux/percpu.h>  #include <linux/profile.h>  #include <linux/sched.h> -#include <linux/tick.h> +#include <linux/module.h>  #include <asm/irq_regs.h> @@ -33,8 +33,22 @@ DEFINE_PER_CPU(struct tick_device, tick_cpu_device);   */  ktime_t tick_next_period;  ktime_t tick_period; + +/* + * tick_do_timer_cpu is a timer core internal variable which holds the CPU NR + * which is responsible for calling do_timer(), i.e. the timekeeping stuff. This + * variable has two functions: + * + * 1) Prevent a thundering herd issue of a gazillion of CPUs trying to grab the + *    timekeeping lock all at once. Only the CPU which is assigned to do the + *    update is handling it. + * + * 2) Hand off the duty in the NOHZ idle case by setting the value to + *    TICK_DO_TIMER_NONE, i.e. a non existing CPU. So the next cpu which looks + *    at it will take over and keep the time keeping alive.  The handover + *    procedure also covers cpu hotplug. + */  int tick_do_timer_cpu __read_mostly = TICK_DO_TIMER_BOOT; -static DEFINE_RAW_SPINLOCK(tick_device_lock);  /*   * Debugging: see timer_list.c @@ -49,9 +63,13 @@ struct tick_device *tick_get_device(int cpu)   */  int tick_is_oneshot_available(void)  { -	struct clock_event_device *dev = __get_cpu_var(tick_cpu_device).evtdev; +	struct clock_event_device *dev = __this_cpu_read(tick_cpu_device.evtdev); -	return dev && (dev->features & CLOCK_EVT_FEAT_ONESHOT); +	if (!dev || !(dev->features & CLOCK_EVT_FEAT_ONESHOT)) +		return 0; +	if (!(dev->features & CLOCK_EVT_FEAT_C3STOP)) +		return 1; +	return tick_broadcast_oneshot_available();  }  /* @@ -60,13 +78,14 @@ int tick_is_oneshot_available(void)  static void tick_periodic(int cpu)  {  	if (tick_do_timer_cpu == cpu) { -		write_seqlock(&xtime_lock); +		write_seqlock(&jiffies_lock);  		/* Keep track of the next tick event */  		tick_next_period = ktime_add(tick_next_period, tick_period);  		do_timer(1); -		write_sequnlock(&xtime_lock); +		write_sequnlock(&jiffies_lock); +		update_wall_time();  	}  	update_process_times(user_mode(get_irq_regs())); @@ -79,19 +98,20 @@ static void tick_periodic(int cpu)  void tick_handle_periodic(struct clock_event_device *dev)  {  	int cpu = smp_processor_id(); -	ktime_t next; +	ktime_t next = dev->next_event;  	tick_periodic(cpu);  	if (dev->mode != CLOCK_EVT_MODE_ONESHOT)  		return; -	/* -	 * Setup the next period for devices, which do not have -	 * periodic mode: -	 */ -	next = ktime_add(dev->next_event, tick_period);  	for (;;) { -		if (!clockevents_program_event(dev, next, ktime_get())) +		/* +		 * Setup the next period for devices, which do not have +		 * periodic mode: +		 */ +		next = ktime_add(next, tick_period); + +		if (!clockevents_program_event(dev, next, false))  			return;  		/*  		 * Have to be careful here. If we're in oneshot mode, @@ -99,12 +119,11 @@ void tick_handle_periodic(struct clock_event_device *dev)  		 * to be sure we're using a real hardware clocksource.  		 * Otherwise we could get trapped in an infinite  		 * loop, as the tick_periodic() increments jiffies, -		 * when then will increment time, posibly causing +		 * which then will increment time, possibly causing  		 * the loop to trigger again and again.  		 */  		if (timekeeping_valid_for_hres())  			tick_periodic(cpu); -		next = ktime_add(next, tick_period);  	}  } @@ -127,14 +146,14 @@ void tick_setup_periodic(struct clock_event_device *dev, int broadcast)  		ktime_t next;  		do { -			seq = read_seqbegin(&xtime_lock); +			seq = read_seqbegin(&jiffies_lock);  			next = tick_next_period; -		} while (read_seqretry(&xtime_lock, seq)); +		} while (read_seqretry(&jiffies_lock, seq));  		clockevents_set_mode(dev, CLOCK_EVT_MODE_ONESHOT);  		for (;;) { -			if (!clockevents_program_event(dev, next, ktime_get())) +			if (!clockevents_program_event(dev, next, false))  				return;  			next = ktime_add(next, tick_period);  		} @@ -160,7 +179,10 @@ static void tick_setup_device(struct tick_device *td,  		 * this cpu:  		 */  		if (tick_do_timer_cpu == TICK_DO_TIMER_BOOT) { -			tick_do_timer_cpu = cpu; +			if (!tick_nohz_full_cpu(cpu)) +				tick_do_timer_cpu = cpu; +			else +				tick_do_timer_cpu = TICK_DO_TIMER_NONE;  			tick_next_period = ktime_get();  			tick_period = ktime_set(0, NSEC_PER_SEC / HZ);  		} @@ -188,7 +210,8 @@ static void tick_setup_device(struct tick_device *td,  	 * When global broadcasting is active, check if the current  	 * device is registered as a placeholder for broadcast mode.  	 * This allows us to handle this x86 misfeature in a generic -	 * way. +	 * way. This function also returns !=0 when we keep the +	 * current active broadcast state for this CPU.  	 */  	if (tick_device_uses_broadcast(newdev, cpu))  		return; @@ -199,17 +222,75 @@ static void tick_setup_device(struct tick_device *td,  		tick_setup_oneshot(newdev, handler, next_event);  } +void tick_install_replacement(struct clock_event_device *newdev) +{ +	struct tick_device *td = &__get_cpu_var(tick_cpu_device); +	int cpu = smp_processor_id(); + +	clockevents_exchange_device(td->evtdev, newdev); +	tick_setup_device(td, newdev, cpu, cpumask_of(cpu)); +	if (newdev->features & CLOCK_EVT_FEAT_ONESHOT) +		tick_oneshot_notify(); +} + +static bool tick_check_percpu(struct clock_event_device *curdev, +			      struct clock_event_device *newdev, int cpu) +{ +	if (!cpumask_test_cpu(cpu, newdev->cpumask)) +		return false; +	if (cpumask_equal(newdev->cpumask, cpumask_of(cpu))) +		return true; +	/* Check if irq affinity can be set */ +	if (newdev->irq >= 0 && !irq_can_set_affinity(newdev->irq)) +		return false; +	/* Prefer an existing cpu local device */ +	if (curdev && cpumask_equal(curdev->cpumask, cpumask_of(cpu))) +		return false; +	return true; +} + +static bool tick_check_preferred(struct clock_event_device *curdev, +				 struct clock_event_device *newdev) +{ +	/* Prefer oneshot capable device */ +	if (!(newdev->features & CLOCK_EVT_FEAT_ONESHOT)) { +		if (curdev && (curdev->features & CLOCK_EVT_FEAT_ONESHOT)) +			return false; +		if (tick_oneshot_mode_active()) +			return false; +	} + +	/* +	 * Use the higher rated one, but prefer a CPU local device with a lower +	 * rating than a non-CPU local device +	 */ +	return !curdev || +		newdev->rating > curdev->rating || +	       !cpumask_equal(curdev->cpumask, newdev->cpumask); +} +  /* - * Check, if the new registered device should be used. + * Check whether the new device is a better fit than curdev. curdev + * can be NULL !   */ -static int tick_check_new_device(struct clock_event_device *newdev) +bool tick_check_replacement(struct clock_event_device *curdev, +			    struct clock_event_device *newdev) +{ +	if (!tick_check_percpu(curdev, newdev, smp_processor_id())) +		return false; + +	return tick_check_preferred(curdev, newdev); +} + +/* + * Check, if the new registered device should be used. Called with + * clockevents_lock held and interrupts disabled. + */ +void tick_check_new_device(struct clock_event_device *newdev)  {  	struct clock_event_device *curdev;  	struct tick_device *td; -	int cpu, ret = NOTIFY_OK; -	unsigned long flags; - -	raw_spin_lock_irqsave(&tick_device_lock, flags); +	int cpu;  	cpu = smp_processor_id();  	if (!cpumask_test_cpu(cpu, newdev->cpumask)) @@ -219,40 +300,15 @@ static int tick_check_new_device(struct clock_event_device *newdev)  	curdev = td->evtdev;  	/* cpu local device ? */ -	if (!cpumask_equal(newdev->cpumask, cpumask_of(cpu))) { - -		/* -		 * If the cpu affinity of the device interrupt can not -		 * be set, ignore it. -		 */ -		if (!irq_can_set_affinity(newdev->irq)) -			goto out_bc; +	if (!tick_check_percpu(curdev, newdev, cpu)) +		goto out_bc; -		/* -		 * If we have a cpu local device already, do not replace it -		 * by a non cpu local device -		 */ -		if (curdev && cpumask_equal(curdev->cpumask, cpumask_of(cpu))) -			goto out_bc; -	} +	/* Preference decision */ +	if (!tick_check_preferred(curdev, newdev)) +		goto out_bc; -	/* -	 * If we have an active device, then check the rating and the oneshot -	 * feature. -	 */ -	if (curdev) { -		/* -		 * Prefer one shot capable devices ! -		 */ -		if ((curdev->features & CLOCK_EVT_FEAT_ONESHOT) && -		    !(newdev->features & CLOCK_EVT_FEAT_ONESHOT)) -			goto out_bc; -		/* -		 * Check the rating -		 */ -		if (curdev->rating >= newdev->rating) -			goto out_bc; -	} +	if (!try_module_get(newdev->owner)) +		return;  	/*  	 * Replace the eventually existing device by the new @@ -267,20 +323,13 @@ static int tick_check_new_device(struct clock_event_device *newdev)  	tick_setup_device(td, newdev, cpu, cpumask_of(cpu));  	if (newdev->features & CLOCK_EVT_FEAT_ONESHOT)  		tick_oneshot_notify(); - -	raw_spin_unlock_irqrestore(&tick_device_lock, flags); -	return NOTIFY_STOP; +	return;  out_bc:  	/*  	 * Can the new device be used as a broadcast device ?  	 */ -	if (tick_check_broadcast_device(newdev)) -		ret = NOTIFY_STOP; - -	raw_spin_unlock_irqrestore(&tick_device_lock, flags); - -	return ret; +	tick_install_broadcast_device(newdev);  }  /* @@ -288,7 +337,7 @@ out_bc:   *   * Called with interrupts disabled.   */ -static void tick_handover_do_timer(int *cpup) +void tick_handover_do_timer(int *cpup)  {  	if (*cpup == tick_do_timer_cpu) {  		int cpu = cpumask_first(cpu_online_mask); @@ -305,13 +354,11 @@ static void tick_handover_do_timer(int *cpup)   * access the hardware device itself.   * We just set the mode and remove it from the lists.   */ -static void tick_shutdown(unsigned int *cpup) +void tick_shutdown(unsigned int *cpup)  {  	struct tick_device *td = &per_cpu(tick_cpu_device, *cpup);  	struct clock_event_device *dev = td->evtdev; -	unsigned long flags; -	raw_spin_lock_irqsave(&tick_device_lock, flags);  	td->mode = TICKDEV_MODE_PERIODIC;  	if (dev) {  		/* @@ -320,28 +367,23 @@ static void tick_shutdown(unsigned int *cpup)  		 */  		dev->mode = CLOCK_EVT_MODE_UNUSED;  		clockevents_exchange_device(dev, NULL); +		dev->event_handler = clockevents_handle_noop;  		td->evtdev = NULL;  	} -	raw_spin_unlock_irqrestore(&tick_device_lock, flags);  } -static void tick_suspend(void) +void tick_suspend(void)  {  	struct tick_device *td = &__get_cpu_var(tick_cpu_device); -	unsigned long flags; -	raw_spin_lock_irqsave(&tick_device_lock, flags);  	clockevents_shutdown(td->evtdev); -	raw_spin_unlock_irqrestore(&tick_device_lock, flags);  } -static void tick_resume(void) +void tick_resume(void)  {  	struct tick_device *td = &__get_cpu_var(tick_cpu_device); -	unsigned long flags;  	int broadcast = tick_resume_broadcast(); -	raw_spin_lock_irqsave(&tick_device_lock, flags);  	clockevents_set_mode(td->evtdev, CLOCK_EVT_MODE_RESUME);  	if (!broadcast) { @@ -350,67 +392,12 @@ static void tick_resume(void)  		else  			tick_resume_oneshot();  	} -	raw_spin_unlock_irqrestore(&tick_device_lock, flags); -} - -/* - * Notification about clock event devices - */ -static int tick_notify(struct notifier_block *nb, unsigned long reason, -			       void *dev) -{ -	switch (reason) { - -	case CLOCK_EVT_NOTIFY_ADD: -		return tick_check_new_device(dev); - -	case CLOCK_EVT_NOTIFY_BROADCAST_ON: -	case CLOCK_EVT_NOTIFY_BROADCAST_OFF: -	case CLOCK_EVT_NOTIFY_BROADCAST_FORCE: -		tick_broadcast_on_off(reason, dev); -		break; - -	case CLOCK_EVT_NOTIFY_BROADCAST_ENTER: -	case CLOCK_EVT_NOTIFY_BROADCAST_EXIT: -		tick_broadcast_oneshot_control(reason); -		break; - -	case CLOCK_EVT_NOTIFY_CPU_DYING: -		tick_handover_do_timer(dev); -		break; - -	case CLOCK_EVT_NOTIFY_CPU_DEAD: -		tick_shutdown_broadcast_oneshot(dev); -		tick_shutdown_broadcast(dev); -		tick_shutdown(dev); -		break; - -	case CLOCK_EVT_NOTIFY_SUSPEND: -		tick_suspend(); -		tick_suspend_broadcast(); -		break; - -	case CLOCK_EVT_NOTIFY_RESUME: -		tick_resume(); -		break; - -	default: -		break; -	} - -	return NOTIFY_OK;  } -static struct notifier_block tick_notifier = { -	.notifier_call = tick_notify, -}; -  /**   * tick_init - initialize the tick control - * - * Register the notifier with the clockevents framework   */  void __init tick_init(void)  { -	clockevents_register_notifier(&tick_notifier); +	tick_broadcast_init();  }  | 
