exti: add support for specifying a max latency requirement for interrupts

A non-zero maxLatency specified for an enabled interrupt will prevent the
system from going into sleep modes that take longer than the specified
latency to wake up.

Set vsync maxLatencyNs to MAX_VSYNC_INT_LATENCY to prevent the cpu from
going into any stop modes while vsync is enabled.

Bug: 29886751
Change-Id: I4b578b7598d8e4d6989b4ea0dafeb9c8900f8553
Signed-off-by: Ben Fennema <fennema@google.com>
diff --git a/firmware/inc/isr.h b/firmware/inc/isr.h
index 2ce9d2c..73a8f03 100644
--- a/firmware/inc/isr.h
+++ b/firmware/inc/isr.h
@@ -34,6 +34,9 @@
 
 struct ChainedIsr {
     link_t node;
+
+    uint32_t maxLatencyNs;
+
     bool (*func)(struct ChainedIsr *);
     uint16_t tid;
 };
@@ -88,4 +91,18 @@
     return count;
 }
 
+static inline uint32_t maxLatencyIsr(struct ChainedInterrupt *interrupt)
+{
+    struct link_t *cur, *tmp;
+    uint32_t latency = 0;
+
+    list_iterate(&interrupt->isrs, cur, tmp) {
+        struct ChainedIsr *curIsr = container_of(cur, struct ChainedIsr, node);
+        if (!latency || (curIsr->maxLatencyNs && curIsr->maxLatencyNs < latency))
+            latency = curIsr->maxLatencyNs;
+    }
+
+    return latency;
+}
+
 #endif /* __ISR_H */
diff --git a/firmware/inc/platform.h b/firmware/inc/platform.h
index c589b5b..aa706ca 100644
--- a/firmware/inc/platform.h
+++ b/firmware/inc/platform.h
@@ -68,6 +68,7 @@
 
 /* 0 for any "max" value means "do not care" */
 bool platRequestDevInSleepMode(uint32_t sleepDevID, uint32_t maxWakeupTime); //request that this device remain powered/clocked in sleep mode   (device lists are platform specific)
+bool platAdjustDevInSleepMode(uint32_t sleepDevID, uint32_t maxWakeupTime); //adjust maxWakeupTime for this device
 bool platReleaseDevInSleepMode(uint32_t sleepDevID); //unrequest that this device remain powered/clocked in sleep mode (device lists are platform specific)
 
 
diff --git a/firmware/inc/platform/stm32f4xx/exti.h b/firmware/inc/platform/stm32f4xx/exti.h
index 84fcd39..59d9db6 100644
--- a/firmware/inc/platform/stm32f4xx/exti.h
+++ b/firmware/inc/platform/stm32f4xx/exti.h
@@ -68,6 +68,8 @@
 int extiUnchainIsr(IRQn_Type n, struct ChainedIsr *isr);
 int extiUnchainAll(uint32_t tid);
 
