Files
bes2600-dkms/bes2600/bes_pwr.h
T
test0r 6168e9d340 bes2600: gate PM indication completion on pending request and track chip state
When mac80211 toggles PSM on the BES2600, the host sends WSM set_pm
and waits up to 5 s on bes_power.pm_enter_cmpl for a firmware-side
PM-changed indication confirming the transition. Three sequenced
flaws make the wait-and-confirm racy and leave host/chip bookkeeping
desynced when anything misfires:

  1) bes2600_pwr_notify_ps_changed() unconditionally fires
     complete(pm_enter_cmpl) for any non-active psmode. It does not
     check whether a host-initiated set_pm is actually pending. A
     spontaneous indication (firmware-internal coex move,
     idle-driven aging) primes the completion, and the next host-
     driven enter_lp_mode sees a false success on its first
     wait_for_completion_timeout.

  2) The wait/reinit ordering in bes2600_pwr_enter_lp_mode is

         status = wait_for_completion_timeout(...);
         atomic_set(pm_set_in_process, 0);
         reinit_completion(...);

     If an indication arrives between wait_for_completion_timeout
     returning with status==1 and reinit_completion, the next
     enter_lp_mode iteration's wait can also see false success. The
     reinit must happen *before* we start the new request, not
     after handling the previous one.

  3) On wait_pm_ind timeout, the driver returns -ETIMEDOUT and walks
     away. It does not record that the firmware's actual PM state
     is no longer known to the host. Subsequent wake paths
     (gpio_wake / sbus_active) assume the chip is still active and
     hit deterministic SDIO failures when the firmware has
     transitioned anyway.

This patch is the safe-prerequisite half of a wider fix:

  * bes_pwr.h gains enum bes2600_chip_pm_state {ACTIVE, LP, UNKNOWN}
    and bes_power.chip_pm_state. Its job is to track what the host
    has *seen the firmware confirm*, not what the host has
    requested. Initialised to ACTIVE in bes2600_pwr_init().

  * bes2600_pwr_notify_ps_changed() unconditionally updates
    chip_pm_state on every indication, but only fires
    complete(pm_enter_cmpl) when atomic_cmpxchg(pm_set_in_process,
    1, 0) succeeds. A spontaneous indication can no longer prime a
    waiter that will only set up its request afterwards.

  * bes2600_pwr_enter_lp_mode() now reinit_completion()s before
    setting pm_set_in_process and sending wsm_set_pm. After a
    timeout, it cmpxchgs pm_set_in_process back to 0 (so a late
    indication cannot prime the next iteration) and on the win-
    cmpxchg branch records chip_pm_state=UNKNOWN.

A follow-up patch consumes chip_pm_state on the wake side
(bes2600_pwr_device_exit_lp_mode + bes2600_gpio_wakeup_mcu) to fix
the deterministic "active mcu fail" cycle this state-record
enables a fix for. Splitting the work this way keeps the lock-free
race fix small and reviewable on its own.

No new locks, no behaviour change on the success path. Only the
recovery path (timeout + spontaneous indication) gains correctness.

Signed-off-by: Markus Fritsche <fritsche.markus@gmail.com>
2026-04-28 19:51:17 +02:00

184 lines
8.2 KiB
C

