• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1.. _seed-0133:
2
3==================
40133: pw_wakelock
5==================
6.. seed::
7   :number: 133
8   :name: pw_wakelock
9   :status: Rejected
10   :proposal_date: 2025-01-31
11   :cl: 263535
12   :authors: Jiaming (Charlie) Wang, Ben Lawson
13   :facilitator: Unassigned
14
15-------------------
16Rejection rationale
17-------------------
18Wake locks are an anti-pattern in software development, especially so in
19embedded systems. MCUs typically do not need wake locks because the scheduler
20does not suspend the system in the middle of tasks like Android does. MCUs also
21suspend/resume faster than more complex systems, so the benefit of keeping the
22system awake when no task is running is minimal or non-existent. Finally, wake
23locks tend to be viral once introduced due to all tasks seemingly needing to be
24wrapped in a wake lock, so we are wary of promoting them.
25
26For now, we think it is more appropriate for subsystems like
27``pw_bluetooth_sapphire`` to provide a custom API for wake lock logic that is
28likely to only be used when the subsystem is running on a big OS like Android
29or Fuchsia. Once we have some more concrete example code, we may reconsider
30power management APIs in Pigweed.
31
32-------
33Summary
34-------
35This SEED proposes the creation of a new ``pw_wakelock`` module that provides a
36portable interface for creating, holding, and destroying wakelocks. A wakelock
37is a handle that, when active, prevents the system from going to a low power
38state.
39
40----------
41Motivation
42----------
43Bluetooth is a major wakeup source on embedded projects and systems typically
44need to stay awake while Bluetooth procedures are active. While this can be done
45with a crude timeout upon packet activity, tight integration with the Bluetooth stack
46enables more effective power management. Accordingly, ``pw_bluetooth_sapphire``
47needs to integrate with the power framework on Fuchsia and future customer
48platforms. This needs to be done in a portable way that enables consistent
49power management behavior across platforms.
50
51Beyond the Sapphire module, wakelocks are a common requirement in battery
52powered embedded projects so we expect users to find a wakelock abstraction
53useful. Additionally, developers need a way to write unit tests for code that
54uses wakelocks without invoking real power management APIs. This indicates the
55need for an abstraction layer.
56
57------------
58Requirements
59------------
60- pw_wakelock should provide an API for creating, holding, and releasing a
61  wakelock.
62- The wakelock API should support unit testing.
63- The wakelock API should not overfit to any existing power management API.
64- The wakelock API should at least be compatible with Fuchsia, Linux, and
65  Zephyr.
66- pw_wakelock backends should be able to adopt an existing platform-specific
67  wakelock. This enables better integration with the rest of the system.
68
69----------------------------------------------
70Investigation into operating system power APIs
71----------------------------------------------
72
73User space power management in Linux
74====================================
75
76Wakelock
77--------
78Wakelock allows processes in user space to prevent CPUs from going into low
79power state by writing a lock name to a file descriptor. The wakelock
80implementation was initially shipped with Android without first landing in the
81Linux mainline, and upstreaming it was `controversial
82<https://lwn.net/Articles/318611/>`_.
83
84Key `APIs <https://lore.kernel.org/lkml/201202220037.53776.rjw@sisk.pl/>`_ from
85userspace are as follows:
86
87Acquire a wakelock
88^^^^^^^^^^^^^^^^^^
89Write wakelock name to ``/sys/power/wake_lock``.
90
91Acquire a wakelock with timeout
92^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
93Write wakelock name along with a timeout (in nanoseconds) to
94``/sys/power/wake_lock``.
95
96Release a wakelock
97^^^^^^^^^^^^^^^^^^
98Write wakelock name to ``/sys/power/wake_unlock``.
99
100Power management in Fuchsia
101=====================================
102See `Fuchsia power framework
103<https://fuchsia.dev/fuchsia-src/concepts/power/intro>`_ for detailed
104information on Fuchsia power concepts.
105
106Power element and power level
107-----------------------------
108Power Element is the abstraction that represents either a hardware device or a
109higher level software component. Power elements are composed of discrete power
110levels, which can be a range of performance levels. Managed elements have their
111power level dictated by the Power Broker. Unmanaged elements cannot have
112dependencies and simply report their current power level.
113
114
115Dependencies in power elements
116------------------------------
117The power level of one power element can have a dependency with the power level
118in another power element. Fulfillment policies for power level dependencies are
119strong fulfillment and weak fulfillment. Strong fulfillment means the power
120level is activated, while weak fulfillment does not control the power level. A
121dependency can be strongly-fulfilled only if its required element is managed,
122whereas dependencies on managed and unmanaged elements may be weakly-fulfilled.
123
124Leases
125------
126A component can take a lease at a certain power level in a power element owned
127by the component. A lease is a grant for a managed element to have all of its
128dependencies for a given power level fulfilled. A component can also acquire a
129lease without explicitly configuring a power element or its dependencies. The
130`` fuchsia.power.system/ActivityGovernor.AcquireWakeLease`` API returns a
131``LeaseToken`` that prevents system suspension. The lease is transferable
132across components and drivers using FIDL messages. `Here
133<https://cs.opensource.google/fuchsia/fuchsia/+/main:examples/power/wake_lease/cpp>`_
134is the latest example code for taking a wake lease directly from the System
135Activity Governor. The timed power lease is not available as of now and
136requires a custom implementation.
137
138Power management in Zephyr
139====================================
140Power State Locks in Zephyr provide a mechanism to explicitly prevent the
141system from transitioning into certain power states. This functionality is
142useful when a component needs to ensure the system remains in a specific power
143state, effectively acting similar to a wakelock in Linux.
144
145Power API
146---------
147Zephyr provides System Power Management which is responsible for controlling
148the overall power state of the CPU or the entire System-on-Chip (SoC). This
149involves transitioning the system between different power states, each
150characterized by varying levels of power consumption and wakeup latency.
151
152Processes / Applications can utilize the following `Power Management Policy
153APIs
154<https://docs.zephyrproject.org/latest/doxygen/html/group__subsys__pm__sys__policy.html#gabbb379f8572f164addafe93ad3468f3d>`_
155to prevent the SoC going into certain power states, to keep the CPU along with
156certain devices in the right power level.
157
158pm_policy_state_lock_get()
159^^^^^^^^^^^^^^^^^^^^^^^^^^
160``void pm_policy_state_lock_get(enum pm_state state, uint8_t substate_id)``
161
162Increase a power state lock counter.
163
164A power state will not be allowed on the first call of
165``pm_policy_state_lock_get()``. Subsequent calls will just increase a reference
166count, thus meaning this API can be safely used concurrently. A state will be
167allowed again after ``pm_policy_state_lock_put()`` is called as many times as
168``pm_policy_state_lock_get()``.
169
170Note that the PM_STATE_ACTIVE state is always allowed, so calling this API with
171PM_STATE_ACTIVE will have no effect.
172
173pm_policy_state_lock_is_active()
174^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
175``bool pm_policy_state_lock_is_active(enum pm_state state, uint8_t substate_id)``
176
177Check if a power state lock is active (not allowed).
178
179pm_policy_state_lock_put()
180^^^^^^^^^^^^^^^^^^^^^^^^^^
181``void pm_policy_state_lock_put(enum pm_state state, uint8_t substate_id)``
182
183Decrease a power state lock counter.
184
185Power states in Zephyr
186----------------------
187Zephyr supports the following power states: active, runtime idle, suspend to
188idle, standby, suspend to ram, suspend to disk, and soft off. See the
189`documentation
190<https://docs.zephyrproject.org/apidoc/latest/group__subsys__pm__states.html#ga20e2f5ea9027a3653e5b9cc5aa1e21d5>`_
191for detailed descriptions.
192
193------
194Design
195------
196We aim to minimize the API surface to maximize portability. For example, we are
197not adding an API with timeout parameters, power levels, or diagnostic features
198(other than names). Tokenization is also optional.
199
200The ``WakelockProvider`` interface is implemented by backends and has a single
201method for vending wakelocks. A ``Wakelock`` is a simple movable class that
202calls a function on destruction. This function can be used by backends to
203unlock the wakelock.
204
205A helper macro ``PW_WAKELOCK_ACQUIRE`` is provided that optionally tokenizes
206the wakelock name parameter depending on the configuration.
207
208Testability is also a primary goal of this design. By using the
209``WakelockProvider`` interface, a fake can be dependency injected into the code
210under test.
211
212API
213===
214.. code-block:: c++
215
216   #define PW_WAKELOCK_ACQUIRE(provider, name) \
217     provider.Acquire(PW_WAKELOCK_TOKEN_EXPR(name))
218
219   class WakelockProvider {
220   public:
221     virtual ~WakelockProvider() = default;
222     virtual Result<Wakelock> Acquire(PW_WAKELOCK_TOKEN_TYPE name) = 0;
223   };
224
225   class Wakelock final {
226   public:
227     Wakelock();
228     Wakelock(pw::Function<void()>);
229     Wakelock(Wakelock&&) = default;
230     Wakelock& operator=(Wakelock&&) = default;
231     Wakelock(const Wakelock&) = delete;
232     Wakelock& operator=(const Wakelock&) = delete;
233
234     ~Wakelock() {
235       if (unlock_fn_) {
236         unlock_fn_();
237       }
238     }
239
240   private:
241     pw::Function<void()> unlock_fn_ = nullptr;
242   };
243
244Example usage
245=============
246.. code-block:: c++
247
248   Status MyFunction(WakelockProvider& provider) {
249     PW_TRY_ASSIGN(Wakelock lock, PW_WAKELOCK_ACQUIRE(provider, "hello_world"));
250     PW_LOG_INFO("Hello World");
251   }
252
253   int main() {
254      LinuxWakelockProvider provider;
255      if (!MyFunction(provider).ok()) {
256        return 1;
257      }
258      return 0;
259   }
260
261Testing
262=======
263A fake ``WakelockProvider`` implementation will be created with the following
264API:
265
266.. code-block:: c++
267
268   class FakeWakelockProvider final : WakelockProvider {
269   public:
270     Result<Wakelock> Acquire(PW_WAKELOCK_TOKEN_TYPE name) override {
271       if (!status_.ok())  {
272         return status_;
273       }
274       wakelock_count_++;
275       return Wakelock([this](){ wakelock_count_--; });
276     }
277
278     uint16_t wakelock_count() const { return wakelock_count_; }
279
280     void set_acquire_status(Status status) { status_ = status; }
281
282   private:
283     uint16_t  wakelock_count_ = 0;
284     Status status_ = PW_STATUS_OK;
285   };
286
287Tokenization
288============
289The tokenized configuration will set ``PW_WAKELOCK_TOKEN_TYPE`` to
290``pw_tokenizer_Token`` and ``PW_WAKELOCK_TOKEN_EXPR`` to
291``PW_TOKENIZE_STRING_EXPR``. By default, these macros will be set to ``const
292char*`` and no-op, respectively.
293
294Backends
295========
296
297No-op
298-----
299There is no reasonable basic backend possible, but we can provide a default
300no-op backend that always succeeds and has an empty implementation.
301
302Linux
303-----
304
305.. code-block:: c++
306
307   #define WAKE_LOCK_PATH "/sys/power/wake_lock"
308   #define WAKE_UNLOCK_PATH "/sys/power/wake_unlock"
309
310   class LinuxWakelockProvider final {
311   public:
312     // Note: if name is tokenized, we can convert the token into a base64
313     // string.
314     Result<Wakelock> Acquire(const char* name) override {
315       int wake_lock_fd = open(WAKE_LOCK_PATH, O_WRONLY|O_APPEND);
316       if (wake_lock_fd < 0) {
317         PW_LOG_WARN("Unable to open %s, err:%s",
318           WAKE_LOCK_PATH, std::strerror(errno));
319         if (errno == ENOENT) {
320           PW_LOG_WARN("No wake lock support");
321         }
322         return Status::Unavailable();
323       }
324
325       // Acquire the wakelock
326       int ret = write(wake_lock_fd, name, strlen(name));
327       close(wake_lock_fd);
328       if (ret < 0) {
329         PW_LOG_ERROR("Failed to acquire wakelock %d %s", ret,
330           strerror(errno));
331         return Status::Unavailable();
332       }
333
334       return Wakelock([name](){ LinuxWakelockProvider::Release(name); });
335     }
336
337     static void Release(const char* name) {
338         int wake_unlock_fd = open(WAKE_UNLOCK_PATH, O_WRONLY|O_APPEND);
339         if (wake_unlock_fd < 0) {
340           PW_LOG_WARN("Unable to open %s, err:%s",
341               WAKE_UNLOCK_PATH, std::strerror(errno));
342           return Status::Unavailable();
343         }
344         // Release the wakelock
345         int ret = write(wake_unlock_fd, name, strlen(name));
346         close(wake_unlock_fd);
347         if (ret < 0) {
348           PW_LOG_ERROR("Failed to release wakelock %d %s", ret, strerror(errno));
349           return;
350         }
351     }
352   };
353
354Fuchsia
355=======
356The Fuchsia backend will be initialized with the client end of
357`ActivityGovernor
358<https://cs.opensource.google/fuchsia/fuchsia/+/main:sdk/fidl/fuchsia.power.system/system.fidl;l=206;drc=89a9ea0b8a5fcb5258a8fef4d054c7afe47ef714>`_.
359
360``FuchsiaWakelockProvider::Acquire`` will be implemented by making a
361synchronous call to `ActivityGovernor.AcquireWakeLease()
362<https://cs.opensource.google/fuchsia/fuchsia/+/main:sdk/fidl/fuchsia.power.system/system.fidl;l=244;drc=89a9ea0b8a5fcb5258a8fef4d054c7afe47ef714>`_
363for the first wakelock to obtain a ``LeaseToken`` and setting a lock counter to
3641. The lock counter will be incremented for additional ``Acquire`` calls. The
365lock counter will be decremented when a ``Wakelock`` is destroyed. When the
366number of active ``Wakelock`` objects is 0, the ``LeaseToken`` will be
367destroyed. A non-portable method ``FuchsiaWakelockProvider::Adopt`` will
368support creating a ``Wakelock`` from an existing ``LeaseToken``. This will be
369used in FIDL clients and servers before passing a received ``Wakelock`` to
370portable code.
371
372Zephyr
373======
374``ZephyrWakelockProvider::Acquire`` will call ``pm_policy_state_lock_get()``
375with a configurable power state. The default implementation should keep the
376system in  ``PM_STATE_ACTIVE``. On destruction of the ``Wakelock``,
377``pm_policy_state_lock_put()`` will be called.
378
379------------
380Alternatives
381------------
382
383NativeWakelock
384==============
385We originally considered the Class/NativeClass pattern, but is difficult to use
386with unit tests.
387
388.. code-block:: c++
389
390   /// Acquire a wakelock object. Returns a Result<Wakelock>.
391   /// PW_HANDLE_WAKELOCK_ACQUIRE is implemented by the backend.
392   #define PW_WAKELOCK_ACQUIRE(name) \
393           PW_HANDLE_WAKELOCK_ACQUIRE(name, __FILE__, __LINE__)
394
395   // Wakelock interface (using the Class/NativeClass pattern)
396   class Wakelock final {
397   public:
398       Wakelock() : native_wakelock_(*this) {}
399       ~Wakelock();
400       Wakelock(Wakelock&& other);
401       Wakelock& operator=(Wakelock&& other);
402
403       /// Returns the inner `NativeWakelock` containing backend-specific state.
404       /// Only non-portable code should call these methods.
405       backend::NativeWakelock& native_wakelock() { return native_wakelock_; }
406       const backend::NativeWakelock& native_wakelock() const { return native_wakelock_; }
407
408   private:
409       backend::NativeWakelock native_wakelock_;
410   };
411
412Custom wakelock API in pw_bluetooth_sapphire
413============================================
414If this proposal is not accepted, we will need to make a custom wakelock
415abstraction layer inside ``pw_bluetooth_sapphire`` that will likely look similar
416to this proposal.
417