+int extiSetMaxLatency(struct ChainedIsr *isr, uint32_t maxLatencyNs);
+
 static inline void extiEnableIntGpio(const struct Gpio *__restrict gpioHandle, enum ExtiTrigger trigger)
 {
     if (gpioHandle) {
diff --git a/firmware/inc/platform/stm32f4xx/plat.h b/firmware/inc/platform/stm32f4xx/plat.h
index 253d9fa..c07ae56 100644
--- a/firmware/inc/platform/stm32f4xx/plat.h
+++ b/firmware/inc/platform/stm32f4xx/plat.h
@@ -35,6 +35,7 @@
     Stm32sleepDevSpi2, /* we use this to prevent stop mode during spi2 xfers */
     Stm32sleepDevSpi3, /* we use this to prevent stop mode during spi3 xfers */
     Stm32sleepDevI2c1, /* we use this to prevent stop mode during i2c1 xfers */
+    Stm32sleepDevExti, /* we use this for max external interrupt latency */
 
     Stm32sleepDevNum,  //must be last always, and must be <= PLAT_MAX_SLEEP_DEVS
 };
diff --git a/firmware/src/drivers/vsync/vsync.c b/firmware/src/drivers/vsync/vsync.c
index c6d55aa..75716ae 100644
--- a/firmware/src/drivers/vsync/vsync.c
+++ b/firmware/src/drivers/vsync/vsync.c
@@ -38,7 +38,8 @@
 
 // This defines how many vsync events we could handle being backed up in the
 // queue. Use this to size our slab
-#define MAX_VSYNC_EVENTS  4
+#define MAX_VSYNC_EVENTS        4
+#define MAX_VSYNC_INT_LATENCY   1000 /* in ns */
 
 #ifndef VSYNC_PIN
 #error "VSYNC_PIN is not defined; please define in variant.h"
@@ -198,6 +199,7 @@
     mTask.sensorHandle = sensorRegister(&mSensorInfo, &mSensorOps, NULL, true);
     mTask.pin = gpioRequest(VSYNC_PIN);
     mTask.isr.func = vsyncIsr;
+    mTask.isr.maxLatencyNs = MAX_VSYNC_INT_LATENCY;
 
     mTask.evtSlab = slabAllocatorNew(sizeof(struct SingleAxisDataEvent) + sizeof(struct SingleAxisDataPoint), 4, MAX_VSYNC_EVENTS);
     if (!mTask.evtSlab) {
diff --git a/firmware/src/platform/stm32f4xx/exti.c b/firmware/src/platform/stm32f4xx/exti.c
index 30e91db..2543ac2 100644
--- a/firmware/src/platform/stm32f4xx/exti.c
+++ b/firmware/src/platform/stm32f4xx/exti.c
@@ -16,6 +16,7 @@
 
 #include <errno.h>
 #include <isr.h>
+#include <platform.h>
 
 #include <plat/inc/cmsis.h>
 #include <plat/inc/exti.h>
@@ -94,7 +95,9 @@
     .irq = i,                               \
 }
 
-static struct ExtiInterrupt gInterrupts[] = {
+uint32_t mMaxLatency = 0;
+
+static struct ExtiInterrupt mInterrupts[] = {
     DECLARE_SHARED_EXTI(EXTI0_IRQn),
     DECLARE_SHARED_EXTI(EXTI1_IRQn),
     DECLARE_SHARED_EXTI(EXTI2_IRQn),
@@ -104,14 +107,39 @@
     DECLARE_SHARED_EXTI(EXTI15_10_IRQn),
 };
 
+static void extiUpdateMaxLatency(uint32_t maxLatencyNs)
+{
+    if (!maxLatencyNs && mMaxLatency)
+        platReleaseDevInSleepMode(Stm32sleepDevExti);
+    else if (maxLatencyNs && !mMaxLatency)
+        platRequestDevInSleepMode(Stm32sleepDevExti, maxLatencyNs);
+    else if (maxLatencyNs && mMaxLatency)
+        platAdjustDevInSleepMode(Stm32sleepDevExti, maxLatencyNs);
+    mMaxLatency = maxLatencyNs;
+}
+
+static void extiCalcMaxLatency()
+{
+    int i;
+    uint32_t maxLatency, newMaxLatency = 0;
+    struct ExtiInterrupt *exti = mInterrupts;
+
+    for (i = 0; i < ARRAY_SIZE(mInterrupts); ++i, ++exti) {
+        maxLatency = maxLatencyIsr(&exti->base);
+        if (!newMaxLatency || (maxLatency && maxLatency < newMaxLatency))
+            newMaxLatency = maxLatency;
+    }
+    extiUpdateMaxLatency(newMaxLatency);
+}
+
 static inline struct ExtiInterrupt *extiForIrq(IRQn_Type n)
 {
     if (n >= EXTI0_IRQn && n <= EXTI4_IRQn)
-        return &gInterrupts[n - EXTI0_IRQn];
+        return &mInterrupts[n - EXTI0_IRQn];
     if (n == EXTI9_5_IRQn)
-        return &gInterrupts[ARRAY_SIZE(gInterrupts) - 2];
+        return &mInterrupts[ARRAY_SIZE(mInterrupts) - 2];
     if (n == EXTI15_10_IRQn)
-        return &gInterrupts[ARRAY_SIZE(gInterrupts) - 1];
+        return &mInterrupts[ARRAY_SIZE(mInterrupts) - 1];
     return NULL;
 }
 
@@ -135,6 +163,24 @@
 DEFINE_SHARED_EXTI_ISR(9_5)
 DEFINE_SHARED_EXTI_ISR(15_10)
 
+int extiSetMaxLatency(struct ChainedIsr *isr, uint32_t maxLatencyNs)
+{
+    uint32_t latency;
+
+    if (!isr)
+        return -EINVAL;
+
+    if (maxLatencyNs != isr->maxLatencyNs) {
+        latency = isr->maxLatencyNs;
+        isr->maxLatencyNs = maxLatencyNs;
+        if (!mMaxLatency || latency == mMaxLatency || (maxLatencyNs && maxLatencyNs < mMaxLatency)) {
+            extiCalcMaxLatency();
+        }
+    }
+
+    return 0;
+}
+
 int extiChainIsr(IRQn_Type n, struct ChainedIsr *isr)
 {
     struct ExtiInterrupt *exti = extiForIrq(n);
@@ -142,6 +188,9 @@
         return -EINVAL;
 
     chainIsr(&exti->base, isr);
+    if (!mMaxLatency || (isr->maxLatencyNs && isr->maxLatencyNs < mMaxLatency))
+        extiUpdateMaxLatency(isr->maxLatencyNs);
+
     return 0;
 }
 
@@ -152,16 +201,19 @@
         return -EINVAL;
 
     unchainIsr(&exti->base, isr);
+    if (isr->maxLatencyNs && isr->maxLatencyNs == mMaxLatency)
+        extiCalcMaxLatency();
     return 0;
 }
 
 int extiUnchainAll(uint32_t tid)
 {
     int i, count = 0;
-    struct ExtiInterrupt *exti = gInterrupts;
+    struct ExtiInterrupt *exti = mInterrupts;
 
-    for (i = 0; i < ARRAY_SIZE(gInterrupts); ++i, ++exti)
+    for (i = 0; i < ARRAY_SIZE(mInterrupts); ++i, ++exti)
         count += unchainIsrAll(&exti->base, tid);
+    extiCalcMaxLatency();
 
     return count;
 }
diff --git a/firmware/src/platform/stm32f4xx/platform.c b/firmware/src/platform/stm32f4xx/platform.c
index 5cbe5a7..f4fc6c6 100644
--- a/firmware/src/platform/stm32f4xx/platform.c
+++ b/firmware/src/platform/stm32f4xx/platform.c
@@ -373,6 +373,16 @@
     return true;
 }
 