/*
* Mac80211 driver for BES2600 device
*
* Copyright (c) 2022, Bestechnic
* Author:
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifndef __BES_PWR_H__
#define __BES_PWR_H__
#include "bes2600.h"
enum bes2600_pwr_event_type {
BES_PWR_LOCK_ON_SCAN = 1,
BES_PWR_LOCK_ON_JOIN,
BES_PWR_LOCK_ON_TX,
BES_PWR_LOCK_ON_RX,
BES_PWR_LOCK_ON_FLUSH,
BES_PWR_LOCK_ON_ROC,
BES_PWR_LOCK_ON_WSM_TX,
BES_PWR_LOCK_ON_WSM_OPER,
BES_PWR_LOCK_ON_BSS_LOST,
BES_PWR_LOCK_ON_GET_IP,
BES_PWR_LOCK_ON_PS_ACTIVE,
BES_PWR_LOCK_ON_LMAC_RSP,
BES_PWR_LOCK_ON_AP,
BES_PWR_LOCK_ON_TEST_CMD,
BES_PWR_LOCK_ON_MUL_REQ,
BES_PWR_LOCK_ON_ADV_SCAN,
BES_PWR_LOCK_ON_DISCON,
BES_PWR_LOCK_ON_QUEUE_GC,
BES_PWR_LOCK_ON_AP_LP_BAD,
/* add new lock event here */
BES_PWR_LOCK_ON_EVENT_MAX,
};
#define BES_PWR_IS_CONSTANT_EVENT(x) ((x) & 0x80000000)
#define BES_PWR_EVENT_SET_CONSTANT(x) (x = (x | 0x80000000))
#define BES_PWR_EVENT_NUMBER(x) ((x) & 0x7fffffff)
#define BES2600_POWER_DOWN_DELAY 50 // unit in millisecond
#define BES2600_DELAY_EVENT_NUM (BES_PWR_LOCK_ON_EVENT_MAX << 1)
#define BES_PWR_EVENT_TX_TIMEOUT 1500 // unit in millisecond
#define BES_PWR_EVENT_RX_TIMEOUT 100 // unit in millisecond
struct bes2600_pwr_event_t
{
struct list_head link;
unsigned long timeout;
unsigned long delay;
u8 idx;
enum bes2600_pwr_event_type event;
};
enum power_down_state
{
POWER_DOWN_STATE_LOCKED = 0,
POWER_DOWN_STATE_LOCKING,
POWER_DOWN_STATE_UNLOCKING,
POWER_DOWN_STATE_UNLOCKED,
};
/*
* Confirmed PM state of the firmware-side chip. Tracks what the host
* has *seen* the firmware acknowledge, not what the host has
* requested. UNKNOWN means a host-initiated transition timed out
* before the firmware indication arrived; the next wake path should
* treat it as "we don't know" and probe before issuing GPIO/SDIO
* wakeup ops.
*/
enum bes2600_chip_pm_state {
BES2600_CHIP_PM_ACTIVE = 0,
BES2600_CHIP_PM_LP,
BES2600_CHIP_PM_UNKNOWN,
};
typedef void (*bes_pwr_enter_lp_cb)(struct bes2600_common *hw_priv);
typedef void (*bes_pwr_exit_lp_cb)(struct bes2600_common *hw_priv);
struct bes2600_pwr_enter_cb_item
{
struct list_head link;
bes_pwr_enter_lp_cb cb;
};
struct bes2600_pwr_exit_cb_item
{
struct list_head link;
bes_pwr_exit_lp_cb cb;
};
struct bes2600_pwr_t
{
spinlock_t pwr_lock;
struct delayed_work power_down_work;
struct work_struct power_async_work;
struct work_struct power_mcu_down_work;
struct list_head async_timeout_list;
struct list_head pending_event_list;
struct list_head free_event_list;
enum power_down_state power_state;
struct task_struct *power_down_task;
struct task_struct *power_up_task;
struct task_struct *sys_suspend_task;
struct task_struct *sys_resume_task;
struct semaphore sync_lock;
bool pending_lock;
atomic_t dev_state;
struct mutex pwr_mutex;
struct bes2600_common *hw_priv;
struct completion pm_enter_cmpl;
struct mutex pwr_cb_mutex;
struct list_head enter_cb_list;
struct list_head exit_cb_list;
wait_queue_head_t dev_lp_wq;
bool ap_lp_bad;
struct bes2600_pwr_event_t pwr_events[BES2600_DELAY_EVENT_NUM];
atomic_t pm_set_in_process;
atomic_t chip_pm_state;
};
#ifdef CONFIG_BES2600_WOWLAN
void bes2600_pwr_init(struct bes2600_common *hw_priv);
void bes2600_pwr_exit(struct bes2600_common *hw_priv);
void bes2600_pwr_prepare(struct bes2600_common *hw_priv);
void bes2600_pwr_complete(struct bes2600_common *hw_priv);
void bes2600_pwr_start(struct bes2600_common *hw_priv);
void bes2600_pwr_stop(struct bes2600_common *hw_priv);
bool bes2600_pwr_constant_event_is_pending(struct bes2600_common *hw_priv, u32 event);
int bes2600_pwr_set_busy_event(struct bes2600_common *hw_priv, u32 event);
int bes2600_pwr_set_busy_event_async(struct bes2600_common *hw_priv, u32 event);
int bes2600_pwr_set_busy_event_with_timeout(struct bes2600_common *hw_priv, u32 event, u32 timeout);
int bes2600_pwr_set_busy_event_with_timeout_async(struct bes2600_common *hw_priv, u32 event, u32 timeout);
int bes2600_pwr_clear_busy_event(struct bes2600_common *hw_priv, u32 event);
void bes2600_pwr_notify_ps_changed(struct bes2600_common *hw_priv, u8 psmode);
bool bes2600_pwr_device_is_idle(struct bes2600_common *hw_priv);
void bes2600_pwr_register_en_lp_cb(struct bes2600_common *hw_priv, bes_pwr_enter_lp_cb cb);
void bes2600_pwr_unregister_en_lp_cb(struct bes2600_common *hw_priv, bes_pwr_enter_lp_cb cb);
void bes2600_pwr_register_exit_lp_cb(struct bes2600_common *hw_priv, bes_pwr_enter_lp_cb cb);
void bes2600_pwr_unregister_exit_lp_cb(struct bes2600_common *hw_priv, bes_pwr_enter_lp_cb cb);
void bes2600_pwr_suspend_start(struct bes2600_common *hw_priv);
void bes2600_pwr_suspend_end(struct bes2600_common *hw_priv);
void bes2600_pwr_resume_start(struct bes2600_common *hw_priv);
void bes2600_pwr_resume_end(struct bes2600_common *hw_priv);
void bes2600_pwr_mcu_sleep_directly(struct bes2600_common *hw_priv);
void bes2600_pwr_mark_ap_lp_bad(struct bes2600_common *hw_priv);
void bes2600_pwr_clear_ap_lp_bad_mark(struct bes2600_common *hw_priv);
int bes2600_pwr_busy_event_dump(struct bes2600_common *hw_priv, char *buffer, u32 buf_len);
int bes2600_pwr_busy_event_record(struct bes2600_common *hw_priv, char *buffer, u32 buf_len);
#else
static inline void bes2600_pwr_init(struct bes2600_common *hw_priv) { }
static inline void bes2600_pwr_exit(struct bes2600_common *hw_priv) { }
static inline void bes2600_pwr_prepare(struct bes2600_common *hw_priv) { }
static inline void bes2600_pwr_complete(struct bes2600_common *hw_priv) { }
static inline void bes2600_pwr_start(struct bes2600_common *hw_priv) { }
static inline void bes2600_pwr_stop(struct bes2600_common *hw_priv) { }
static inline bool bes2600_pwr_constant_event_is_pending(struct bes2600_common *hw_priv, u32 event) { return false; }
static inline int bes2600_pwr_set_busy_event(struct bes2600_common *hw_priv, u32 event) { return 0; }
static inline int bes2600_pwr_set_busy_event_async(struct bes2600_common *hw_priv, u32 event) {return 0; }
static inline int bes2600_pwr_set_busy_event_with_timeout(struct bes2600_common *hw_priv, u32 event, u32 timeout) { return 0; }
static inline int bes2600_pwr_set_busy_event_with_timeout_async(struct bes2600_common *hw_priv, u32 event, u32 timeout) { return 0; }
static inline int bes2600_pwr_clear_busy_event(struct bes2600_common *hw_priv, u32 event) { return 0; }
static inline void bes2600_pwr_notify_ps_changed(struct bes2600_common *hw_priv, u8 psmode) { }
static inline bool bes2600_pwr_device_is_idle(struct bes2600_common *hw_priv) { return false; }
static inline void bes2600_pwr_register_en_lp_cb(struct bes2600_common *hw_priv, bes_pwr_enter_lp_cb cb) { }
static inline void bes2600_pwr_unregister_en_lp_cb(struct bes2600_common *hw_priv, bes_pwr_enter_lp_cb cb) { }
static inline void bes2600_pwr_register_exit_lp_cb(struct bes2600_common *hw_priv, bes_pwr_enter_lp_cb cb) { }
static inline void bes2600_pwr_unregister_exit_lp_cb(struct bes2600_common *hw_priv, bes_pwr_enter_lp_cb cb) { }
static inline void bes2600_pwr_suspend_start(struct bes2600_common *hw_priv) { }
static inline void bes2600_pwr_suspend_end(struct bes2600_common *hw_priv) { }
static inline void bes2600_pwr_resume_start(struct bes2600_common *hw_priv) { }
static inline void bes2600_pwr_resume_end(struct bes2600_common *hw_priv) { }
static inline void bes2600_pwr_mcu_sleep_directly(struct bes2600_common *hw_priv) { }
static inline void bes2600_pwr_mark_ap_lp_bad(struct bes2600_common *hw_priv) { }
static inline void bes2600_pwr_clear_ap_lp_bad_mark(struct bes2600_common *hw_priv) { }
static inline int bes2600_pwr_busy_event_dump(struct bes2600_common *hw_priv, char *buffer, u32 buf_len) { return 0; }
static inline int bes2600_pwr_busy_event_record(struct bes2600_common *hw_priv, char *buffer, u32 buf_len) { return 0; }
#endif
#endif