| // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause |
| /* Copyright(c) 2020-2022 Realtek Corporation |
| */ |
| |
| #include "chan.h" |
| #include "debug.h" |
| #include "fw.h" |
| #include "ps.h" |
| #include "util.h" |
| |
| static enum rtw89_subband rtw89_get_subband_type(enum rtw89_band band, |
| u8 center_chan) |
| { |
| switch (band) { |
| default: |
| case RTW89_BAND_2G: |
| switch (center_chan) { |
| default: |
| case 1 ... 14: |
| return RTW89_CH_2G; |
| } |
| case RTW89_BAND_5G: |
| switch (center_chan) { |
| default: |
| case 36 ... 64: |
| return RTW89_CH_5G_BAND_1; |
| case 100 ... 144: |
| return RTW89_CH_5G_BAND_3; |
| case 149 ... 177: |
| return RTW89_CH_5G_BAND_4; |
| } |
| case RTW89_BAND_6G: |
| switch (center_chan) { |
| default: |
| case 1 ... 29: |
| return RTW89_CH_6G_BAND_IDX0; |
| case 33 ... 61: |
| return RTW89_CH_6G_BAND_IDX1; |
| case 65 ... 93: |
| return RTW89_CH_6G_BAND_IDX2; |
| case 97 ... 125: |
| return RTW89_CH_6G_BAND_IDX3; |
| case 129 ... 157: |
| return RTW89_CH_6G_BAND_IDX4; |
| case 161 ... 189: |
| return RTW89_CH_6G_BAND_IDX5; |
| case 193 ... 221: |
| return RTW89_CH_6G_BAND_IDX6; |
| case 225 ... 253: |
| return RTW89_CH_6G_BAND_IDX7; |
| } |
| } |
| } |
| |
| static enum rtw89_sc_offset rtw89_get_primary_chan_idx(enum rtw89_bandwidth bw, |
| u32 center_freq, |
| u32 primary_freq) |
| { |
| u8 primary_chan_idx; |
| u32 offset; |
| |
| switch (bw) { |
| default: |
| case RTW89_CHANNEL_WIDTH_20: |
| primary_chan_idx = RTW89_SC_DONT_CARE; |
| break; |
| case RTW89_CHANNEL_WIDTH_40: |
| if (primary_freq > center_freq) |
| primary_chan_idx = RTW89_SC_20_UPPER; |
| else |
| primary_chan_idx = RTW89_SC_20_LOWER; |
| break; |
| case RTW89_CHANNEL_WIDTH_80: |
| case RTW89_CHANNEL_WIDTH_160: |
| if (primary_freq > center_freq) { |
| offset = (primary_freq - center_freq - 10) / 20; |
| primary_chan_idx = RTW89_SC_20_UPPER + offset * 2; |
| } else { |
| offset = (center_freq - primary_freq - 10) / 20; |
| primary_chan_idx = RTW89_SC_20_LOWER + offset * 2; |
| } |
| break; |
| } |
| |
| return primary_chan_idx; |
| } |
| |
| void rtw89_chan_create(struct rtw89_chan *chan, u8 center_chan, u8 primary_chan, |
| enum rtw89_band band, enum rtw89_bandwidth bandwidth) |
| { |
| enum nl80211_band nl_band = rtw89_hw_to_nl80211_band(band); |
| u32 center_freq, primary_freq; |
| |
| memset(chan, 0, sizeof(*chan)); |
| chan->channel = center_chan; |
| chan->primary_channel = primary_chan; |
| chan->band_type = band; |
| chan->band_width = bandwidth; |
| |
| center_freq = ieee80211_channel_to_frequency(center_chan, nl_band); |
| primary_freq = ieee80211_channel_to_frequency(primary_chan, nl_band); |
| |
| chan->freq = center_freq; |
| chan->subband_type = rtw89_get_subband_type(band, center_chan); |
| chan->pri_ch_idx = rtw89_get_primary_chan_idx(bandwidth, center_freq, |
| primary_freq); |
| } |
| |
| bool rtw89_assign_entity_chan(struct rtw89_dev *rtwdev, |
| enum rtw89_sub_entity_idx idx, |
| const struct rtw89_chan *new) |
| { |
| struct rtw89_hal *hal = &rtwdev->hal; |
| struct rtw89_chan *chan = &hal->sub[idx].chan; |
| struct rtw89_chan_rcd *rcd = &hal->sub[idx].rcd; |
| bool band_changed; |
| |
| rcd->prev_primary_channel = chan->primary_channel; |
| rcd->prev_band_type = chan->band_type; |
| band_changed = new->band_type != chan->band_type; |
| rcd->band_changed = band_changed; |
| |
| *chan = *new; |
| return band_changed; |
| } |
| |
| static void __rtw89_config_entity_chandef(struct rtw89_dev *rtwdev, |
| enum rtw89_sub_entity_idx idx, |
| const struct cfg80211_chan_def *chandef, |
| bool from_stack) |
| { |
| struct rtw89_hal *hal = &rtwdev->hal; |
| |
| hal->sub[idx].chandef = *chandef; |
| |
| if (from_stack) |
| set_bit(idx, hal->entity_map); |
| } |
| |
| void rtw89_config_entity_chandef(struct rtw89_dev *rtwdev, |
| enum rtw89_sub_entity_idx idx, |
| const struct cfg80211_chan_def *chandef) |
| { |
| __rtw89_config_entity_chandef(rtwdev, idx, chandef, true); |
| } |
| |
| void rtw89_config_roc_chandef(struct rtw89_dev *rtwdev, |
| enum rtw89_sub_entity_idx idx, |
| const struct cfg80211_chan_def *chandef) |
| { |
| struct rtw89_hal *hal = &rtwdev->hal; |
| enum rtw89_sub_entity_idx cur; |
| |
| if (chandef) { |
| cur = atomic_cmpxchg(&hal->roc_entity_idx, |
| RTW89_SUB_ENTITY_IDLE, idx); |
| if (cur != RTW89_SUB_ENTITY_IDLE) { |
| rtw89_debug(rtwdev, RTW89_DBG_TXRX, |
| "ROC still processing on entity %d\n", idx); |
| return; |
| } |
| |
| hal->roc_chandef = *chandef; |
| } else { |
| cur = atomic_cmpxchg(&hal->roc_entity_idx, idx, |
| RTW89_SUB_ENTITY_IDLE); |
| if (cur == idx) |
| return; |
| |
| if (cur == RTW89_SUB_ENTITY_IDLE) |
| rtw89_debug(rtwdev, RTW89_DBG_TXRX, |
| "ROC already finished on entity %d\n", idx); |
| else |
| rtw89_debug(rtwdev, RTW89_DBG_TXRX, |
| "ROC is processing on entity %d\n", cur); |
| } |
| } |
| |
| static void rtw89_config_default_chandef(struct rtw89_dev *rtwdev) |
| { |
| struct cfg80211_chan_def chandef = {0}; |
| |
| rtw89_get_default_chandef(&chandef); |
| __rtw89_config_entity_chandef(rtwdev, RTW89_SUB_ENTITY_0, &chandef, false); |
| } |
| |
| void rtw89_entity_init(struct rtw89_dev *rtwdev) |
| { |
| struct rtw89_hal *hal = &rtwdev->hal; |
| |
| bitmap_zero(hal->entity_map, NUM_OF_RTW89_SUB_ENTITY); |
| atomic_set(&hal->roc_entity_idx, RTW89_SUB_ENTITY_IDLE); |
| rtw89_config_default_chandef(rtwdev); |
| } |
| |
| enum rtw89_entity_mode rtw89_entity_recalc(struct rtw89_dev *rtwdev) |
| { |
| struct rtw89_hal *hal = &rtwdev->hal; |
| const struct cfg80211_chan_def *chandef; |
| enum rtw89_entity_mode mode; |
| struct rtw89_chan chan; |
| u8 weight; |
| u8 last; |
| u8 idx; |
| |
| weight = bitmap_weight(hal->entity_map, NUM_OF_RTW89_SUB_ENTITY); |
| switch (weight) { |
| default: |
| rtw89_warn(rtwdev, "unknown ent chan weight: %d\n", weight); |
| bitmap_zero(hal->entity_map, NUM_OF_RTW89_SUB_ENTITY); |
| fallthrough; |
| case 0: |
| rtw89_config_default_chandef(rtwdev); |
| fallthrough; |
| case 1: |
| last = RTW89_SUB_ENTITY_0; |
| mode = RTW89_ENTITY_MODE_SCC; |
| break; |
| case 2: |
| last = RTW89_SUB_ENTITY_1; |
| mode = rtw89_get_entity_mode(rtwdev); |
| if (mode == RTW89_ENTITY_MODE_MCC) |
| break; |
| |
| mode = RTW89_ENTITY_MODE_MCC_PREPARE; |
| break; |
| } |
| |
| for (idx = 0; idx <= last; idx++) { |
| chandef = rtw89_chandef_get(rtwdev, idx); |
| rtw89_get_channel_params(chandef, &chan); |
| if (chan.channel == 0) { |
| WARN(1, "Invalid channel on chanctx %d\n", idx); |
| return RTW89_ENTITY_MODE_INVALID; |
| } |
| |
| rtw89_assign_entity_chan(rtwdev, idx, &chan); |
| } |
| |
| rtw89_set_entity_mode(rtwdev, mode); |
| return mode; |
| } |
| |
| static void rtw89_chanctx_notify(struct rtw89_dev *rtwdev, |
| enum rtw89_chanctx_state state) |
| { |
| const struct rtw89_chip_info *chip = rtwdev->chip; |
| const struct rtw89_chanctx_listener *listener = chip->chanctx_listener; |
| int i; |
| |
| if (!listener) |
| return; |
| |
| for (i = 0; i < NUM_OF_RTW89_CHANCTX_CALLBACKS; i++) { |
| if (!listener->callbacks[i]) |
| continue; |
| |
| rtw89_debug(rtwdev, RTW89_DBG_CHAN, |
| "chanctx notify listener: cb %d, state %d\n", |
| i, state); |
| |
| listener->callbacks[i](rtwdev, state); |
| } |
| } |
| |
| static int rtw89_mcc_start(struct rtw89_dev *rtwdev) |
| { |
| if (rtwdev->scanning) |
| rtw89_hw_scan_abort(rtwdev, rtwdev->scan_info.scanning_vif); |
| |
| rtw89_leave_lps(rtwdev); |
| |
| rtw89_debug(rtwdev, RTW89_DBG_CHAN, "MCC start\n"); |
| rtw89_chanctx_notify(rtwdev, RTW89_CHANCTX_STATE_MCC_START); |
| return 0; |
| } |
| |
| static void rtw89_mcc_stop(struct rtw89_dev *rtwdev) |
| { |
| rtw89_debug(rtwdev, RTW89_DBG_CHAN, "MCC stop\n"); |
| rtw89_chanctx_notify(rtwdev, RTW89_CHANCTX_STATE_MCC_STOP); |
| } |
| |
| void rtw89_chanctx_work(struct work_struct *work) |
| { |
| struct rtw89_dev *rtwdev = container_of(work, struct rtw89_dev, |
| chanctx_work.work); |
| enum rtw89_entity_mode mode; |
| int ret; |
| |
| mutex_lock(&rtwdev->mutex); |
| |
| mode = rtw89_get_entity_mode(rtwdev); |
| switch (mode) { |
| case RTW89_ENTITY_MODE_MCC_PREPARE: |
| rtw89_set_entity_mode(rtwdev, RTW89_ENTITY_MODE_MCC); |
| rtw89_set_channel(rtwdev); |
| |
| ret = rtw89_mcc_start(rtwdev); |
| if (ret) |
| rtw89_warn(rtwdev, "failed to start MCC: %d\n", ret); |
| break; |
| default: |
| break; |
| } |
| |
| mutex_unlock(&rtwdev->mutex); |
| } |
| |
| void rtw89_queue_chanctx_work(struct rtw89_dev *rtwdev) |
| { |
| enum rtw89_entity_mode mode; |
| u32 delay; |
| |
| mode = rtw89_get_entity_mode(rtwdev); |
| switch (mode) { |
| default: |
| return; |
| case RTW89_ENTITY_MODE_MCC_PREPARE: |
| delay = ieee80211_tu_to_usec(RTW89_CHANCTX_TIME_MCC_PREPARE); |
| break; |
| } |
| |
| rtw89_debug(rtwdev, RTW89_DBG_CHAN, |
| "queue chanctx work for mode %d with delay %d us\n", |
| mode, delay); |
| ieee80211_queue_delayed_work(rtwdev->hw, &rtwdev->chanctx_work, |
| usecs_to_jiffies(delay)); |
| } |
| |
| int rtw89_chanctx_ops_add(struct rtw89_dev *rtwdev, |
| struct ieee80211_chanctx_conf *ctx) |
| { |
| struct rtw89_hal *hal = &rtwdev->hal; |
| struct rtw89_chanctx_cfg *cfg = (struct rtw89_chanctx_cfg *)ctx->drv_priv; |
| const struct rtw89_chip_info *chip = rtwdev->chip; |
| u8 idx; |
| |
| idx = find_first_zero_bit(hal->entity_map, NUM_OF_RTW89_SUB_ENTITY); |
| if (idx >= chip->support_chanctx_num) |
| return -ENOENT; |
| |
| rtw89_config_entity_chandef(rtwdev, idx, &ctx->def); |
| rtw89_set_channel(rtwdev); |
| cfg->idx = idx; |
| hal->sub[idx].cfg = cfg; |
| return 0; |
| } |
| |
| void rtw89_chanctx_ops_remove(struct rtw89_dev *rtwdev, |
| struct ieee80211_chanctx_conf *ctx) |
| { |
| struct rtw89_hal *hal = &rtwdev->hal; |
| struct rtw89_chanctx_cfg *cfg = (struct rtw89_chanctx_cfg *)ctx->drv_priv; |
| enum rtw89_entity_mode mode; |
| struct rtw89_vif *rtwvif; |
| u8 drop, roll; |
| |
| drop = cfg->idx; |
| if (drop != RTW89_SUB_ENTITY_0) |
| goto out; |
| |
| roll = find_next_bit(hal->entity_map, NUM_OF_RTW89_SUB_ENTITY, drop + 1); |
| |
| /* Follow rtw89_config_default_chandef() when rtw89_entity_recalc(). */ |
| if (roll == NUM_OF_RTW89_SUB_ENTITY) |
| goto out; |
| |
| /* RTW89_SUB_ENTITY_0 is going to release, and another exists. |
| * Make another roll down to RTW89_SUB_ENTITY_0 to replace. |
| */ |
| hal->sub[roll].cfg->idx = RTW89_SUB_ENTITY_0; |
| hal->sub[RTW89_SUB_ENTITY_0] = hal->sub[roll]; |
| |
| rtw89_for_each_rtwvif(rtwdev, rtwvif) { |
| if (rtwvif->sub_entity_idx == roll) |
| rtwvif->sub_entity_idx = RTW89_SUB_ENTITY_0; |
| } |
| |
| atomic_cmpxchg(&hal->roc_entity_idx, roll, RTW89_SUB_ENTITY_0); |
| |
| drop = roll; |
| |
| out: |
| mode = rtw89_get_entity_mode(rtwdev); |
| switch (mode) { |
| case RTW89_ENTITY_MODE_MCC: |
| rtw89_mcc_stop(rtwdev); |
| break; |
| default: |
| break; |
| } |
| |
| clear_bit(drop, hal->entity_map); |
| rtw89_set_channel(rtwdev); |
| } |
| |
| void rtw89_chanctx_ops_change(struct rtw89_dev *rtwdev, |
| struct ieee80211_chanctx_conf *ctx, |
| u32 changed) |
| { |
| struct rtw89_chanctx_cfg *cfg = (struct rtw89_chanctx_cfg *)ctx->drv_priv; |
| u8 idx = cfg->idx; |
| |
| if (changed & IEEE80211_CHANCTX_CHANGE_WIDTH) { |
| rtw89_config_entity_chandef(rtwdev, idx, &ctx->def); |
| rtw89_set_channel(rtwdev); |
| } |
| } |
| |
| int rtw89_chanctx_ops_assign_vif(struct rtw89_dev *rtwdev, |
| struct rtw89_vif *rtwvif, |
| struct ieee80211_chanctx_conf *ctx) |
| { |
| struct rtw89_chanctx_cfg *cfg = (struct rtw89_chanctx_cfg *)ctx->drv_priv; |
| |
| rtwvif->sub_entity_idx = cfg->idx; |
| return 0; |
| } |
| |
| void rtw89_chanctx_ops_unassign_vif(struct rtw89_dev *rtwdev, |
| struct rtw89_vif *rtwvif, |
| struct ieee80211_chanctx_conf *ctx) |
| { |
| rtwvif->sub_entity_idx = RTW89_SUB_ENTITY_0; |
| } |