mt7915: missing 2.4 GHz noise on Linksys E7350 with ECO0 / CCA firmware path

Open
#1,171 0 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
5/5
Estimated time
Over a week
Newbie friendliness
25/100
Issue type
Bug
Clarity
Mostly clear
Activity status
Active
Tech stack
c, linux

Research direction

Start with mt7915/mac.c at the linked mt7915_phy_get_nf() and mt7915_update_channel() code, then inspect how the WM selects IPI or CCA for each ECO revision. The issue establishes a missing noise report on one ECO0 device and a temporary CCA publication test, but leaves CCA validity conditions and RF semantics unresolved. Done means a revision-appropriate source publishes valid hardware-derived survey noise without fixed values; the issue does not identify a test file.

Written by the indexing model from the issue text.

Description

Problem

On a Linksys E7350 v1, MT7915D in 2x2+2x2 DBDC mode, the stock mt7915e driver omits the 2.4 GHz survey noise attribute and ubus iwinfo reports noise: 0. Wi-Fi otherwise operates. This is reproducible on a fixed channel; it is not just a LuCI display issue.

A TP-Link EAP615-Wall v1 using the same mt7915 driver/WM files reports approximately -92 dBm through its populated IPI histogram. The devices differ in an ECO register and the firmware's selected noise measurement path. An experimental CCA companion probe on the E7350 recovered normal survey/iwinfo publication for 15 seconds without replacing firmware or reloading the stock driver, then restored the original missing-noise state.

What is established: empty IPI histograms on this E7350, an ECO-dependent CCA/IPI selection in the exact WM, and successful temporary CCA-to-survey publication. What is not established: that every ECO0 board is affected, why the silicon leaves IPI empty, absolute RF accuracy of CCA, or a production-ready permanent fix.

Expected behavior

Report a valid hardware-derived noise estimate for supported MT7915 revisions through nl80211 survey/iwinfo. If CCA requires additional validity/idle conditions, establish those conditions before using it for SURVEY_INFO_NOISE_DBM; do not substitute a fixed noise value.

Reproduction and evidence

Reproduce the unmodified-driver symptom

On the E7350, with 2.4 GHz AP enabled on channel 1 at HE20/20 MHz:

iw dev phy0-ap0 info
iw dev phy0-ap0 survey dump
ubus call iwinfo info '{"device":"phy0-ap0"}' | jsonfilter -e '@.noise'

The in-use 2412 MHz survey has active/busy/RX/TX times but no noise line. The final command returns 0. The value 0 means unavailable in this observation; it is not an RF measurement of 0 dBm. The CLI iwinfo fallback seen during investigation must not be treated as a real -100 dBm sample either.

mt7915_phy_get_nf() reads the IPI/IRPI histogram, sums the sample counts, and returns 0 when their sum is zero. mt7915_update_channel() consequently leaves state->noise at zero. There is no alternate CCA branch or existing CCA/IPI user setting in this stock driver:

Exact installed mt76 source, mt7915/mac.c.

Hardware and runtime comparison
Observation Linksys E7350 v1 (affected) TP-Link EAP615-Wall v1 (comparison)
2.4 GHz IPI bins, both chains All 11 bins per chain remain zero across repeated captures Nonzero, mainly first bin
Survey noise Absent About -92 dBm
0x70010200 0x7915 0x7915
0x70010204 0x8a10 0x8a10
FWVersion 0x70010208 0x8a01 0x8a01
Register identified as ECO by WM, 0x70010210 0 1
WM route flag at 0xe00d2c90 0 1
WM callback pointer at 0xe00d2d5c 0xe0058be2 (CCA idle power) 0xe0058aa6 (IPI)

The MCU memory layout above applies only to the exact WM hash below. Four positive controls against known string words were validated before interpreting MCU state: 0xe0092338=0x234f4345, 0xe009233c=0x000a6425, 0xe008e85c=0x3a207325, 0xe008e860=0x74655320. Three repeated runtime route reads per device agreed with the disassembly. The report timers were inactive: the callback pointers demonstrate configuration, not that those callbacks were running periodically.

The ECO register is not the OEM E1/E2 firmware selector. The original driver's firmware selection uses FWVersion 0x70010208, which is identical on both devices and selects E2. These observations do not justify loading E1 firmware.

