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