kernel/idle: Clean up and refactoring / remove TICKLESS_IDLE_THRESH

While I'm in the idle code, let's clean this loop up.  It was a really
bad #ifdef hell:

* Remove the CONFIG_TICKLESS_IDLE_THRESH logic (and the kconfig),
  which never did anything but needlessly increase latency.

* Move the needed timeout logic from the main loop into
  pm_save_idle(), which eliminates the special case for
  !SYS_CLOCK_EXISTS.

Behavior (modulo that one kconfig) should be completely unchanged, and
now the inner part of the idle loop looks like:

    while (true) {
        (void) arch_irq_lock();

        if (IS_ENABLED(CONFIG_PM)) {
            pm_save_idle();
        } else {
            k_cpu_idle();
        }
    }

Signed-off-by: Andy Ross <andrew.j.ross@intel.com>
diff --git a/kernel/Kconfig b/kernel/Kconfig
index 6258ef4..49241bb 100644
--- a/kernel/Kconfig
+++ b/kernel/Kconfig
@@ -852,16 +852,6 @@
 	  saving state for extended periods without having to wake up to
 	  service each tick as it occurs.
 
-config TICKLESS_IDLE_THRESH
-	int "Tickless idle threshold"
-	default 3
-	depends on TICKLESS_IDLE
-	help
-	  This option enables clock interrupt suppression when the kernel idles
-	  for only a short period of time. It specifies the minimum number of
-	  ticks that must occur before the next kernel timer expires in order
-	  for suppression to happen.
-
 config TICKLESS_KERNEL
 	bool "Tickless kernel"
 	default y if TICKLESS_CAPABLE
diff --git a/kernel/idle.c b/kernel/idle.c
index 1b9d3c7..9b7d3bb 100644
--- a/kernel/idle.c
+++ b/kernel/idle.c
@@ -14,21 +14,10 @@
 #include <logging/log.h>
 #include <ksched.h>
 
+extern uint32_t z_timestamp_idle;
+
 LOG_MODULE_DECLARE(os, CONFIG_KERNEL_LOG_LEVEL);
 
-#ifdef CONFIG_TICKLESS_IDLE_THRESH
-#define IDLE_THRESH CONFIG_TICKLESS_IDLE_THRESH
-#else
-#define IDLE_THRESH 1
-#endif
-
-/* Fallback idle spin loop for SMP platforms without a working IPI */
-#if (defined(CONFIG_SMP) && !defined(CONFIG_SCHED_IPI_SUPPORTED))
-#define SMP_FALLBACK 1
-#else
-#define SMP_FALLBACK 0
-#endif
-
 #ifdef CONFIG_PM
 /*
  * Used to allow pm_system_suspend() implementation to control notification
@@ -36,7 +25,6 @@
  */
 unsigned char pm_idle_exit_notify;
 
-
 /* LCOV_EXCL_START
  * These are almost certainly overidden and in any event do nothing
  */
@@ -52,20 +40,16 @@
 #endif /* CONFIG_PM */
 
 /**
- *
  * @brief Indicate that kernel is idling in tickless mode
  *
  * Sets the kernel data structure idle field to either a positive value or
  * K_FOREVER.
- *
- * @param ticks the number of ticks to idle
- *
- * @return N/A
  */