For read-only hardware inspection with debugfs, this reads the selectors/CCA words/IPI bins and restores the software register selector. It does not write regval, pulse any hardware control, or access MCU runtime memory:

set -eu
base=/sys/kernel/debug/ieee80211/phy0/mt76
saved=$(cat "$base/regidx")
trap 'printf "%s\n" "$saved" > "$base/regidx"' EXIT
for addr in 0x70010200 0x70010204 0x70010208 0x70010210 0x830884f0 0x830984f0 0x830a60ac 0x830a60b0; do
    printf '%s\n' "$addr" > "$base/regidx"
    printf '%s=' "$addr"
    cat "$base/regval"
done
for chain in 0 1; do
    bin=0
    while [ "$bin" -lt 11 ]; do
        addr=$((0x83006000 + (chain << 16) + 4 * bin))
        printf '0x%x\n' "$addr" > "$base/regidx"
        printf 'chain=%s bin=%s value=' "$chain" "$bin"
        cat "$base/regval"
        bin=$((bin + 1))
    done
done
Firmware/original-driver findings

The installed WM reads the two 2.4 GHz CCA fields at 0x830a60ac and 0x830a60b0 as (word & 0x3fe00) >> 9. Its feature query for 0x10000 chooses CCA for the observed ECO0 case and IPI for ECO1. The original Linksys WM has the same selection logic; its feature-query and CCA-reader functions match the installed WM counterparts byte for byte. This argues against treating the problem as a regression unique to the newer firmware blob.

The exact official Linksys V1.1.00.032 image was analyzed offline; OEM was not booted, and correct OEM UI noise reporting was not observed. The old V1.0.00.024 GPL objects are a different build and were not used as proof of the newer kernel's behavior.

In the extracted V1.1.00.032 kernel (load address 0x81001000), the Average Power consumer at 0x814b4dbc-0x814b4e18 converts negative nine-bit codes as:

raw9 = (register_word & 0x3fe00) >> 9
# Only the negative encoding domain 256..511 was validated:
integer_dBm = floor((raw9 - 512) / 2)
# 318 -> -97, 319 -> -97, 320 -> -96

The equivalence was checked for all 256 negative encodings. Codes outside that domain are rejected by the experiment; no generic conversion for them is assumed. WM averages raw words per chain before the host conversion. Its band-specific accumulator reset pulses bit 30 of CCA control; the experiment writes only band 0, 0x830884f0, preserving every other bit. Both observed CCA controls were 0x33ca175b. No band-1 reset or MCU noise-report command was issued. The traced extended noise-report event is 0xb8; 0xd0 is its length, not the event ID.

For independent image identification:

OEM image: FW_E7350_V1.1.00.032_Prod_20220902_code.bin
SHA256: 98df04683ee19072771bf5c5cb1d231353d41734a578925e8cd04b2731346d6f
Extracted uncompressed kernel SHA256:
c7c0ebf602ee7bad61cec70b00e7d401ba2e3d99bca266cb9273aab61589c831
Installed WM: feature helper file offset 0x8d2a0, CCA reader 0xb4f54
OEM WM:      feature helper file offset 0x8a67c, CCA reader 0xb17b4

Official Linksys firmware source page.

Interventions and measurement limits
  • A 2.4 GHz restart and the examined IRPI reset pulse did not populate IPI bins. The pulse/control state was restored.
  • The driver already includes the known running-state handling for MCU recovery. No WM/WA recovery resets were reported in the earlier checked baseline. This is not being reported as that already-fixed recovery bug.
  • Passive CCA readings include large, short transients. In 360 Linksys intervals, 28 contained a peak above -80 dBm; 27 had measurable TX/RX activity and one did not at the exported millisecond resolution. Filtering only exported TX/RX counters is insufficient.
  • Using reset windows helps in some captures but does not eliminate all transients. No arbitrary clipping or fixed -97 dBm was used.
  • A temporary channel 1 -> 6 -> 11 -> 1 test, HE20 throughout, produced four ten-sample estimates per stage:
Stage Estimates, dBm
1, first visit -97, -83, -83, -97
6 -97, -94, -91, -83
11 -96, -96, -97, -93
1, return -97, -97, -97, -96

These are experimental CCA estimates, not stock survey output. The repeat-channel difference prevents assigning the peaks solely to channel. The TP-Link was on a different channel and is a control for functioning IPI publication, not an RF calibration reference.

