From 4c33920131301cc3452996f4d6285db381fd7d69 Mon Sep 17 00:00:00 2001 From: "June R. Tate-Gans" Date: Thu, 9 Oct 2014 14:16:25 -0700 Subject: [PATCH] Adding some documentation for the power management flow. --- doc/power_management.md | 202 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 202 insertions(+) create mode 100644 doc/power_management.md diff --git a/doc/power_management.md b/doc/power_management.md new file mode 100644 index 000000000..ae90d2975 --- /dev/null +++ b/doc/power_management.md @@ -0,0 +1,202 @@ +## Power Management + +### Overview + +Power management (PM) in bluedroid is an event-driven state machine, tickled by +various `bta/sys` events via a callback. The actual state switching calls are +handled by the BTM HCI interfacing code, with results being posted back to the +PM code via the BTA workqueue thread. + +Power states are managed per-device, per-profile, so every incoming event +includes a profile ID, app ID, and a `BD_ADDR`. + +The events fired to drive the state machine at the time of this writing are: + + - `BTA_SYS_CONN_OPEN` + - `BTA_SYS_CONN_CLOSE` + - `BTA_SYS_CONN_IDLE` + - `BTA_SYS_CONN_BUSY` + - `BTA_SYS_APP_OPEN` + - `BTA_SYS_APP_CLOSE` + - `BTA_SYS_SCO_OPEN` + - `BTA_SYS_SCO_CLOSE` + +Each of these correspond to a function name in `bta/sys/bta_sys_conn.c`, which +are called by each profile definition in `bta/$PROFILE`. + +The PM code makes calls into the BTM module to set various power +states. Responses are handled in an asynchronous fashion, primarily via the +callbacks `bta_dm_pm_cback` and `bta_dm_pm_timer_cback`. Responses are handled +through the BTA workqueue thread and the `bta_dm_pm_btm_status` function. Since +we might possibly get into a bad state where we never hear back from the +controller, timers are used to post messages to the BTA workqueue thread as +well, which filters down through the same status function. + +Overall power states are managed *per device*, not per connection, but the power +policy is determined by the greatest allowable power action defined across all +currently known connections to a given device. Thus, if RFCOMM specifies that +it's willing to go to into SNIFF and specifies that as an action, and say, a PAN +connection is up which specifies it is willing to go into SNIFF, but its action +states it wants ACTIVE, the power management code will change to ACTIVE. + +### Power management tables + +The tables that determine which power levels are acceptable for which profiles +and what actions to take for the above events are defined in the +`bta/dm/bta_dm_cfg.c` file, as `bta_dm_pm_cfg`, `bta_dm_pm_spec`, and +`bta_dm_ssr_spec`. + +During a lookup attempt, the code iterates over the `bta_dm_pm_cfg` array, +looking for a match between the profile and app IDs. When it finds one, it uses +the `spec_idx` field to index into `bta_dm_pm_spec` array to determine which +power modes are acceptable and what actions to take for each event. + +The action constants are defined in `bta_api.h` and are defined as a series of +hex bitfields. The actual actions taken are determined by the +`bta_dm_pm_set_mode` function, but a few of the actions listed deserve some +additional description: + + - `BTA_DM_PM_NO_ACTION` is effectively a no-op and has a value of zero, so any + other profile will override this. + - `BTA_DM_PM_NO_PREF` overrides `BTA_DM_PM_NO_ACTION` and if selected as the + action that `bta_dm_pm_set_mode` will take, the connection will be removed + from `bta_dm_conn_srvcs` and no longer be considered for power management + decisions. + - `BTA_DM_PM_SNIFF` through `BTA_DM_PM_SNIFF4` are special, in that each + level specifies a set of parameters for the SNIFF mode which relate to the + min and max intervals, the number of attempts and the timeout. The overall + action is still the same, however -- SNIFF mode is attempted. There are + definitions available up to SNIFF7, but actual SSR values are only defined + up to SNIFF4. Params are defined in `bta_dm_ssr_spec`. + - `BTA_DM_PM_ACTIVE` is full-on power. + - `BTA_DM_PM_RETRY` has the same effect as `BTA_DM_PM_NO_ACTION`, except a + timeout is possible to be set, which effectively allows a power operation to + be "retried". + +### Initialization + +`bta_dm_pm.c`'s `bta_dm_init_pm` function calls out to register +`bta_dm_pm_cback` with the bta sys module for incoming power management events, +and also registers `bta_dm_pm_btm_cback` with the btm module to handle responses +and timeouts of HCI requests (via `bta_dm_pm_btm_status`). + +At this point, the power managment code is basically done until the first set of +events come in through `bta_dm_pm_cback`. + +Throughout the `bta_dm_pm.c` file, connections whose power management states are +managed are tracked in a global array called `bta_dm_conn_srvcs`. Unfortunately, +while this variable is declared as an extern in the `bta_dm_int.h` file, it only +seems to be used in the `bta_dm_act.c` file, and only for reinitialization. + +### Event flow + +#### Events fired from SYS + + 1. An event is fired from one of the methods mentioned above in + `bta/sys/bta_sys_conn.c` + 2. The `bta_dm_pm_cback` function is called. + - The power mode config is looked up in the `bta_dm_pm_cfg` table. If none + are found for the given profile ID and app ID, the function simply + returns with no action taken. + - If any timers were set for the given `BD_ADDR`, they are stopped. + - The SSR params for the CONN_OPEN event are looked up. + - The power spec state table (`bta_dm_pm_spec`) is checked to see if + there's no action to be performed (`BTA_DM_PM_NO_ACTION`), and if so, + returns with no action taken. + - `bta_dm_conn_srvcs` is consulted to ensure there's an entry for this + connection if it's supposed to be managed according to the power spec + state tables. If the spec specifies `BTA_DM_PM_NO_PREF`, then any + existing entry in this list is removed, otherwise one is added/updated + with the state given to the function. + 3. `bta_dm_pm_cback` checks to see if the `bta_dm_ssr_spec` specifies SSR + adjustments are to be made, and if so, `bta_dm_pm_ssr` is called with the + peer `BD_ADDR`. + - `bta_dm_pm_ssr` iterates the managed services array to find all connected + services for the given `BD_ADDR`, then looks up the ssr values from the + `bta_dm_ssr_spec` tables, looking for the smallest max latency to use. + - `bta_dm_pm_ssr` calls `BTM_SetSsrParams` to actually send along the SSR + params to the bluetooth chip. + 4. `bta_dm_pm_cback` calls `bta_dm_pm_set_mode` with the peer address and the + `timed_out` parameter set to `false`. + - For each managed connection, `bta_dm_pm_set_mode` grabs + both actions specified for the profile in the `bta_dm_pm_spec` tables. If + the first power management action didn't timeout (or was never attempted, + according to the `tBTA_DM_PEER_DEVICE` `pm_mode_failed` and + `pm_mode_attempted` fields), its timeout and mode are used. Otherwise, + the same check is done against the second action and it is used + instead. If both actions have been attempted, then the action is set to + `BTA_DM_PM_NO_ACTION`. Only the highest power mode action is chosen from + all connected profiles. + - If the chosen action is `BTA_DM_PM_PARK` or `BTA_DM_PM_SNIFF` but the + profile doesn't allow it, this function takes no action. + - If a timeout is specified in the power spec table, then an unused timer + in `bta_dm_cb.pm_timer` is started. + - If the action chosen is `BTA_DM_PM_PARK`, `bta_dm_pm_park` is called, + which calls `BTM_ReadPowerMode` and `BTM_SetPowerMode` to make an HCI + request to enable PARK for the given peer and connection. + - If the action chosen is `BTA_DM_PM_SNIFF`, the peer device's link policy + is checked to see if it's allowed. If so, then `bta_dm_pm_sniff` is + called, which makes various calls to `BTM_ReadLocalFeatures`, + `BTM_ReadRemoteFeatures` and `BTM_SetPowerMode` to ensure SNIFF mode is + enabled. + - If the action chosen is `BTA_DM_PM_ACTIVE`, a call to `bta_dm_pm_active` + is made, which calls `BTM_SetPowerMode` to set the link into ACTIVE + mode. + +At this point, if one of the timers in `bta_dm_cb.pm_timer` times out, a call is +made through the BTA workqueue thread to `bta_dm_pm_btm_cback`, which then +triggers `bta_dm_pm_btm_status`, with the timeout field set to TRUE. HCI +responses are also fired as messages through the BTA workqueue thread, which are +handled again, through `bta_dm_pm_btm_status`. + +#### Events fired through BTM + +Essentially these messages eventually go through the same functions as events +fired from the SYS side of things, except from the initial path they take: + + 1. An event is fired from a callback in BTM to `bta_dm_pm_btm_cback`. + 2. `bta_dm_pm_btm_cback` packages up the given parameters into a + `tBTA_DM_PM_BTM_STATUS` struct and posts it to the BTA workqueue thread via + `bta_sys_sendmsg`, with the event header set to + `BTA_DM_PM_BTM_STATUS_EVT`. + 3. This is eventually routed to the `bta_dm_pm_btm_status` function. + **Determine if this is running on the workqueue thread or not** + - The message `status` passed in is actually the current status of the + device. + - If the status is `BTM_PM_STS_ACTIVE` (still in the ACTIVE power mode), + checks the HCI status code: + - If that's non-zero and a PARK or SNIFF mode change was attempted, + `bta_dm_pm_btm_status` stops any timers started for the device in + `bta_dm_pm_set_mode`, clears some status bits in the peer device + structure, and then calls back into `bta_dm_pm_set_mode` with the peer + device address and timeout set to FALSE. + - If the status is zero, and if the peer device `tBTA_DM_PEER_DEVICE` + `prev_low` field is set, calls `bta_dm_pm_ssr` to re-send SSR params, + stops all timers for the device, and then re-calls `bta_dm_pm_set_mode` + with timeout set to FALSE to re-attempt with a second action (if the + previous PARK or SNIFF failed, otherwise it'll re-attempt the first + action). + - If the status is `BTM_PM_STS_PARK` or `BTM_PM_STS_HOLD`, saves the + previous low power mode in the peer device's `prev_low` field. + - If the status is `BTM_PM_STS_SSR`, simply clears or sets the device + `info` field's `BTA_DM_DI_USE_SSR` bit, depending on the value of + `tBTA_DM_MSG.value`, which determines if the device can handle SSR. + - If the status is `BTM_PM_STS_SNIFF` and the info field has the + `BTA_DM_DI_SET_SNIFF` bit set, then `BTA_DM_DI_INT_SNIFF` is set, + otherwise `BTA_DM_DI_ACP_SNIFF` is set. + - If `BTA_PM_STS_ERROR`, the `BTA_DM_DI_SET_SNIFF` bit is cleared in the + device info field. + +At this point, either the method simply returns, or has called back into +`bta_dm_pm_set_mode`, in which case the usual flow takes over. + +#### Events fired from timers + +Timers are used exclusively for handling HCI command timeouts, and filter +through to a call to `bta_dm_pm_set_mode`: + + 1. A timer expires, and calls `bta_dm_pm_timer_cback`. + 2. `bta_dm_pm_timer_cback` clears the use flag on the timer that fired, and + sends off an event to the BTA workqueue thread. + 3. The event eventually fires off a call to `bta_dm_pm_timer`, which just + calls `bta_dm_pm_set_mode` with timeout set to `TRUE`. -- 2.11.0