Files
koreader/frontend/device/wakeupmgr.lua
NiLuJe 86c35ad066 A host of low power states related tweaks (#9036)
* Disable all non power management related input during suspend. (This prevents wonky touch events from being tripped when closing a sleep cover on an already-in-suspend device, among other things).
* Kobo: Use our WakeupMgr instance, not the class.
* WakupMgr: split `removeTask` in two: 
* `removeTask`, which *only* takes a queue index as input, and only removes a single task. Greatly simplifies the function (i.e., it's just a `table.remove`).
* `removeTasks`, which takes an epoch or a cb ref, and removes *every* task that matches.
* Both of these will also *always* re-schedule the next task (if any) on exit, since we can have multiple WakeupMgr tasks queued, but we can only have a single RTC wake alarm set ;).
* `wakeupAction` now takes a `proximity` argument, which it passes on to its `validateWakeupAlarmByProximity` call, allowing call sites to avoir having to duplicate that call themselves when they want to use a custom proximity window.
* `wakeupAction` now re-schedules the next task (if any) on exit.
* Simplify `Kobo:checkUnexpectedWakeup`, by removing the duplicate `WakerupMgr:validateWakeupAlarmByProximity` call, now that we can pass a proximity window to `WakeuoMgr:wakeupAction`.
* The various network activity timeouts are now halved when autostandby is enabled.
* Autostandby: get rid of the dummy deadline_guard task, as it's no longer necessary since #9009.
* UIManager: The previous change allows us to simplify `getNextTaskTimes` into a simpler `getNextTaskTime` variant, getting rid of a table & a loop.
* ReaderFooter & ReaderHeader: Make sure we only perform a single refresh when exiting standby.
* Kobo: Rewrite sysfs writes to use ANSI C via FFI instead of stdio via Lua, as it obscured some common error cases (e.g., EBUSY on /sys/power/state).
* Kobo: Simplify `suspend`, now that we have sane error handling in sysfs writes.
* Kobo.powerd: Change `isCharging` & `isAuxCharging` behavior to match the behavior of the NTX ioctl (i.e., Charging == Plugged-in). This has the added benefit of making the AutoSuspend checks behave sensibly in the "fully-charged but still plugged in" scenario (because being plugged in is enough to break PM on `!canPowerSaveWhileCharging` devices).
* AutoSuspend: Disable our `AllowStandby` handler when auto standby is disabled, so as to not interfere with other modules using `UIManager:allowStandby` (fix #9038).
* PowerD: Allow platforms to implement `isCharged`, indicating that the battery is full while still plugged in to a power source (battery icon becomes a power plug icon).
* Kobo.powerd: Implement `isCharged`, and kill charging LEDs once battery is full.
* Kindle.powerd: Implement `isCharged` on post-Wario devices. (`isCharging` is still true in that state, as it ought to).
2022-05-01 23:41:08 +02:00

231 lines
6.8 KiB
Lua

--[[--
RTC wakeup interface.
Many devices can schedule hardware wakeups with a real time clock alarm.
On embedded devices this can typically be easily manipulated by the user
through `/sys/class/rtc/rtc0/wakealarm`. Some, like the Kobo Aura H2O,
can only schedule wakeups through ioctl.
See @{ffi.rtc} for implementation details.
See also: <https://linux.die.net/man/4/rtc>.
--]]
local RTC = require("ffi/rtc")
local logger = require("logger")
--[[--
WakeupMgr base class.
@table WakeupMgr
--]]
local WakeupMgr = {
dev_rtc = "/dev/rtc0", -- RTC device
_task_queue = {}, -- Table with epoch at which to schedule the task and the function to be scheduled.
}
--[[--
Initiate a WakeupMgr instance.
@usage
local WakeupMgr = require("device/wakeupmgr")
local wakeup_mgr = WakeupMgr:new{
-- The default is `/dev/rtc0`, but some devices have more than one RTC.
-- You might therefore need to use `/dev/rtc1`, etc.
dev_rtc = "/dev/rtc0",
}
--]]
function WakeupMgr:new(o)
o = o or {}
setmetatable(o, self)
self.__index = self
if o.init then o:init() end
return o
end
--[[--
Add a task to the queue.
@todo Group by type to avoid useless wakeups.
For example, maintenance, sync, and shutdown.
I'm not sure if the distinction between maintenance and sync makes sense
but it's wifi on vs. off.
--]]
function WakeupMgr:addTask(seconds_from_now, callback)
-- Make sure we passed valid input, so that stuff doesn't break in fun and interesting ways (especially in removeTasks).
assert(type(seconds_from_now) == "number", "delay is not a number")
assert(type(callback) == "function", "callback is not a function")
local epoch = RTC:secondsFromNowToEpoch(seconds_from_now)
logger.info("WakeupMgr: scheduling wakeup in", seconds_from_now)
local old_upcoming_task = (self._task_queue[1] or {}).epoch
table.insert(self._task_queue, {
epoch = epoch,
callback = callback,
})
--- @todo Binary insert? This table should be so small that performance doesn't matter.
-- It might be useful to have that available as a utility function regardless.
table.sort(self._task_queue, function(a, b) return a.epoch < b.epoch end)
local new_upcoming_task = self._task_queue[1].epoch
if not old_upcoming_task or (new_upcoming_task < old_upcoming_task) then
self:setWakeupAlarm(new_upcoming_task)
end
end
--[[--
Remove task(s) from queue.
This method removes one or more tasks by either scheduled time or callback.
If any tasks are left on exit, the upcoming one will automatically be scheduled (if necessary).
@int epoch The epoch for when this task is scheduled to wake up.
Normally the preferred method for outside callers.
@int callback A scheduled callback function. Store a reference for use
with anonymous functions.
@treturn bool (true if one or more tasks were removed; false otherwise; nil if the task queue is empty).
--]]
function WakeupMgr:removeTasks(epoch, callback)
if #self._task_queue < 1 then return end
local removed = false
local reschedule = false
for k = #self._task_queue, 1, -1 do
local v = self._task_queue[k]
if epoch == v.epoch or callback == v.callback then
table.remove(self._task_queue, k)
removed = true
-- If we've successfuly pop'ed the upcoming task, we need to schedule the next one (if any) on exit.
if k == 1 then
reschedule = true
end
end
end
-- Schedule the next wakeup action, if any (and if necessary).
if reschedule and self._task_queue[1] then
self:setWakeupAlarm(self._task_queue[1].epoch)
end
return removed
end
--[[--
Variant of @{removeTasks} that will only remove a single task, identified by its task queue index.
@int idx Task queue index. Mainly useful within this module.
@treturn bool (true if a task was removed; false otherwise).
--]]
function WakeupMgr:removeTask(idx)
local removed = false
-- We don't want to keep the pop'ed entry around, we just want to know if we pop'ed something.
if table.remove(self._task_queue, idx) then
removed = true
end
-- Schedule the next wakeup action, if any (and if necessary).
if removed and idx == 1 and self._task_queue[1] then
self:setWakeupAlarm(self._task_queue[1].epoch)
end
return removed
end
--[[--
Execute wakeup action.
This method should be called by the device resume logic in case of a scheduled wakeup.
It checks if the wakeup was scheduled by us using @{validateWakeupAlarmByProximity},
in which case the task is executed.
If necessary, the next upcoming task (if any) is scheduled on exit.
@int proximity Proximity window to the scheduled wakeup (passed to @{validateWakeupAlarmByProximity}).
@treturn bool (true if we were truly woken up by the scheduled wakeup; false otherwise; nil if there weren't any tasks scheduled).
--]]
function WakeupMgr:wakeupAction(proximity)
if #self._task_queue > 0 then
local task = self._task_queue[1]
if self:validateWakeupAlarmByProximity(task.epoch, proximity) then
task.callback()
-- NOTE: removeTask will take care of scheduling the next upcoming task, if necessary.
self:removeTask(1)
return true
end
return false
end
return nil
end
--[[--
Set wakeup alarm.
Simple wrapper for @{ffi.rtc.setWakeupAlarm}.
--]]
function WakeupMgr:setWakeupAlarm(epoch, enabled)
return RTC:setWakeupAlarm(epoch, enabled)
end
--[[--
Unset wakeup alarm.
Simple wrapper for @{ffi.rtc.unsetWakeupAlarm}.
--]]
function WakeupMgr:unsetWakeupAlarm()
return RTC:unsetWakeupAlarm()
end
--[[--
Get wakealarm as set by us.
Simple wrapper for @{ffi.rtc.getWakeupAlarm}.
--]]
function WakeupMgr:getWakeupAlarm()
return RTC:getWakeupAlarm()
end
--[[--
Get RTC wakealarm from system.
Simple wrapper for @{ffi.rtc.getWakeupAlarm}.
--]]
function WakeupMgr:getWakeupAlarmSys()
return RTC:getWakeupAlarmSys()
end
--[[--
Validate wakeup alarm.
Checks if we set the alarm.
Simple wrapper for @{ffi.rtc.validateWakeupAlarmByProximity}.
--]]
function WakeupMgr:validateWakeupAlarmByProximity(task_alarm_epoch, proximity)
return RTC:validateWakeupAlarmByProximity(task_alarm_epoch, proximity)
end
--[[--
Check if a wakeup is scheduled.
Simple wrapper for @{ffi.rtc.isWakeupAlarmScheduled}.
--]]
function WakeupMgr:isWakeupAlarmScheduled()
local wakeup_scheduled = RTC:isWakeupAlarmScheduled()
if wakeup_scheduled then
-- NOTE: This can't return nil given that we're behind an isWakeupAlarmScheduled check.
local alarm = RTC:getWakeupAlarmEpoch()
logger.dbg("WakeupMgr:isWakeupAlarmScheduled: An alarm is scheduled for " .. alarm .. os.date(" (%F %T %z)", alarm))
else
logger.dbg("WakeupMgr:isWakeupAlarmScheduled: No alarm is currently scheduled.")
end
return wakeup_scheduled
end
return WakeupMgr