Live A/B/A publication test

An explicitly loaded companion module, built against the original private driver structures, sampled band-0 CCA approximately every 100 ms. After ten raw samples per chain it applied the OEM conversion, combined the two chain magnitudes, and updated the existing noise cache using mt76's usual EWMA. It left the stock driver and firmware installed. It had a 15-second automatic deadline and restored the original cache on expiration; the module was then unloaded.

Sanitized observations from the saved test (survey and iwinfo queries are sequential):

phase       survey noise     iwinfo noise    probe state
before      absent           0              not loaded
~1.1 s      absent           -97            active=1 samples=10 reports=1 invalid=0
~2.2 s      -96 dBm          -96            active=1 samples=20 reports=2 invalid=0
~4.4 s      -97 dBm          -97            active=1 samples=40 reports=4 invalid=0
~13.1 s     -95 dBm          -95            active=1 samples=119 reports=11 invalid=0
expired     absent           0              active=0 samples=134 reports=13 invalid=0
after rmmod absent           0              not loaded

The first survey was queried before that window's update, explaining the first iwinfo/survey mismatch. During the run, published survey values ranged from -97 to -95 dBm. There were 134 samples and 13 completed reports, no unsupported codes. Eleven clients remained associated; all association timers advanced 21 seconds, and interface indices stayed unchanged. Both 2.4 GHz BSS remained channel 1/20 MHz; 5 GHz remained channel 36/40 MHz. Wireless configuration and stock module hashes were unchanged. Both CCA controls ended at 0x33ca175b, selector at zero, and the probe/debugfs/temporary files were removed.

This demonstrates that CCA-derived values can traverse the existing survey/iwinfo path on this hardware. It does not establish calibrated idle-noise semantics, nor validate the complete patched driver under long-term operation.

Environment

Field Affected device / tested configuration
Brand/model Linksys E7350 v1, linksys,e7350
SoC / target MT7621AT, ramips/mt7621, mipsel_24kc
Wi-Fi MT7915D, PCI 14c3:7915, DBDC 2x2 per band
Tested scope 2.4 GHz AP, HE20, channel 1; temporary channel 6/11 comparison
OpenWrt SNAPSHOT r37770-79aba5e092
Kernel 6.18.55
mac80211/backports 6.18.55.7.2-r11 / backports 7.2
mt76 source 467ae2e3f190baf737e15e318906da0dc7d0ab91
Installed WM build 20240429200752

The TP-Link EAP615-Wall v1 comparison was brought to the same OpenWrt revision. The following files were verified byte-identical between both devices:

mt7915e.ko: 293a5ae3f77361773e4108a4d42a5c67a840f2e6e77b39ff76b2aea65090ee6b
mt76.ko:    9fa8727281d6667711af2b5c0b7cee2381080903705a92a547763a6dbe77c8af
mac80211.ko:33ef473718ab83dcfbc2ac6e6fac1707ba0c7417ea515e64e747606326e80778
mt7915_wm.bin:
73ae4c95fcef55f2e537e2d122d267d4cb666f896e96ec5e88573839bcf985b2

Historical saved observations also show the symptom when both devices used r36779-9b95be917b; no controlled downgrade was performed. This is not proven to be a regression introduced by the current snapshot.

Cause indicated by the evidence and proposed fix

The evidence strongly points to missing ECO-aware noise acquisition in the host driver: mt76 assumes populated IPI for both revisions, while the firmware explicitly chooses CCA for the affected case. That explains the immediate zero-return/publication failure. It does not prove that the firmware's selected callback is itself what causes all IPI counters to remain empty; the ECO selection was not forcibly changed.

A potential permanent fix is to select the valid noise source for each supported revision, retaining IPI where it works and adding a properly validated CCA path where required. The experimental patch below is deliberately opt-in and restricted to this tested E7350/ECO0/2.4 GHz/20 MHz combination. It averages ten samples, enforces minimum spacing, rejects invalid/stale data, and resets state across channel/radio transitions. It is a discussion candidate, not a request to merge board-specific magic controls as-is.

Maintainer input is particularly needed on documented CCA validity, TX/RX exclusion, sampling/reset semantics, and whether an MCU-supported interface should be preferred over host register access. Hardware-source selection should be generalized only with evidence for other revisions/boards. A configurable stock CCA/IPI switch was not found or tested: cca_nf=0/1 is introduced only by this candidate.

