Skip to content

drivers: wifi: siwx91x: Add power-save support #86537

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

ArunmaniAlagarsamy2710
Copy link
Collaborator

@ArunmaniAlagarsamy2710 ArunmaniAlagarsamy2710 commented Mar 3, 2025

Added power-save mode support to optimize station wake-up timing
and reduce power consumption. The device currently supports
only legacy power-save mode.

Implemented power-save features:

  • ps ON/OFF : By default, the device operates in
    fast PSP mode.
  • ps_listen_interval : Moves to DTIM wakeup if set to zero.
  • ps_exit_strategy : Updates mode if enabled; otherwise, follows
    the configured exit strategy when power-save
    is enabled.
  • ps_wakeup_mode : Configures the wake-up behavior.
  • ps_timeout : Defines the timeout duration for power-save mode.

@ArunmaniAlagarsamy2710 ArunmaniAlagarsamy2710 changed the title wifi: driver: siwx91x: Add power-save support drivers: wifi: siwx91x: Add power-save support Mar 26, 2025
@ArunmaniAlagarsamy2710 ArunmaniAlagarsamy2710 force-pushed the dev/power_save branch 4 times, most recently from c6f7a2e to bbd7677 Compare March 26, 2025 12:57
@ArunmaniAlagarsamy2710 ArunmaniAlagarsamy2710 marked this pull request as ready for review March 26, 2025 13:01
@github-actions github-actions bot added the area: Wi-Fi Wi-Fi label Mar 26, 2025
sl_ps_profile.profile = req_ps_mode;
}
} else {
return -EAGAIN;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did you mean -EALREADY?

}
} else if (params->enabled == WIFI_PS_DISABLED) {
if (sl_ps_profile.profile == HIGH_PERFORMANCE) {
return -EAGAIN;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did you mean -EALREADY?

@jerome-pouiller jerome-pouiller self-requested a review March 26, 2025 13:37
@ArunmaniAlagarsamy2710 ArunmaniAlagarsamy2710 force-pushed the dev/power_save branch 3 times, most recently from a95a68a to 51c0d0f Compare March 28, 2025 07:32
@github-actions github-actions bot added the platform: Silabs Silicon Labs label Mar 28, 2025
@github-actions github-actions bot requested a review from jhedberg March 28, 2025 07:32

config WIFI_SIWX91X_ENHANCED_MAX_PSP
bool "Enhanced Max PSP Support"
default n
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

n is the "default default", so you can just remove this line

Comment on lines 767 to 782
case WIFI_PS_PARAM_STATE:
if (params->enabled == WIFI_PS_ENABLED) {
if (sl_ps_profile.profile == HIGH_PERFORMANCE) {
if (siwx91x_ps_cfg.req_ps_mode == HIGH_PERFORMANCE) {
sl_ps_profile.profile = ASSOCIATED_POWER_SAVE_LOW_LATENCY;
} else {
sl_ps_profile.profile = siwx91x_ps_cfg.req_ps_mode;
}
} else {
return -EALREADY;
}

if (sl_ps_profile.dtim_aligned_type == 0) {
beacon_interval = siwx91x_get_beacon_interval(dev);
if (beacon_interval < 0) {
params->fail_reason = WIFI_PS_PARAM_FAIL_DEVICE_NOT_CONNECTED;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The compliance check is failing because of the long (> 100) lines. To me it looks like this function could use some refactoring into smaller helper functions, so that you don't need to do so deep indentations.

return -EINVAL;
}

get_wifi_current_performance_profile(&sl_ps_profile);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a private function. Use sl_wifi_get_performance_profile() instead.

if (status != SL_STATUS_OK) {
params->fail_reason = WIFI_PS_PARAM_FAIL_CMD_EXEC_FAIL;
LOG_ERR("Failed to set power save profile: 0x%x", status);
return -EINVAL;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EINVAL is used when the caller didn't provide the right arguments. Here, I rather believe the error is unexpected. I would rather return EIO.

help
Enable this option to allow the device to configure
Enhanced Maximum Power Save (PSP) support during
device initialization.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Enhanced Maximum Power Save is not standard feature. It makes sense to explain what are the drawbacks and the advantage of this option.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This option only makes sense if WIFI_SILABS_SIWX91X is selected.

/* HIGH_PERFORMANCE by default */
sl_si91x_performance_profile_t req_ps_mode;
uint16_t listen_interval;
} siwx91x_ps_cfg;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid global variable. These variables should be a part of struct siwx91x_dev.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW, avoid to declare struct/variables in the middle of the code.

@@ -709,6 +712,270 @@ static int siwx91x_stats(const struct device *dev, struct net_stats_wifi *stats)
}
#endif

struct siwx91x_ps_profile {
/* HIGH_PERFORMANCE by default */
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can fix the code rather than require a comment:

[...] siwx91x_ps_cfg = {
    .req_ps_mode = HIGH_PERFORMANCE,
}

}
} else {
return -EALREADY;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have to admit I don't understand what the above lines are doing. Maybe a comment could help.

break;
case WIFI_PS_DISABLED:
if (sl_ps_profile->profile == HIGH_PERFORMANCE) {
return -EALREADY;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it make sense to report an error if the profile is already selected. EALREADY exists for cases the function won't do what the user expect if it is called twice (eg. wifi_connect()).

return -EINVAL;
}

sl_ps_profile.monitor_interval = (uint16_t)params->timeout_ms;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the cast required?

if (beacon_interval > 0) {
/* converts MSEC to beacon unit */
config->ps_params.listen_interval =
(sl_ps_profile.listen_interval / beacon_interval);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Parentheses are useless.


if (sl_ps_profile.dtim_aligned_type == 1) {
config->ps_params.wakeup_mode = WIFI_PS_WAKEUP_MODE_DTIM;
} else if (sl_ps_profile.dtim_aligned_type == 0) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you consider dtim_aligned_type is a boolean, test it as a boolean:

	if (sl_ps_profile.dtim_aligned_type) {
            ...
	} else {
            ...
        }

if (sidev->state != WIFI_STATE_COMPLETED) {
/* fail reason */
LOG_ERR("Device not connected");
return -EIO;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You probably meant EINVAL, EBUSY or EAGAIN

sl_status_t status;

if (sidev->state != WIFI_STATE_COMPLETED) {
/* fail reason */
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Useless comment.

return -EINVAL;
}

return sys_get_le16(sl_stat.beacon_interval);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Until now, we didn't considered the host CPU could be big endian. I believe it does not make sense to do it for sl_stat.beacon_interval.

*listen_interval = SIWX91X_PS_MAX_LISTEN_INTERVAL_MS;
}

return 0;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest to return the result. Hence, you will be consistent with siwx91x_get_beacon_interval().


__ASSERT(sl_ps_profile, "sl_ps_profile cannot be NULL");

switch(state) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing space after switch.

@ArunmaniAlagarsamy2710
Copy link
Collaborator Author

Rebased to reduce the CI failures

@ArunmaniAlagarsamy2710 ArunmaniAlagarsamy2710 force-pushed the dev/power_save branch 2 times, most recently from fbecf7c to ea86695 Compare April 3, 2025 14:12
@@ -61,7 +62,9 @@ int siwg91x_get_nwp_config(int wifi_oper_mode, sl_wifi_device_configuration_t *g

#ifdef CONFIG_WIFI_SILABS_SIWX91X
boot_config->ext_tcp_ip_feature_bit_map = SL_SI91X_CONFIG_FEAT_EXTENSION_VALID;
#ifdef CONFIG_WIFI_SIWX91X_ENHANCED_MAX_PSP
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use IS_ENABLED().

sl_status_t status;

sidev->state = WIFI_STATE_INTERFACE_DISABLED;
sidev->iface = iface;

siwx91x_ps_cfg->req_ps_mode = HIGH_PERFORMANCE;
siwx91x_ps_cfg->listen_interval = 0;
siwx91x_ps_cfg->listen_interval_wakeup = false;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can statically assign these values during declaration:

static struct siwx91x_dev sidev = {
        .siwx91x_ps_cfg.req_ps_mode = HIGH_PERFORMANCE,
};

Initialization of listen_interval and listen_interval_wakeup are not required because static variable are already filled with 0s.

BTW, do we need to introduce yet another struct for PS parameters? I feel the only information we need to store is req_ps_mode (to restore it on join).


status = siwx91x_connected_ap_beacon_interval(&beacon_interval);
if (status == SL_STATUS_OK && beacon_interval > 0) {
/* converts MSEC to beacon unit */
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MSEC is not SI: Convert ms in number of beacons. (BTW, it's obvious, the comment is not required)

__ASSERT(listen_interval, "listen_interval cannot be NULL");

if (sidev->state != WIFI_STATE_COMPLETED) {
siwx91x_ps_cfg->listen_interval_wakeup = true;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This side effect is unexpected in function called get_xxx().

}

params.enabled = WIFI_PS_ENABLED;
params.type = WIFI_PS_PARAM_STATE;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer assignment during declaration:

	struct wifi_ps_params params = {
		.type = WIFI_PS_PARAM_STATE,
		.enabled = WIFI_PS_ENABLED,
	};

@ArunmaniAlagarsamy2710
Copy link
Collaborator Author

Rebased to resolve conflicts

Copy link
Collaborator

@jerome-pouiller jerome-pouiller left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code is still super difficult to understand.

case WIFI_PS_PARAM_LISTEN_INTERVAL:
sl_ps_profile->listen_interval = params->listen_interval;
sl_ps_profile->dtim_aligned_type = 0;
return 0;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this return expected? I feel listen_interval will be never applied.


static int siwx91x_set_power_save(const struct device *dev, struct wifi_ps_params *params)
{
static sl_si91x_performance_profile_t exit_strategy_mode = { HIGH_PERFORMANCE };
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid global variables. This variable should probably go in siwx91x_dev.

BTW, I feel this variable is redundant with sl_ps_profile->profile.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, we need the exit_strategy_mode variable to store the power save profile in case the user configures the exit strategy before the power save enabled. If we directly use sl_ps_profile->profile, power save would be enabled immediately even if the user hasn't explicitly enabled it.

static int siwx91x_convert_li_bcn_unit_to_ms(uint16_t bcn_li)
{
uint16_t beacon_interval;
uint16_t ms_li;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why uint16_t? Unless you have good reason use int.

return -EAGAIN;
}

ms_li = bcn_li * beacon_interval;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want to use uint16_t, you have to add a comment explaining why you guarantee there is no overflow here.

static int siwx91x_convert_li_bcn_unit_to_ms(uint16_t bcn_li)
{
uint16_t beacon_interval;
uint16_t ms_li;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable names lack of consistency in the function. I suggest:

  • listen_interval
  • beacon_interval
  • listen_interval_ms

static int siwx91x_configure_ps_mode(const struct device *dev, enum wifi_ps state,
sl_si91x_performance_profile_t exit_strategy_mode)
sl_si91x_performance_profile_t exit_strategy_mode,
uint16_t bcn_li)
{
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment than for siwx91x_schedule_listen_interval_wakeup().

@@ -795,6 +864,7 @@ static int siwx91x_set_power_save(const struct device *dev, struct wifi_ps_param
struct siwx91x_dev *sidev = dev->data;
sl_wifi_performance_profile_t *sl_ps_profile = &sidev->sl_ps_profile;
sl_wifi_interface_t interface;
static uint16_t bcn_li;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please (really) avoid global variables.

@@ -816,7 +886,8 @@ static int siwx91x_set_power_save(const struct device *dev, struct wifi_ps_param

switch (params->type) {
case WIFI_PS_PARAM_STATE:
status = siwx91x_configure_ps_mode(dev, params->enabled, exit_strategy_mode);
status = siwx91x_configure_ps_mode(dev, params->enabled, exit_strategy_mode,
bcn_li);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Value of may be bcn_li is undefined.

siwx91x_on_join_ipv6(sidev);

return 0;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you relocate this function in a separate commit so the review will be easier?

if (status < 0) {
return status;
if (status == -EINVAL) {
sl_ps_profile->profile = HIGH_PERFORMANCE;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Is it related to this commit?
  2. EINVAL means "Invalid argument". This is a bit weird to fix this error by changing the profile.
  3. What happens now if status < 0 && status != -EINVAL ?

Added power-save mode support to optimize station wake-up timing
and reduce power consumption. The device currently supports
only legacy power-save mode.

Implemented power-save features:
- ps ON/OFF	     : By default, the device operates in
		       fast PSP mode.
- ps_exit_strategy   : Updates mode if enabled; otherwise, follows
		       the configured exit strategy when power-save
		       is enabled.
- ps_wakeup_mode     : Configures the wake-up behavior.
- ps_timeout         : Defines the timeout duration for power-save mode.

Signed-off-by: Arunmani Alagarsamy <[email protected]>
Adds support to configure listen interval in power save mode.
Zephyr sets listen interval based on beacon units,
while SDK expects it in ms. The setting takes effect after
connection. If set to 0, device switches to DTIM wakeup mode.

Signed-off-by: Arunmani Alagarsamy <[email protected]>
Move the siwx91x_on_join() function to make it more accessible
for power save feature integration. No functional changes.

Signed-off-by: Arunmani Alagarsamy <[email protected]>
Allow users to enable listen interval wakeup mode before connection.
Power save is automatically activated after connection if configured
beforehand.

Signed-off-by: Arunmani Alagarsamy <[email protected]>
@ArunmaniAlagarsamy2710
Copy link
Collaborator Author

Rebased to resolve conflicts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants