2021-05-10 15:15:57

by Reto Schneider

[permalink] [raw]
Subject: rtl8xxxu: Wi-Fi Alliance Certification on Realtek RTL8188CUS

Hi Jes,
Hi all,

Chris (writing code, testing) and me (testing) are working [1] on
re-newing the Wi-Fi Alliance certification of an IoT gateway (STA) which
uses the Realtek RTL8188CUS chip, and is (now) running Linux 5.10+ with
the rtl8xxxu driver, firmware version 88.2 [2]. The existing
certification [3] has been done using the RealTek-provided 8192cu driver
[4] and and Linux 3.19.

While the rtl8xxxu in upstream is *very* stable, it lacks some features
which are needed for the certification. I tried to summarize this in the
wiki [1].

The following has been implemented by Chris (on top of mainline):
- STBC Receive
- TX power configuration (using the iw command, not regdb)

Still missing:
- WMM support
- A-MSDU RX support
- TX power by rate

WIP (See also Chris' mail from earlier today):
- A-MPDU
- Rate adaption
- TX descriptors

The motivation for writing this mail is that we we are currently blocked
by an issue which we can not resolve already for some months: The
re-transmission percentage of the rtl8xxxu is too high (rtl8192cu is the
same) - way higher than what 8192cu achieves. According to Wireshark,
the retransmission rate is always between 15-65% for the rtl8xxxu, while
the realtek-provided 8192cu stays below 5%, often averaging at less than
1% in my setup.

Examples with an Linksys WRT3200ACM AP (RA/TA: 24:f5:a2:c0:4e:b1, DA/SA:
00:60:6e:00:07:7d, SSID customer-testwifi) and the DUT
(00:1d:43:c0:19:8a) running iperf3 (TCP, TX):

rtl8xxxu:
- 65% retries, many addresses reported which do not actually exists
- Code: [5]
- Captures: [7]

891cu:
- 0.9% retries, no ghost addresses captured
- Code: [6]
- Captures: [8]

I'd be really grateful for any kind of ideas, pointers, help.

Kind regards,
Reto

[1] Wiki entry (slightly outdated):
https://wireless.wiki.kernel.org/en/users/wi-fi-alliance-certification
[2] rtl8912cu firmware v88.2, part of linux-firmware 20201118:
https://git.kernel.org/pub/scm/linux/kernel/git/firmware/linux-firmware.git/commit/rtlwifi/rtl8192cufw_TMSC.bin?id=2ea86675db1349235e9af0a9d0372b72da4db259
[3] Existing certification:
https://api.cert.wi-fi.org/api/certificate/download/public?variantId=14856
[4] https://github.com/husqvarnagroup/smart-garden-gateway-seluxit-8192cu
[5] Most recent rtl8xxxu development:
https://github.com/husqvarnagroup/linux/commit/909f13c8fd45260a622d148882030624d492c54f
[6] 8192cu for Linux 5.10+:
https://github.com/husqvarnagroup/realtek-8192cu_rtl8188cus
[7] pcap:
https://files.reto-schneider.ch/diesunddas/rtl8xxxu/2021-05-10/rtl8xxxu-65%25-retries.pcapng.gz
shell:
https://files.reto-schneider.ch/diesunddas/rtl8xxxu/2021-05-10/rtl8xxxu-65%25-retries-shell.log
[8] pcap:
https://files.reto-schneider.ch/diesunddas/rtl8xxxu/2021-05-10/8192cu-0.9%25-retries.pcapng.gz
shell:
https://files.reto-schneider.ch/diesunddas/rtl8xxxu/2021-05-10/8192cu-0.9%25-retries-shell.log


2021-05-14 02:35:58

by Reto Schneider

[permalink] [raw]
Subject: [PATCH 0/7] [RFC] rtl8xxxu/RTL8188CUS: Wi-Fi Alliance Certification

From: Reto Schneider <[email protected]>

This is our most recent, WIP code. It is plagued by the too-frequent
retransmission issue as described in the original mail.

Any kind of help is welcome.


Chris Chiu (7):
rtl8xxxu: add code to handle
BSS_CHANGED_TXPOWER/IEEE80211_CONF_CHANGE_POWER
rtl8xxxu: add handle for mac80211 get_txpower
rtl8xxxu: Enable RX STBC by default
rtl8xxxu: feed antenna information for mac80211
rtl8xxxu: fill up txrate info for all chips
rtl8xxxu: Fix the reported rx signal strength
rtl8xxxu: Fix ampdu_action to get block ack session work

.../net/wireless/realtek/rtl8xxxu/rtl8xxxu.h | 7 +
.../realtek/rtl8xxxu/rtl8xxxu_8192c.c | 2 +
.../realtek/rtl8xxxu/rtl8xxxu_8723a.c | 1 +
.../wireless/realtek/rtl8xxxu/rtl8xxxu_core.c | 301 +++++++++++++++---
4 files changed, 270 insertions(+), 41 deletions(-)

--
2.29.2


2021-05-14 02:35:58

by Reto Schneider

[permalink] [raw]
Subject: [PATCH 7/7] rtl8xxxu: Fix ampdu_action to get block ack session work

From: Chris Chiu <[email protected]>

The HAS_RATE_CONTROL hw capability needs to be unset for the mac80211
rate control to work. The mac80211 rate control will be in charge
of the TX aggregation related work so the AMPDU TX part in each
driver should be modified accordingly.

Signed-off-by: Chris Chiu <[email protected]>
(cherry picked from commit c470f7aed67f223d941e3da6e9d2a464dd0083ee)

Signed-off-by: Reto Schneider <[email protected]>
---

.../net/wireless/realtek/rtl8xxxu/rtl8xxxu.h | 1 +
.../wireless/realtek/rtl8xxxu/rtl8xxxu_core.c | 35 ++++++++++++-------
2 files changed, 24 insertions(+), 12 deletions(-)

diff --git a/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu.h b/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu.h
index 3d16d6c9ff39..65620ecf9ac2 100644
--- a/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu.h
+++ b/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu.h
@@ -1384,6 +1384,7 @@ struct rtl8xxxu_priv {
u8 no_pape:1;
u8 int_buf[USB_INTR_CONTENT_LENGTH];
u8 rssi_level;
+ u8 agg_state_bitmap;
/*
* Only one virtual interface permitted because only STA mode
* is supported and no iface_combinations are provided.
diff --git a/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu_core.c b/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu_core.c
index 088e007e8bd0..7ce27d1bb27a 100644
--- a/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu_core.c
+++ b/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu_core.c
@@ -5030,6 +5030,8 @@ rtl8xxxu_fill_txdesc_v1(struct ieee80211_hw *hw, struct ieee80211_hdr *hdr,
struct ieee80211_rate *tx_rate = ieee80211_get_tx_rate(hw, tx_info);
struct rtl8xxxu_priv *priv = hw->priv;
struct device *dev = &priv->udev->dev;
+ u8 *qc = ieee80211_get_qos_ctl(hdr);
+ u8 tid = qc[0] & IEEE80211_QOS_CTL_TID_MASK;
u32 rate;
u16 rate_flags = tx_info->control.rates[0].flags;
u16 seq_number;
@@ -5053,10 +5055,12 @@ rtl8xxxu_fill_txdesc_v1(struct ieee80211_hw *hw, struct ieee80211_hdr *hdr,

tx_desc->txdw3 = cpu_to_le32((u32)seq_number << TXDESC32_SEQ_SHIFT);

- if (ampdu_enable)
+ if (ampdu_enable && (priv->agg_state_bitmap & BIT(tid)) &&
+ (tx_info->flags & IEEE80211_TX_CTL_AMPDU)) {
tx_desc->txdw1 |= cpu_to_le32(TXDESC32_AGG_ENABLE);
- else
+ } else {
tx_desc->txdw1 |= cpu_to_le32(TXDESC32_AGG_BREAK);
+ }

if (ieee80211_is_mgmt(hdr->frame_control)) {
tx_desc->txdw5 = cpu_to_le32(rate);
@@ -5101,6 +5105,8 @@ rtl8xxxu_fill_txdesc_v2(struct ieee80211_hw *hw, struct ieee80211_hdr *hdr,
struct rtl8xxxu_priv *priv = hw->priv;
struct device *dev = &priv->udev->dev;
struct rtl8xxxu_txdesc40 *tx_desc40;
+ u8 *qc = ieee80211_get_qos_ctl(hdr);
+ u8 tid = qc[0] & IEEE80211_QOS_CTL_TID_MASK;
u32 rate;
u16 rate_flags = tx_info->control.rates[0].flags;
u16 seq_number;
@@ -5127,10 +5133,13 @@ rtl8xxxu_fill_txdesc_v2(struct ieee80211_hw *hw, struct ieee80211_hdr *hdr,

tx_desc40->txdw9 = cpu_to_le32((u32)seq_number << TXDESC40_SEQ_SHIFT);

- if (ampdu_enable)
+ if (ampdu_enable && (priv->agg_state_bitmap & BIT(tid)) &&
+ (tx_info->flags & IEEE80211_TX_CTL_AMPDU)) {
tx_desc40->txdw2 |= cpu_to_le32(TXDESC40_AGG_ENABLE);
- else
+ tx_desc40->txdw3 |= cpu_to_le32(0x1f << 17);
+ } else {
tx_desc40->txdw2 |= cpu_to_le32(TXDESC40_AGG_BREAK);
+ }

if (ieee80211_is_mgmt(hdr->frame_control)) {
tx_desc40->txdw4 = cpu_to_le32(rate);
@@ -6299,6 +6308,7 @@ rtl8xxxu_ampdu_action(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
struct device *dev = &priv->udev->dev;
u8 ampdu_factor, ampdu_density;
struct ieee80211_sta *sta = params->sta;
+ u16 tid = params->tid;
enum ieee80211_ampdu_mlme_action action = params->action;

switch (action) {
@@ -6311,17 +6321,19 @@ rtl8xxxu_ampdu_action(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
dev_dbg(dev,
"Changed HT: ampdu_factor %02x, ampdu_density %02x\n",
ampdu_factor, ampdu_density);
- break;
+ return IEEE80211_AMPDU_TX_START_IMMEDIATE;
+ case IEEE80211_AMPDU_TX_STOP_CONT:
case IEEE80211_AMPDU_TX_STOP_FLUSH:
- dev_dbg(dev, "%s: IEEE80211_AMPDU_TX_STOP_FLUSH\n", __func__);
- rtl8xxxu_set_ampdu_factor(priv, 0);
- rtl8xxxu_set_ampdu_min_space(priv, 0);
- break;
case IEEE80211_AMPDU_TX_STOP_FLUSH_CONT:
- dev_dbg(dev, "%s: IEEE80211_AMPDU_TX_STOP_FLUSH_CONT\n",
- __func__);
+ dev_dbg(dev, "%s: IEEE80211_AMPDU_TX_STOP\n", __func__);
rtl8xxxu_set_ampdu_factor(priv, 0);
rtl8xxxu_set_ampdu_min_space(priv, 0);
+ priv->agg_state_bitmap &= ~BIT(tid);
+ ieee80211_stop_tx_ba_cb_irqsafe(vif, sta->addr, tid);
+ break;
+ case IEEE80211_AMPDU_TX_OPERATIONAL:
+ dev_dbg(dev, "%s: IEEE80211_AMPDU_TX_OPERATIONAL\n", __func__);
+ priv->agg_state_bitmap |= BIT(tid);
break;
case IEEE80211_AMPDU_RX_START:
dev_dbg(dev, "%s: IEEE80211_AMPDU_RX_START\n", __func__);
@@ -6893,7 +6905,6 @@ static int rtl8xxxu_probe(struct usb_interface *interface,
/*
* The firmware handles rate control
*/
- ieee80211_hw_set(hw, HAS_RATE_CONTROL);
ieee80211_hw_set(hw, AMPDU_AGGREGATION);

wiphy_ext_feature_set(hw->wiphy, NL80211_EXT_FEATURE_CQM_RSSI_LIST);
--
2.29.2


2021-05-14 02:35:58

by Reto Schneider

[permalink] [raw]
Subject: [PATCH 2/7] rtl8xxxu: add handle for mac80211 get_txpower

From: Chris Chiu <[email protected]>

add .get_txpower handle for mac80211 operations for `iw` and `wext`
tools to get the underlying tx power (max limit).

Signed-off-by: Chris Chiu <[email protected]>
(cherry picked from commit 2295263455630bd53eb51379e6e745b943d5017d)
Signed-off-by: Reto Schneider <[email protected]>
---

.../net/wireless/realtek/rtl8xxxu/rtl8xxxu.h | 3 +
.../realtek/rtl8xxxu/rtl8xxxu_8192c.c | 1 +
.../wireless/realtek/rtl8xxxu/rtl8xxxu_core.c | 72 ++++++++++++++++++-
3 files changed, 74 insertions(+), 2 deletions(-)

diff --git a/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu.h b/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu.h
index c15e4a52b9e5..3d16d6c9ff39 100644
--- a/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu.h
+++ b/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu.h
@@ -1319,6 +1319,7 @@ struct rtl8xxxu_priv {
struct rtl8723au_idx ht20_tx_power_diff[RTL8723B_TX_COUNT];
struct rtl8723au_idx ht40_tx_power_diff[RTL8723B_TX_COUNT];
struct rtl8xxxu_power_base *power_base;
+ u8 cur_cck_txpwridx, cur_ofdm24g_txpwridx;
u32 chip_cut:4;
u32 rom_rev:4;
u32 is_multi_func:1;
@@ -1427,6 +1428,7 @@ struct rtl8xxxu_fileops {
void (*disable_rf) (struct rtl8xxxu_priv *priv);
void (*usb_quirks) (struct rtl8xxxu_priv *priv);
u8 (*dbm_to_txpwridx)(struct rtl8xxxu_priv *priv, u16 mode, int dbm);
+ int (*get_tx_power)(struct rtl8xxxu_priv *priv);
void (*set_tx_power) (struct rtl8xxxu_priv *priv, int channel,
bool ht40);
void (*update_rate_mask) (struct rtl8xxxu_priv *priv,
@@ -1513,6 +1515,7 @@ u8 rtl8xxxu_gen1_dbm_to_txpwridx(struct rtl8xxxu_priv *priv,
u16 mode, int dbm);
void rtl8xxxu_gen1_set_tx_power(struct rtl8xxxu_priv *priv,
int channel, bool ht40);
+int rtl8xxxu_gen1_get_tx_power(struct rtl8xxxu_priv *priv);
void rtl8xxxu_gen1_config_channel(struct ieee80211_hw *hw);
void rtl8xxxu_gen2_config_channel(struct ieee80211_hw *hw);
void rtl8xxxu_gen1_usb_quirks(struct rtl8xxxu_priv *priv);
diff --git a/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu_8192c.c b/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu_8192c.c
index bb6df8cac82f..54f41af1015e 100644
--- a/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu_8192c.c
+++ b/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu_8192c.c
@@ -558,6 +558,7 @@ struct rtl8xxxu_fileops rtl8192cu_fops = {
.usb_quirks = rtl8xxxu_gen1_usb_quirks,
.dbm_to_txpwridx = rtl8xxxu_gen1_dbm_to_txpwridx,
.set_tx_power = rtl8xxxu_gen1_set_tx_power,
+ .get_tx_power = rtl8xxxu_gen1_get_tx_power,
.update_rate_mask = rtl8xxxu_update_rate_mask,
.report_connect = rtl8xxxu_gen1_report_connect,
.fill_txdesc = rtl8xxxu_fill_txdesc_v1,
diff --git a/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu_core.c b/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu_core.c
index e8459cb6035f..585018383712 100644
--- a/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu_core.c
+++ b/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu_core.c
@@ -1414,6 +1414,55 @@ rtl8xxxu_gen1_dbm_to_txpwridx(struct rtl8xxxu_priv *priv, u16 mode, int dbm)
return txpwridx;
}

+static int
+rtl8xxxu_gen1_txpwridx_to_dbm(struct rtl8xxxu_priv *priv, u16 mode, u8 idx)
+{
+ int offset;
+ int pwrout_dbm;
+
+ switch (mode) {
+ case WIRELESS_MODE_B:
+ offset = -7;
+ break;
+ case WIRELESS_MODE_G:
+ case WIRELESS_MODE_N_24G:
+ offset = -8;
+ break;
+ default:
+ offset = -8;
+ break;
+ }
+ pwrout_dbm = idx / 2 + offset;
+
+ return pwrout_dbm;
+}
+
+int
+rtl8xxxu_gen1_get_tx_power(struct rtl8xxxu_priv *priv)
+{
+ u8 txpwr_level;
+ int txpwr_dbm;
+
+ txpwr_level = priv->cur_cck_txpwridx;
+ txpwr_dbm = rtl8xxxu_gen1_txpwridx_to_dbm(priv, WIRELESS_MODE_B,
+ txpwr_level);
+ txpwr_level = priv->cur_ofdm24g_txpwridx +
+ priv->ofdm_tx_power_index_diff[1].a;
+
+ if (rtl8xxxu_gen1_txpwridx_to_dbm(priv, WIRELESS_MODE_G, txpwr_level)
+ > txpwr_dbm)
+ txpwr_dbm = rtl8xxxu_gen1_txpwridx_to_dbm(priv, WIRELESS_MODE_G,
+ txpwr_level);
+ txpwr_level = priv->cur_ofdm24g_txpwridx;
+ if (rtl8xxxu_gen1_txpwridx_to_dbm(priv, WIRELESS_MODE_N_24G,
+ txpwr_level) > txpwr_dbm)
+ txpwr_dbm = rtl8xxxu_gen1_txpwridx_to_dbm(priv,
+ WIRELESS_MODE_N_24G,
+ txpwr_level);
+
+ return txpwr_dbm;
+}
+
void
rtl8xxxu_gen1_set_tx_power(struct rtl8xxxu_priv *priv, int channel, bool ht40)
{
@@ -4540,6 +4589,19 @@ rtl8xxxu_wireless_mode(struct ieee80211_hw *hw, struct ieee80211_sta *sta)
return network_type;
}

+static int rtl8xxxu_get_txpower(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif, int *dbm)
+{
+ struct rtl8xxxu_priv *priv = hw->priv;
+
+ if (!priv->fops->get_tx_power)
+ return -EOPNOTSUPP;
+
+ *dbm = priv->fops->get_tx_power(priv);
+
+ return 0;
+}
+
static void rtl8xxxu_update_txpower(struct rtl8xxxu_priv *priv, int power)
{
bool ht40 = false;
@@ -4569,10 +4631,12 @@ static void rtl8xxxu_update_txpower(struct rtl8xxxu_priv *priv, int power)
ofdm_txpwridx = priv->fops->dbm_to_txpwridx(priv, WIRELESS_MODE_N_24G,
power);

- if (ofdm_txpwridx - priv->ofdm_tx_power_index_diff[1].a > 0)
+ if (ofdm_txpwridx - priv->ofdm_tx_power_index_diff[1].a > 0) {
+ /* refer to rtlefuse->legacy_ht_txpowerdiff in vendor driver */
ofdm_txpwridx -= priv->ofdm_tx_power_index_diff[1].a;
- else
+ } else {
ofdm_txpwridx = 0;
+ }

group = rtl8xxxu_gen1_channel_to_group(channel);

@@ -4586,6 +4650,9 @@ static void rtl8xxxu_update_txpower(struct rtl8xxxu_priv *priv, int power)
priv->ht40_1s_tx_power_index_B[i] = ofdm_txpwridx;
}

+ priv->cur_cck_txpwridx = priv->cck_tx_power_index_A[group];
+ priv->cur_ofdm24g_txpwridx = priv->ht40_1s_tx_power_index_A[group];
+
priv->fops->set_tx_power(priv, channel, ht40);
}

@@ -6542,6 +6609,7 @@ static const struct ieee80211_ops rtl8xxxu_ops = {
.set_key = rtl8xxxu_set_key,
.ampdu_action = rtl8xxxu_ampdu_action,
.sta_statistics = rtl8xxxu_sta_statistics,
+ .get_txpower = rtl8xxxu_get_txpower,
};

static int rtl8xxxu_parse_usb(struct rtl8xxxu_priv *priv,
--
2.29.2


2021-05-14 02:35:58

by Reto Schneider

[permalink] [raw]
Subject: [PATCH 4/7] rtl8xxxu: feed antenna information for mac80211

From: Chris Chiu <[email protected]>

(cherry picked from commit 557e87b5db655394a16c9b54a07a2fc11f1ea618)
Signed-off-by: Reto Schneider <[email protected]>
---

.../net/wireless/realtek/rtl8xxxu/rtl8xxxu_core.c | 15 +++++++++++++++
1 file changed, 15 insertions(+)

diff --git a/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu_core.c b/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu_core.c
index d5c53d6dec33..0a04ce00b1fe 100644
--- a/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu_core.c
+++ b/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu_core.c
@@ -1688,6 +1688,7 @@ static void rtl8xxxu_print_chipinfo(struct rtl8xxxu_priv *priv)
static int rtl8xxxu_identify_chip(struct rtl8xxxu_priv *priv)
{
struct device *dev = &priv->udev->dev;
+ struct ieee80211_hw *hw = priv->hw;
u32 val32, bonding;
u16 val16;

@@ -1764,6 +1765,8 @@ static int rtl8xxxu_identify_chip(struct rtl8xxxu_priv *priv)
priv->usb_interrupts = 1;
priv->has_wifi = 1;
}
+ hw->wiphy->available_antennas_tx = BIT(priv->tx_paths) - 1;
+ hw->wiphy->available_antennas_rx = BIT(priv->rx_paths) - 1;

switch (priv->rtl_chip) {
case RTL8188E:
@@ -4363,6 +4366,17 @@ static void rtl8xxxu_cam_write(struct rtl8xxxu_priv *priv,
rtl8xxxu_debug = tmp_debug;
}

+static
+int rtl8xxxu_get_antenna(struct ieee80211_hw *hw, u32 *tx_ant, u32 *rx_ant)
+{
+ struct rtl8xxxu_priv *priv = hw->priv;
+
+ *tx_ant = BIT(priv->tx_paths) - 1;
+ *rx_ant = BIT(priv->rx_paths) - 1;
+
+ return 0;
+}
+
static void rtl8xxxu_sw_scan_start(struct ieee80211_hw *hw,
struct ieee80211_vif *vif, const u8 *mac)
{
@@ -6609,6 +6623,7 @@ static const struct ieee80211_ops rtl8xxxu_ops = {
.set_key = rtl8xxxu_set_key,
.ampdu_action = rtl8xxxu_ampdu_action,
.sta_statistics = rtl8xxxu_sta_statistics,
+ .get_antenna = rtl8xxxu_get_antenna,
.get_txpower = rtl8xxxu_get_txpower,
};

--
2.29.2


2021-05-14 09:18:16

by Joe Perches

[permalink] [raw]
Subject: Re: [PATCH 2/7] rtl8xxxu: add handle for mac80211 get_txpower

On Fri, 2021-05-14 at 04:04 +0200, Reto Schneider wrote:
> From: Chris Chiu <[email protected]>
>
> add .get_txpower handle for mac80211 operations for `iw` and `wext`
> tools to get the underlying tx power (max limit).
[]
> diff --git a/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu_core.c b/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu_core.c
[]

> +int
> +rtl8xxxu_gen1_get_tx_power(struct rtl8xxxu_priv *priv)
> +{
> + u8 txpwr_level;
> + int txpwr_dbm;
> +
> + txpwr_level = priv->cur_cck_txpwridx;
> + txpwr_dbm = rtl8xxxu_gen1_txpwridx_to_dbm(priv, WIRELESS_MODE_B,
> + txpwr_level);
> + txpwr_level = priv->cur_ofdm24g_txpwridx +
> + priv->ofdm_tx_power_index_diff[1].a;
> +
> + if (rtl8xxxu_gen1_txpwridx_to_dbm(priv, WIRELESS_MODE_G, txpwr_level)
> + > txpwr_dbm)
> + txpwr_dbm = rtl8xxxu_gen1_txpwridx_to_dbm(priv, WIRELESS_MODE_G,
> + txpwr_level);

probably better to use a temporaries instead of multiple calls.

foo = rtl8xxxu_gen1_txpwridx_to_dbm(priv, WIRELESS_MODE_G, txpwr_level);
if (foo > txpwr_dbm)
txpwr_dbm = foo;

> + txpwr_level = priv->cur_ofdm24g_txpwridx;
> + if (rtl8xxxu_gen1_txpwridx_to_dbm(priv, WIRELESS_MODE_N_24G,
> + txpwr_level) > txpwr_dbm)
> + txpwr_dbm = rtl8xxxu_gen1_txpwridx_to_dbm(priv,
> + WIRELESS_MODE_N_24G,
> + txpwr_level);
> +
> + return txpwr_dbm;

foo = rtl8xxxu_gen1_txpwridx_to_dbm(priv, WIRELESS_MODE_N_24G, txpwr_level);

return min(txpwr_dbm, foo);