The candidate compiled against the exact mt76 revision; rebuilding the unchanged baseline reproduced .text, .rodata, .data and .modinfo byte for byte for mt76, mt76-connac-lib and mt7915e. Native tests extracting the actual C functions fail for the original empty-IPI case and pass for the candidate's integration, cadence, averaging, invalid/stale samples, reset lifecycle and scope gates. The companion's publication/expiry/rollback functions were also tested. The complete patched mt7915e module has not been loaded; only the separate bounded companion was tested live. RF calibration, recovery/suspend coverage and long-duration stability remain open.

Acceptance

  • Recover a valid hardware-derived 2.4 GHz noise estimate on this E7350 without a fabricated constant.
  • Preserve existing IPI behavior on unaffected revisions/devices and avoid interference with the other DBDC band.
  • Validate the source's idle/noise meaning and invalid-sample handling, including channel switches and recovery.
  • Verify the complete driver change on hardware; the temporary companion test alone is not final acceptance.

Related work

  • #88: older broad ACS/missing-noise reports; no matching E7350 ECO0/CCA investigation found in the discussion reviewed.
  • #270: closed MT76x2E noise-availability report, different hardware and era.
  • #116: older generic survey-noise PR, closed unmerged; this case already reaches the current survey path when a valid cache value is supplied.
Experimental driver patch against mt76 467ae2e3 (compiled and native-tested; not loaded)
diff --git a/mt7915/mac.c b/mt7915/mac.c
index 06cddff..4c04bcc 100644
--- a/mt7915/mac.c
+++ b/mt7915/mac.c
@@ -2,7 +2,9 @@
 /* Copyright (C) 2020 MediaTek Inc. */
 
 #include <linux/etherdevice.h>
+#include <linux/module.h>
 #include <linux/timekeeping.h>
+#include <linux/of.h>
 #include "coredump.h"
 #include "mt7915.h"
 #include "../dma.h"
@@ -11,6 +13,13 @@
 
 #define to_rssi(field, rcpi)	((FIELD_GET(field, rcpi) - 220) / 2)
 