-#if !SMP_FALLBACK && CONFIG_PM
-static enum pm_state pm_save_idle(int32_t ticks)
+static void pm_save_idle(void)
 {
-	static enum pm_state idle_state = PM_STATE_ACTIVE;
+#ifdef CONFIG_PM
+	int32_t ticks = z_get_next_timeout_expiry();
+	_kernel.idle = ticks;
 
 	pm_idle_exit_notify = 1U;
 
@@ -82,16 +66,12 @@
 	 * idle processing re-enables interrupts which is essential for
 	 * the kernel's scheduling logic.
 	 */
-	idle_state = pm_system_suspend(ticks);
-	if (idle_state == PM_STATE_ACTIVE) {
+	if (pm_system_suspend(ticks) == PM_STATE_ACTIVE) {
 		pm_idle_exit_notify = 0U;
+		k_cpu_idle();
 	}
-
-	return idle_state;
-
+#endif
 }
-#endif /* !SMP_FALLBACK */
-
 
 void z_pm_save_idle_exit(int32_t ticks)
 {
@@ -105,64 +85,57 @@
 	if (pm_idle_exit_notify) {
 		pm_system_resume();
 	}
-#endif	/* CONFIG_PM */
+#endif
 	z_clock_idle_exit();
 }
 
-
-#if K_IDLE_PRIO < 0
-#define IDLE_YIELD_IF_COOP() k_yield()
-#else
-#define IDLE_YIELD_IF_COOP() do { } while (false)
-#endif
-
-void idle(void *p1, void *unused2, void *unused3)
+void idle(void *unused1, void *unused2, void *unused3)
 {
+	ARG_UNUSED(unused1);
 	ARG_UNUSED(unused2);
 	ARG_UNUSED(unused3);
 
 #ifdef CONFIG_BOOT_TIME_MEASUREMENT
-	/* record timestamp when idling begins */
-
-	extern uint32_t z_timestamp_idle;
-
 	z_timestamp_idle = k_cycle_get_32();
-#endif /* CONFIG_BOOT_TIME_MEASUREMENT */
+#endif
 
 	while (true) {
-#if SMP_FALLBACK
-		k_busy_wait(100);
-		k_yield();
-#else
+		/* SMP systems without a working IPI can't
+		 * actual enter an idle state, because they
+		 * can't be notified of scheduler changes
+		 * (i.e. threads they should run).  They just
+		 * spin in a yield loop.  This is intended as
+		 * a fallback configuration for new platform
+		 * bringup.
+		 */
+		if (IS_ENABLED(CONFIG_SMP) &&
+		    !IS_ENABLED(CONFIG_SCHED_IPI_SUPPORTED)) {
+			k_busy_wait(100);
+			k_yield();
+			continue;
+		}
 
+		/* Note weird API: k_cpu_idle() is called with local
+		 * CPU interrupts masked, and returns with them
+		 * unmasked.  It does not take a spinlock or other
+		 * higher level construct.
+		 */
 		(void) arch_irq_lock();
 
-#ifdef CONFIG_SYS_CLOCK_EXISTS
-		int32_t ticks = z_get_next_timeout_expiry();
-
-		/* The documented behavior of CONFIG_TICKLESS_IDLE_THRESH is
-		 * that the system should not enter a tickless idle for
-		 * periods less than that.  This seems... silly, given that it
-		 * saves no power and does not improve latency.  But it's an
-		 * API we need to honor...
-		 */
-		z_set_timeout_expiry((ticks < IDLE_THRESH) ? 1 : ticks, true);
-#ifdef CONFIG_PM
-		_kernel.idle = ticks;
-		/* Check power policy and decide if we are going to sleep or
-		 * just idle.
-		 */
-		if (pm_save_idle(ticks) == PM_STATE_ACTIVE) {
+		if (IS_ENABLED(CONFIG_PM)) {
+			pm_save_idle();
+		} else {
 			k_cpu_idle();
 		}
-#else /* CONFIG_PM */
-		k_cpu_idle();
-#endif /* CONFIG_PM */
-#else /* CONFIG_SYS_CLOCK_EXISTS */
-		k_cpu_idle();
-#endif /* CONFIG_SYS_CLOCK_EXISTS */
 
-		IDLE_YIELD_IF_COOP();
-#endif /* SMP_FALLBACK */
+		/* It is possible to (pathologically) configure the
+		 * idle thread to have a non-preemptible priority.
+		 * You might think this is an API bug, but we actually
+		 * have a test that exercises this.  Handle the edge
+		 * case when that happens.
+		 */
+		if (K_IDLE_PRIO < 0) {
+			k_yield();
+		}
 	}
 }