11 KiB
Power Management
Overview
Power management (PM) 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.cc
, 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.cc
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
overridesBTA_DM_PM_NO_ACTION
and if selected as the action thatbta_dm_pm_set_mode
will take, the connection will be removed frombta_dm_conn_srvcs
and no longer be considered for power management decisions.BTA_DM_PM_SNIFF
throughBTA_DM_PM_SNIFF6
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 SNIFF6. Params are defined inbta_dm_ssr_spec
.BTA_DM_PM_ACTIVE
is full-on power.BTA_DM_PM_RETRY
has the same effect asBTA_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.cc
'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.cc
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.cc
file, and only for reinitialization.
Event flow
Events fired from SYS
- An event is fired from one of the methods mentioned above in
bta/sys/bta_sys_conn.cc
- 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 specifiesBTA_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.
- The power mode config is looked up in the
bta_dm_pm_cback
checks to see if thebta_dm_ssr_spec
specifies SSR adjustments are to be made, and if so,bta_dm_pm_ssr
is called with the peerBD_ADDR
.bta_dm_pm_ssr
iterates the managed services array to find all connected services for the givenBD_ADDR
, then looks up the ssr values from thebta_dm_ssr_spec
tables, looking for the smallest max latency to use.bta_dm_pm_ssr
callsBTM_SetSsrParams
to actually send along the SSR params to the bluetooth chip.
bta_dm_pm_cback
callsbta_dm_pm_set_mode
with the peer address and thetimed_out
parameter set tofalse
.- For each managed connection,
bta_dm_pm_set_mode
grabs both actions specified for the profile in thebta_dm_pm_spec
tables. If the first power management action didn't timeout (or was never attempted, according to thetBTA_DM_PEER_DEVICE
pm_mode_failed
andpm_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 toBTA_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
orBTA_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 callsBTM_ReadPowerMode
andBTM_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, thenbta_dm_pm_sniff
is called, which makes various calls to the local controller,BTM_ReadRemoteFeatures
andBTM_SetPowerMode
to ensure SNIFF mode is enabled. - If the action chosen is
BTA_DM_PM_ACTIVE
, a call tobta_dm_pm_active
is made, which callsBTM_SetPowerMode
to set the link into ACTIVE mode.
- For each managed connection,
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:
- An event is fired from a callback in BTM to
bta_dm_pm_btm_cback
. bta_dm_pm_btm_cback
packages up the given parameters into atBTA_DM_PM_BTM_STATUS
struct and posts it to the BTA workqueue thread viabta_sys_sendmsg
, with the event header set toBTA_DM_PM_BTM_STATUS_EVT
.- 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 inbta_dm_pm_set_mode
, clears some status bits in the peer device structure, and then calls back intobta_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, callsbta_dm_pm_ssr
to re-send SSR params, stops all timers for the device, and then re-callsbta_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 that's non-zero and a PARK or SNIFF mode change was attempted,
- If the status is
BTM_PM_STS_PARK
orBTM_PM_STS_HOLD
, saves the previous low power mode in the peer device'sprev_low
field. - If the status is
BTM_PM_STS_SSR
, simply clears or sets the deviceinfo
field'sBTA_DM_DI_USE_SSR
bit, depending on the value oftBTA_DM_MSG.value
, which determines if the device can handle SSR. - If the status is
BTM_PM_STS_SNIFF
and the info field has theBTA_DM_DI_SET_SNIFF
bit set, thenBTA_DM_DI_INT_SNIFF
is set, otherwiseBTA_DM_DI_ACP_SNIFF
is set. - If
BTA_PM_STS_ERROR
, theBTA_DM_DI_SET_SNIFF
bit is cleared in the device info field.
- The message
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
:
- A timer expires, and calls
bta_dm_pm_timer_cback
. bta_dm_pm_timer_cback
clears the use flag on the timer that fired, and sends off an event to the BTA workqueue thread.- The event eventually fires off a call to
bta_dm_pm_timer
, which just callsbta_dm_pm_set_mode
with timeout set toTRUE
.