+/* Experimental, opt-in and restricted to the board/band measured in this
+ * investigation. Do not infer support for other ECO0 boards or 5 GHz.
+ */
+static bool cca_nf;
+module_param(cca_nf, bool, 0444);
+MODULE_PARM_DESC(cca_nf, "Experimental E7350 ECO0 2.4 GHz CCA noise reporting");
+
 static const struct mt7915_dfs_radar_spec etsi_radar_specs = {
 	.pulse_th = { 110, -10, -80, 40, 5200, 128, 5200 },
 	.radar_pattern = {
@@ -1093,6 +1102,91 @@ void mt7915_queue_rx_skb(struct mt76_dev *mdev, enum mt76_rxq_id q,
 	}
 }
 
+static bool mt7915_use_cca_nf(struct mt7915_phy *phy)
+{
+	struct mt7915_dev *dev = phy->dev;
+
+	return cca_nf && is_mt7915(&dev->mt76) && dev->dbdc_support &&
+	       !phy->mt76->band_idx && phy->mt76->chainmask == 3 &&
+	       phy->mt76->chandef.chan &&
+	       phy->mt76->chandef.chan->band == NL80211_BAND_2GHZ &&
+	       phy->mt76->chandef.width == NL80211_CHAN_WIDTH_20 &&
+	       of_machine_is_compatible("linksys,e7350") &&
+	       !mt76_rr(dev, MT7915_HW_ECO);
+}
+
+static void mt7915_reset_cca_nf(struct mt7915_phy *phy)
+{
+	mt7915_nf_cca_clear(&phy->cca_nf);
+	phy->cca_nf_started = false;
+	phy->cca_nf_last = 0;
+	phy->noise = 0;
+	phy->mt76->chan_state->noise = 0;
+}
+
+/* WM b4e52: pulse only the band-0 idle-power accumulator's bit 30.
+ * The exact observed configuration is required for this experimental path;
+ * no other CCA settings are programmed, and band 1 is never written.
+ */
+static bool mt7915_reset_cca_window(struct mt7915_phy *phy)
+{
+	struct mt7915_dev *dev = phy->dev;
+	u32 ctrl = mt76_rr(dev, MT7915_CCA_IDLE_CTRL0);
+
+	if (ctrl != 0x33ca175b)
+		return false;
+
+	mt76_set(dev, MT7915_CCA_IDLE_CTRL0, MT7915_CCA_IDLE_RESET);
+	mt76_clear(dev, MT7915_CCA_IDLE_CTRL0, MT7915_CCA_IDLE_RESET);
+
+	return mt76_rr(dev, MT7915_CCA_IDLE_CTRL0) == ctrl;
+}
+
+static u8 mt7915_get_cca_nf(struct mt7915_phy *phy)
+{
+	struct mt7915_dev *dev = phy->dev;
+	unsigned long now = jiffies;
+	u16 raw0, raw1;
+	int nf;
+
+	if (!test_bit(MT76_STATE_RUNNING, &phy->mt76->state)) {
+		mt7915_reset_cca_nf(phy);
+		return 0;
+	}
+
+	/* Survey polling must not shorten the window or weight cached values. */
+	if (phy->cca_nf_started &&
+	    time_before(now, phy->cca_nf_last + msecs_to_jiffies(100)))
+		return 0;
+
+	if (!phy->cca_nf_started ||
+	    time_after(now, phy->cca_nf_last + HZ)) {
+		mt7915_reset_cca_nf(phy);
+		if (mt7915_reset_cca_window(phy)) {
+			phy->cca_nf_started = true;
+			phy->cca_nf_last = jiffies;
+		}
+		return 0;
+	}
+
+	raw0 = FIELD_GET(MT7915_CCA_IDLE_PWR_MASK,
+			 mt76_rr(dev, MT7915_CCA_IDLE_PWR0));
+	raw1 = FIELD_GET(MT7915_CCA_IDLE_PWR_MASK,
+			 mt76_rr(dev, MT7915_CCA_IDLE_PWR1));
+	if (!mt7915_reset_cca_window(phy)) {
+		mt7915_reset_cca_nf(phy);
+		return 0;
+	}
+	phy->cca_nf_last = jiffies;
+	nf = mt7915_nf_cca_add(&phy->cca_nf, raw0, raw1);
+	if (nf < 0) {
+		mt7915_reset_cca_nf(phy);
+		return 0;
+	}
+
+	return nf;
+}
+
 void mt7915_mac_cca_stats_reset(struct mt7915_phy *phy)
 {
 	struct mt7915_dev *dev = phy->dev;
@@ -1120,6 +1214,8 @@ void mt7915_mac_reset_counters(struct mt7915_phy *phy)
 		 MT_WF_RMAC_MIB_RXTIME_CLR);
 
 	mt7915_mcu_get_chan_mib_info(phy, true);
+	if (mt7915_use_cca_nf(phy) || phy->cca_nf_started)
+		mt7915_reset_cca_nf(phy);
 }
 
 void mt7915_mac_set_timing(struct mt7915_phy *phy)
@@ -1230,7 +1326,13 @@ void mt7915_update_channel(struct mt76_phy *mphy)
 
 	mt7915_mcu_get_chan_mib_info(phy, false);
 
-	nf = mt7915_phy_get_nf(phy, phy->mt76->band_idx);
+	if (mt7915_use_cca_nf(phy)) {
+		nf = mt7915_get_cca_nf(phy);
+	} else {
+		if (phy->cca_nf_started)
+			mt7915_reset_cca_nf(phy);
+		nf = mt7915_phy_get_nf(phy, phy->mt76->band_idx);
+	}
 	if (!phy->noise)
 		phy->noise = nf << 4;
 	else if (nf)
diff --git a/mt7915/mt7915.h b/mt7915/mt7915.h
index 7ee9bed..2c6b916 100644
--- a/mt7915/mt7915.h
+++ b/mt7915/mt7915.h
@@ -8,6 +8,7 @@
 #include <linux/ktime.h>
 #include "../mt76_connac.h"
 #include "regs.h"
+#include "nf-cca.h"
 
 #define MT7915_MAX_INTERFACES		19
 #define MT7915_WTBL_SIZE		288
