I have a Lenovo IdeaPad S340 laptop with an Intel WiFi adapter (Intel Dual Band Wireless AC 9462, to be precise) running Ubuntu Linux 19.04. When I use most WiFi networks, download and upload speeds are both fine, but on one network in particular (which uses WiFi access points from Cisco), my upload speed is atrocious, less than 1Mbps even when my phone connected to the same network gets upload speeds of more than 50Mbps.
I Googled around trying to find other people with similar issues and how they were able to solve them, and I found a lot of people recommending changing the configuration of the iwlwifi
kernel module to specify 11n_disable=1
or 11n_disable=8
to solve the problem. I tried them both:
11n_disable=1
improved my upload speed, but it also cut my download speed in half, which is obviously unacceptable.11n_disable=8
didn’t improve my upload speed.
So no luck there. However, with further testing, I discovered that 11n_disable=2
makes my upload speed seven times faster. At first glance, then, that would seem to be the solution, but alas, it’s not that simple.
Unfortunately, in addition to improving my upload speed, that setting also decreases my download speed, by about 12%. That’s an acceptable trade-off on a WiFi network whose upload speed is otherwise unusable, but it also decreases my upload speed on WiFi networks that don’t have upload speed issues. I don’t want to make my download speed 12% slower when I don’t need to.
Alas, 11n_disable
is not something you can tweak in the settings for a particular WiFi network. It’s a kernel module setting that can only be set when the kernel module is loaded. To change the setting, therefore, you have to unload the kernel module (thus taking down your WiFi connection) and then reload it with the new setting.
I therefore decided to install a script which runs whenever I connect to a WiFi network, determines based on the name of the network whether 11n_disable
needs to be changed, and does the needful:
11n_disable.sh script
#!/bin/bash -e WHOAMI=$(basename $0) IFACE="$1"; shift ACTION="$1"; shift log() { level="$1"; shift logger -p daemon.$level -t "$WHOAMI" $@ } if [ "$ACTION" != "up" ]; then log debug ignoring action $ACTION exit 0 fi state=$(cat /sys/module/iwlwifi/parameters/11n_disable) log notice previous 11n_disable state is $state if [ "$CONNECTION_ID" = "bad-wifi-network-name" ]; then want_state=2 else want_state=0 fi if [ "$state" != "$want_state" ]; then log notice reloading iwlwifi with 11n_disable=$want_state if ! rmmod iwlmvm iwlwifi; then log err rmmod iwlmvm iwlwifi failed exit 1 fi if ! modprobe iwlwifi 11n_disable=$want_state; then log err modprobe iwlwifi 11n_disable=$want_state failed exit 1 fi if ! modprobe iwlmvm; then log err modprobe iwlmvm failed exit 1 fi log notice finished reloading iwlwifi with 11n_disable=$want_state else log notice 11n_disable is correct, taking no action fi
This script is installed on my laptop as /etc/NetworkManager/dispatcher.d/02-11n_disable.sh
, owned by root, mode 0755. Obviously on my laptop I’ve replaced bad-wifi-network-name
with the name of the network with the bad upload speed.
This script is run automatically by NetworkManager whenever a network is connected (actually, it’s run more often than that, but the script checks the action specified on the command line and exits if it isn’t “up”).
The script determines what value we want 11n_disable
to have based on the name of the WiFi network, checks if the current value matches what we want, and if not, unloads the relevant kernel modules and then reloads them with the desired value.
There is one potential problem with the script which might impact you. You could end up flapping between different WiFi networks if you have multiple networks in range and the script is configured to use different 11n_disable
values for different networks. Caveat emptor.
By the way, here’s a different script I put together which automates the process of testing which 11n_disable
setting is the most performant. Perhaps you will find it useful.
iwlwifi_11n_tester.py script
#!/usr/bin/env python3 # This script tests all the possible values for the 11n_disable # parameter of the iwlwifi kernel module to determine which value # gives you the best performance for any particular WiFi network. # # The script will bounce your WiFi up and down many times while it is # running. It assumes you're using NetworkManager. It also assumes you # have only one WiFi device. # # You need to have speedtest-cli installed for the script to work. # # If you don't specify a WiFi network name on the command line, then # the script will just assume that whatever network gets connected to # automatically when WiFi is enabled is the one you're testing. # # When the script is done running it will do one final rest of # 11n_disable to whatever value it determined was the best. import argparse import csv import io import re import requests import statistics import subprocess import time default_iterations_per_value = 1 def parse_args(): parser = argparse.ArgumentParser(description="Test all 11n_disable values " "for iwlwifi to find the best one") parser.add_argument("--wifi-network", action="store", help="WiFi network " "to test (if not specified, whichever network is " "is connected to automatically is used)") parser.add_argument("--iterations-per-value", type=int, action="store", default=default_iterations_per_value, help="How many iterations of the test to do for each " "value of 11n_disable (default is {}, increase for " "more accurate results)".format( default_iterations_per_value)) return parser.parse_args() def get_csv_header(): output = subprocess.check_output(["speedtest-cli", "--csv-header"]).\ decode('utf-8') f = io.StringIO(output) reader = csv.reader(f) row = next(reader) return row def wait_for_wifi(): print("Waiting for WiFi to come back up") while True: output = subprocess.check_output(["nmcli", "c", "show", "--active"]).\ decode('utf-8') match = re.search(r'\n(.*\S)\s+\S+\s+wifi\s+(\S+)', output) if match: break time.sleep(1) return(match.group(1), match.group(2)) def reset_wifi(value, network): print("Resetting WiFi with 11n_disable={}".format(value)) subprocess.check_call(["sudo", "rmmod", "iwlmvm"]) subprocess.check_call(["sudo", "rmmod", "iwlwifi"]) subprocess.check_call(["sudo", "modprobe", "iwlwifi", "11n_disable={}".format(value)]) subprocess.check_call(["sudo", "modprobe", "iwlmvm"]) connected_network, device = wait_for_wifi() if network and connected_network != network: print("Reconnecting to WiFi network {}".format(network)) subprocess.check_call(["nmcli", "d", "disconnect", device]) subprocess.check_call(["nmcli", "d", "wifi", "connect", network]) wait_for_wifi() # A delay is needed for the network to "settle" while True: try: response = requests.get('http://neverssl.com') except requests.exceptions.ConnectionError: status_code = 500 else: status_code = response.status_code if status_code == 200: break time.sleep(1) def test_11n_disable_value(value, args, rows): reset_wifi(value, args.wifi_network) for iteration in range(args.iterations_per_value): do_iteration(value, iteration, args, rows) def do_iteration(value, iteration, args, rows): print("Running iteration {} of {} for 11n_disable={}".format( iteration + 1, args.iterations_per_value, value)) output = subprocess.check_output(["speedtest-cli", "--csv"]).\ decode("utf-8") f = io.StringIO(output) reader = csv.reader(f) row = next(reader) download = int(float(row[args.download_index])) upload = int(float(row[args.upload_index])) print(" 11n_disable={}, download={}, upload={}".format( value, download, upload)) rows.append([value, download, upload]) def analyze_results(args, rows): download_speeds = [] upload_speeds = [] for value in range(8): try: download_speeds.append(statistics.mean( r[1] for r in rows if r[0] == value)) except statistics.StatisticsError: # We haven't done that value yet. break upload_speeds.append(statistics.mean( r[2] for r in rows if r[0] == value)) best_download = max(download_speeds) best_upload = max(upload_speeds) overall_scores = [] for value in range(len(download_speeds)): overall_scores.append(download_speeds[value] / best_download + upload_speeds[value] / best_upload) best_overall = max(overall_scores) for value in range(len(download_speeds)): download_pct = int(100 * download_speeds[value] / best_download) upload_pct = int(100 * upload_speeds[value] / best_upload) print("11n_disable={} has {}% of best download speed, {}% of best " "upload speed".format(value, download_pct, upload_pct)) best_overall_value = next(v for v in range(len(overall_scores)) if overall_scores[v] == best_overall) print("Best overall is 11n_disable={}".format(best_overall_value)) reset_wifi(best_overall_value, args.wifi_network) def main(): args = parse_args() header = get_csv_header() args.download_index = next(i for i in range(len(header)) if header[i] == 'Download') args.upload_index = next(i for i in range(len(header)) if header[i] == 'Upload') rows = [] for value in range(8): test_11n_disable_value(value, args, rows) analyze_results(args, rows) if __name__ == '__main__': main()
Comment below if you find this useful!
turns out there are only 4 available values: 1,2,4,8
https://github.com/torvalds/linux/blob/master/drivers/net/wireless/intel/iwlwifi/iwl-drv.c
MODULE_PARM_DESC(11n_disable,
“disable 11n functionality, bitmap: 1: full, 2: disable agg TX, 4: disable agg RX, 8 enable agg TX”);
https://github.com/torvalds/linux/blob/master/drivers/net/wireless/intel/iwlwifi/iwl-modparams.h
enum iwl_disable_11n {
IWL_DISABLE_HT_ALL = BIT(0),
IWL_DISABLE_HT_TXAGG = BIT(1),
IWL_DISABLE_HT_RXAGG = BIT(2),
IWL_ENABLE_HT_TXAGG = BIT(3),
};
technically: [0, 1, 2, 4, 8]
Thank you SO much for your python tester script. I was wondering how each of the 11n_disable values effected performance.
Pingback: iwlwifi 11n_disable=1 - Why does this work briefly? - Boot Panic
Thanks.
This was exactly what I needed.
nice script, percentages at the end were a nice touch.
I had to do that, yes. See https://askubuntu.com/questions/1144049/ubuntu-on-lenovo-ideapad-s340-intel-i3-8-gig.
Hi, I want to know if you have to change to ahci mode in bios for can install linux?
Cause i have the same model of laptop s340, but when the usb linux start i continued but can’t the ssd disk for selected like a destination disk.