+bool platAdjustDevInSleepMode(uint32_t sleepDevID, uint32_t maxWakeupTime)
+{
+    if (sleepDevID >= PLAT_MAX_SLEEP_DEVS || sleepDevID >= Stm32sleepDevNum)
+        return false;
+
+    mDevsMaxWakeTime[sleepDevID] = maxWakeupTime;
+
+    return true;
+}
+
 bool platReleaseDevInSleepMode(uint32_t sleepDevID)
 {
     if (sleepDevID >= PLAT_MAX_SLEEP_DEVS || sleepDevID >= Stm32sleepDevNum)
@@ -519,6 +529,7 @@
         .jitterPpm = 0,
         .driftPpm = 50,
         .maxWakeupTime = 407000ull,
+        .devsAvail = (1 << Stm32sleepDevExti),
         .prepare = sleepClockRtcPrepare,
         .wake = sleepClockRtcWake,
         .userData = (void*)stm32f411SleepModeStopLPLV,
@@ -532,6 +543,7 @@
         .jitterPpm = 0,
         .driftPpm = 50,
         .maxWakeupTime = 130000ull,
+        .devsAvail = (1 << Stm32sleepDevExti),
         .prepare = sleepClockRtcPrepare,
         .wake = sleepClockRtcWake,
         .userData = (void*)stm32f411SleepModeStopLPFD,
@@ -545,6 +557,7 @@
         .jitterPpm = 0,
         .driftPpm = 50,
         .maxWakeupTime = 111000ull,
+        .devsAvail = (1 << Stm32sleepDevExti),
         .prepare = sleepClockRtcPrepare,
         .wake = sleepClockRtcWake,
         .userData = (void*)stm32f411SleepModeStopMRFPD,
@@ -558,6 +571,7 @@
         .jitterPpm = 0,
         .driftPpm = 50,
         .maxWakeupTime = 14500ull,
+        .devsAvail = (1 << Stm32sleepDevExti),
         .prepare = sleepClockRtcPrepare,
         .wake = sleepClockRtcWake,
         .userData = (void*)stm32f411SleepModeStopMR,
@@ -571,7 +585,7 @@
         .jitterPpm = 0,
         .driftPpm = 30,
         .maxWakeupTime = 12ull,
-        .devsAvail = (1 << Stm32sleepDevTim2) | (1 << Stm32sleepDevTim4) | (1 << Stm32sleepDevTim5) | (1 << Stm32sleepDevTim9) | (1 << Stm32sleepWakeup) | (1 << Stm32sleepDevSpi2) | (1 << Stm32sleepDevSpi3) | (1 << Stm32sleepDevI2c1),
+        .devsAvail = (1 << Stm32sleepDevTim2) | (1 << Stm32sleepDevTim4) | (1 << Stm32sleepDevTim5) | (1 << Stm32sleepDevTim9) | (1 << Stm32sleepWakeup) | (1 << Stm32sleepDevSpi2) | (1 << Stm32sleepDevSpi3) | (1 << Stm32sleepDevI2c1) | (1 << Stm32sleepDevExti),
         .prepare = sleepClockTmrPrepare,
         .wake = sleepClockTmrWake,
     },
@@ -583,7 +597,7 @@
         .jitterPpm = 0,
         .driftPpm = 0,
         .maxWakeupTime = 0,
-        .devsAvail = (1 << Stm32sleepDevTim2) | (1 << Stm32sleepDevTim4) | (1 << Stm32sleepDevTim5) | (1 << Stm32sleepDevTim9) | (1 << Stm32sleepWakeup) | (1 << Stm32sleepDevSpi2) | (1 << Stm32sleepDevSpi3) | (1 << Stm32sleepDevI2c1),
+        .devsAvail = (1 << Stm32sleepDevTim2) | (1 << Stm32sleepDevTim4) | (1 << Stm32sleepDevTim5) | (1 << Stm32sleepDevTim9) | (1 << Stm32sleepWakeup) | (1 << Stm32sleepDevSpi2) | (1 << Stm32sleepDevSpi3) | (1 << Stm32sleepDevI2c1) | (1 << Stm32sleepDevExti),
         .prepare = sleepClockJustWfiPrepare,
     },
 
@@ -620,7 +634,7 @@
             if (predecrement > length)
                 continue;
 
-            //skip options with too much  drift
+            //skip options with too much drift
             if (sleepClock->driftPpm > mMaxDriftPpm)
                 continue;