@@ -225,6 +226,9 @@ struct mt7915_phy {
 	u64 omac_mask;
 
 	u16 noise;
+	struct mt7915_nf_cca cca_nf;
+	unsigned long cca_nf_last;
+	bool cca_nf_started;
 
 	s16 coverage_class;
 	u8 slottime;
diff --git a/mt7915/regs.h b/mt7915/regs.h
index dc7bb9a..e06d4b6 100644
--- a/mt7915/regs.h
+++ b/mt7915/regs.h
@@ -1204,6 +1204,12 @@ enum offs_rev {
 
 /* PHY */
 #define MT_WF_PHY_BASE			0x83080000
+#define MT7915_HW_ECO			0x70010210
+#define MT7915_CCA_IDLE_CTRL0		0x830884f0
+#define MT7915_CCA_IDLE_RESET		BIT(30)
+#define MT7915_CCA_IDLE_PWR0		0x830a60ac
+#define MT7915_CCA_IDLE_PWR1		0x830a60b0
+#define MT7915_CCA_IDLE_PWR_MASK		GENMASK(17, 9)
 #define MT_WF_PHY(ofs)			(MT_WF_PHY_BASE + (ofs))
 
 #define MT_WF_PHY_RX_CTRL1(_phy)	MT_WF_PHY(0x2004 + ((_phy) << 16))
diff --git a/mt7915/nf-cca.h b/mt7915/nf-cca.h
new file mode 100644
index 0000000..0bd27ea
--- /dev/null
+++ b/mt7915/nf-cca.h
@@ -0,0 +1,49 @@
+/* SPDX-License-Identifier: BSD-3-Clause-Clear */
+#ifndef __MT7915_NF_CCA_H
+#define __MT7915_NF_CCA_H
+
+#include <linux/types.h>
+
+#define MT7915_NF_CCA_SAMPLES 10
+
+struct mt7915_nf_cca {
+	u32 sum[2];
+	u8 count;
+};
+
+static inline void mt7915_nf_cca_clear(struct mt7915_nf_cca *state)
+{
+	state->sum[0] = 0;
+	state->sum[1] = 0;
+	state->count = 0;
+}
+
+/* Return -1 for an unsupported encoding, 0 while collecting, else |dBm|.
+ * Match the OEM order: average raw words per chain, convert to integer
+ * dBm, then average the two chains. No clipping or TX/RX threshold.
+ */
+static inline int mt7915_nf_cca_add(struct mt7915_nf_cca *state,
+				  u16 raw0, u16 raw1)
+{
+	u32 avg0, avg1, nf0, nf1;
+
+	if (raw0 < 256 || raw0 > 511 || raw1 < 256 || raw1 > 511) {
+		mt7915_nf_cca_clear(state);
+		return -1;
+	}
+
+	state->sum[0] += raw0;
+	state->sum[1] += raw1;
+	if (++state->count < MT7915_NF_CCA_SAMPLES)
+		return 0;
+
+	avg0 = state->sum[0] / MT7915_NF_CCA_SAMPLES;
+	avg1 = state->sum[1] / MT7915_NF_CCA_SAMPLES;
+	nf0 = (513 - avg0) / 2;
+	nf1 = (513 - avg1) / 2;
+	mt7915_nf_cca_clear(state);
+
+	return (nf0 + nf1) / 2;
+}
+
+#endif
Companion probe source used for the live test (private ABI, seconds=15)

This is diagnostic source, not a portable or production module. The include layout used an include/mt7915/ copy of the unmodified mt7915.h and regs.h from the exact mt76 commit; include/mt76_connac.h points to that revision's mt76_connac.h. The CCA helper at ../patched/mt7915/nf-cca.h is included in the patch above. The module Makefile was obj-m += mt7915-cca-probe.o and ccflags-y += -Werror -DCONFIG_MT76_LEDS. It was built using the matching kernel/backport headers and stock configuration. A different kernel/private-structure layout must not be used. The driver-patch binary itself was not inserted.

// SPDX-License-Identifier: GPL-2.0-only
/* Temporary, explicitly loaded diagnostic. No driver reload or UCI changes.
 * Built against the original private headers whose baseline .text/.rodata
 * were reproduced byte for byte. This is not a portable module interface.
 */
#include <linux/debugfs.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/pci.h>
#include <linux/seq_file.h>
#include <linux/workqueue.h>
#include "include/mt7915/mt7915.h"
#include "../patched/mt7915/nf-cca.h"

#define CCA_CTRL0 0x830884f0
#define CCA_CTRL1 0x830984f0
#define CCA_RESET BIT(30)
#define CCA_POWER0 0x830a60ac
#define CCA_POWER1 0x830a60b0
#define CCA_POWER_MASK GENMASK(17, 9)

static unsigned int seconds = 30;
module_param(seconds, uint, 0444);
MODULE_PARM_DESC(seconds, "Bounded diagnostic duration, 5..60 seconds");

static struct {
	struct pci_dev *pdev;
	struct module *parent;
	struct mt7915_dev *dev;
	struct mt7915_phy *phy;
	struct mt76_channel_state *channel;
	struct ieee80211_channel *frequency;
	enum nl80211_chan_width width;
	struct delayed_work work;
	struct dentry *debugfs;
	struct mt7915_nf_cca accumulator;
	unsigned long deadline;
	u16 saved_phy_noise;
	s8 saved_channel_noise;
	u16 last_raw[2];
	u32 samples;
	u32 reports;
	u32 invalid;
	bool stopped;
} probe;

static bool reset_window(void)
{
	u32 original = mt76_rr(probe.dev, CCA_CTRL0);

	if (original != 0x33ca175b ||
	    mt76_rr(probe.dev, CCA_CTRL1) != 0x33ca175b)
		return false;
	mt76_set(probe.dev, CCA_CTRL0, CCA_RESET);
	mt76_clear(probe.dev, CCA_CTRL0, CCA_RESET);
	return mt76_rr(probe.dev, CCA_CTRL0) == original;
}

/* Caller holds the same mutex as the stock survey updater. */
static void stop_probe(void)
{
	if (probe.stopped)
		return;
	probe.phy->noise = probe.saved_phy_noise;
	probe.channel->noise = probe.saved_channel_noise;
	if (probe.phy->mt76->chan_state != probe.channel)
		probe.phy->mt76->chan_state->noise = 0;
	mt7915_nf_cca_clear(&probe.accumulator);
	probe.stopped = true;
}

static void sample_work(struct work_struct *work)
{
	struct mt76_phy *mphy = probe.phy->mt76;
	int nf;

	(void)work;
	mutex_lock(&probe.dev->mt76.mutex);
	if (time_after_eq(jiffies, probe.deadline) ||
	    !test_bit(MT76_STATE_RUNNING, &mphy->state) ||
	    test_bit(MT76_RESET, &mphy->state) ||
	    mphy->chan_state != probe.channel ||
	    mphy->chandef.chan != probe.frequency ||
	    mphy->chandef.width != probe.width) {
		stop_probe();
		goto out;
	}

	probe.last_raw[0] = FIELD_GET(CCA_POWER_MASK,
				     mt76_rr(probe.dev, CCA_POWER0));
	probe.last_raw[1] = FIELD_GET(CCA_POWER_MASK,
				     mt76_rr(probe.dev, CCA_POWER1));
	if (!reset_window()) {
		stop_probe();
		goto out;
	}
	probe.samples++;
	nf = mt7915_nf_cca_add(&probe.accumulator,
			       probe.last_raw[0], probe.last_raw[1]);
	if (nf < 0) {
		probe.invalid++;
		probe.phy->noise = 0;
		probe.channel->noise = 0;
	} else if (nf) {
		if (!probe.phy->noise)
			probe.phy->noise = nf << 4;
		else
			probe.phy->noise += nf - (probe.phy->noise >> 4);
		probe.channel->noise = -(probe.phy->noise >> 4);
		probe.reports++;
	}
	queue_delayed_work(system_wq, &probe.work, msecs_to_jiffies(100));
out:
	mutex_unlock(&probe.dev->mt76.mutex);
}

static int status_show(struct seq_file *seq, void *data)
{
	(void)data;
	mutex_lock(&probe.dev->mt76.mutex);
	seq_printf(seq,
		   "active=%u samples=%u reports=%u invalid=%u raw0=%u raw1=%u noise=%d\n",
		   !probe.stopped, probe.samples, probe.reports, probe.invalid,
		   probe.last_raw[0], probe.last_raw[1], probe.channel->noise);
	mutex_unlock(&probe.dev->mt76.mutex);
	return 0;
}
DEFINE_SHOW_ATTRIBUTE(status);

static int __init probe_init(void)
{
	struct mt76_dev *mdev;
	struct dentry *status;
	int ret = -ENODEV;

	if (seconds < 5 || seconds > 60 ||
	    !of_machine_is_compatible("linksys,e7350"))
		return -EINVAL;
	probe.pdev = pci_get_device(PCI_VENDOR_ID_MEDIATEK, 0x7915, NULL);
	if (!probe.pdev)
		return -ENODEV;
	device_lock(&probe.pdev->dev);
	if (!probe.pdev->dev.driver ||
	    strcmp(probe.pdev->dev.driver->name, "mt7915e"))
		goto unlock_device;
	probe.parent = probe.pdev->dev.driver->owner;
	if (!probe.parent || !try_module_get(probe.parent))
		goto unlock_device;
	mdev = pci_get_drvdata(probe.pdev);
	if (!mdev)
		goto put_parent;
	probe.dev = container_of(mdev, struct mt7915_dev, mt76);
	probe.phy = &probe.dev->phy;
	mutex_lock(&mdev->mutex);
	if (!is_mt7915(mdev) || !probe.dev->dbdc_support ||
	    probe.phy->mt76 != &mdev->phy ||
	    mdev->phy.band_idx || mdev->phy.chainmask != 3 ||
	    !test_bit(MT76_STATE_RUNNING, &mdev->phy.state) ||
	    test_bit(MT76_RESET, &mdev->phy.state) ||
	    !mdev->phy.chandef.chan ||
	    mdev->phy.chandef.chan->band != NL80211_BAND_2GHZ ||
	    mdev->phy.chandef.width != NL80211_CHAN_WIDTH_20 ||
	    mt76_rr(probe.dev, 0x70010200) != 0x7915 ||
	    mt76_rr(probe.dev, 0x70010204) != 0x8a10 ||
	    mt76_rr(probe.dev, 0x70010208) != 0x8a01 ||
	    mt76_rr(probe.dev, 0x70010210) != 0 ||
	    probe.phy->noise || mdev->phy.chan_state->noise)
		goto unlock_mdev;
	probe.channel = mdev->phy.chan_state;
	probe.frequency = mdev->phy.chandef.chan;
	probe.width = mdev->phy.chandef.width;
	probe.saved_phy_noise = probe.phy->noise;
	probe.saved_channel_noise = probe.channel->noise;
	probe.debugfs = debugfs_create_dir("mt7915_cca_probe", NULL);
	if (IS_ERR_OR_NULL(probe.debugfs)) {
		ret = probe.debugfs ? PTR_ERR(probe.debugfs) : -ENOMEM;
		goto unlock_mdev;
	}
	status = debugfs_create_file("status", 0400, probe.debugfs, NULL,
				     &status_fops);
	if (IS_ERR_OR_NULL(status)) {
		ret = status ? PTR_ERR(status) : -ENOMEM;
		debugfs_remove_recursive(probe.debugfs);
		goto unlock_mdev;
	}
	if (!reset_window()) {
		debugfs_remove_recursive(probe.debugfs);
		goto unlock_mdev;
	}
	INIT_DELAYED_WORK(&probe.work, sample_work);
	probe.deadline = jiffies + seconds * HZ;
	queue_delayed_work(system_wq, &probe.work, msecs_to_jiffies(100));
	mutex_unlock(&mdev->mutex);
	device_unlock(&probe.pdev->dev);
	return 0;

unlock_mdev:
	mutex_unlock(&mdev->mutex);
put_parent:
	module_put(probe.parent);
unlock_device:
	device_unlock(&probe.pdev->dev);
	pci_dev_put(probe.pdev);
	return ret;
}

static void __exit probe_exit(void)
{
	cancel_delayed_work_sync(&probe.work);
	mutex_lock(&probe.dev->mt76.mutex);
	stop_probe();
	mutex_unlock(&probe.dev->mt76.mutex);
	debugfs_remove_recursive(probe.debugfs);
	module_put(probe.parent);
	pci_dev_put(probe.pdev);
}

module_init(probe_init);
module_exit(probe_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Bounded E7350 2.4 GHz OEM CCA diagnostic without driver reload");

SHA-256 of the stripped companion module tested: eee2724e39d59472324071fd5412c4e19de9e89a34d3f4421086685329e5173e.

Dominant language
C
Stars
895
Forks
461
PR merge metrics
No merged PRs in 30d

Getting set up

This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from openwrt/mt76

All issues in openwrt/mt76

Similar issues

More C issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.