Overview
Having a Lenovo Yoga 2 13″ (non-pro) running Ubuntu 14.04.1, I couldn’t get Wireless LAN up and running, as the WLAN NIC appeared to be “hardware locked”. This is the summary of how I solved this issue. If you’re not interested in the gory details, you may jump right to bottom, where I offer a replacement module that fixes it. At least for me.
Environment details: Distribution kernel 3.13.0-32-generic on an Intel i5-4210U CPU @ 1.70GHz. The Wifi device is an Intel Dual Band Wireless-AC 7260 (8086:08b1) connected to the PCIe bus, taken care of by the iwlwifi driver.
The problem
Laptops have a mechanism for working in “flight mode” which means turning off any device that could emit RF power, so that the airplane can crash for whatever different reason. Apparently, some laptops have a physical on-off switch to request this, but on Lenovo Yoga 13, the arrangement is to press a button on the keyboard with an airplane drawn on it. The one shared with F7.
It seems to be, that on Lenovo Yoga 13, the ACPI interface, which is responsible for reporting the Wifi’s buttons state, always reports that it’s in flight mode. So Linux turns off Wifi, and on the desktop’s Gnome network applet it says “Wi-Fi is disabled by hardware switch”.
In the dmesg log one can tell the problem with a line like
iwlwifi 0000:01:00.0: RF_KILL bit toggled to disable radio.
which is issued by the interrupt request handler defined in drivers/net/wireless/iwlwifi/pcie/rx.c, which responds to an interrupt from the device that informs the host that the hardware RF kill bit is set. So the iwlwifi module is not to blame here — it just responds to a request from the ACPI subsystem.
rfkill
The management of RF-related devices is handled by the rfkill subsystem. On my laptop, before solving the problem, a typical output went
$ rfkill list all 0: ideapad_wlan: Wireless LAN Soft blocked: yes Hard blocked: yes 1: ideapad_bluetooth: Bluetooth Soft blocked: no Hard blocked: yes 6: hci0: Bluetooth Soft blocked: no Hard blocked: no 7: phy1: Wireless LAN Soft blocked: yes Hard blocked: yes
So there are different entities that can be controlled with rfkill, enumerated and assigned soft and hard blocks. Each of these relate to a directory in /sys/class/rfkill/. For example, the last device, “phy7″ enumerated as 7 corresponds to /sys/class/rfkill/rfkill7, where the “hard” and “soft” pseudo-files signify the status with “0″ or “1″ values.
The soft block can be changed by “rfkill unblock 0″ or “rfkill unblock 7″, but this doesn’t really help with the hardware block. Both has to be “off” to use the device.
As can be seen easily from the rkfill list above, each of the physical devices are registered twice as rfkill devices: Once by their driver, and a second time by the ideapad_laptop driver. This will be used in the solution below.
The ideapad_laptop module
The ideapad-laptop module is responsible for talking with the ACPI layer on machines that match “VPC2004″ as a platform (as in /sys/devices/platform/VPC2004:00, or /sys/bus/acpi/devices/VPC2004:00, but doesn’t fit anything found in /sys/class/dmi/id/).
Blacklisting this module has been suggested for Yoga laptops all over the web. In particular this post suggests to insmod the module once with a hack that forces the Wifi on, and then blacklist it.
But by blacklisting ideapad-laptop, the computer loses some precious functionality, including disabling Wifi and the touchpad by pressing a button. So this is not an appealing solution.
Ideapad’s two debugfs output files go:
# cat /sys/kernel/debug/ideapad/cfg cfg: 0x017DE014 Capability: Bluetooth Wireless Camera Graphic: # cat /sys/kernel/debug/ideapad/status Backlight max: 16 Backlight now: 9 BL power value: On ===================== Radio status: Off(0) Wifi status: Off(0) BT status: On(1) 3G status: Off(0) ===================== Touchpad status:Off(0) Camera status: On(1)
So the Radio and Wifi statuses, which are read from the ACPI registers, are off. This makes the ideapad_laptop module conclude that everything should go off.
The solution
In essence, the solution for the problem is to take the ideapad_laptop’s hands off the Wifi hardware, except for turning the hardware block off when it’s loaded. It consists of making the following changes in drivers/platform/x86/ideapad-laptop.c:
- First, remove the driver’s rfkill registration. Somewhere at the beginning of the file, change
#define IDEAPAD_RFKILL_DEV_NUM (3)
to
#define IDEAPAD_RFKILL_DEV_NUM (2)
and in the definition of ideapad_rfk_data[], remove the line saying
{ "ideapad_wlan", CFG_WIFI_BIT, VPCCMD_W_WIFI, RFKILL_TYPE_WLAN }
This prevents the driver from presenting an rfkill interface, so it keeps its hands off.
- There is however a chance that the relevant bit in the ACPI layer already has the hardware block on. So let’s turn it off every time the driver loads. In ideapad_acpi_add(), after the call to ideapad_sync_rfk_state(), more or less, add the following two lines:
pr_warn("Hack: Forcing WLAN hardware block off\n"); write_ec_cmd(priv->adev->handle, VPCCMD_W_WIFI, 1);
- And finally, solve a rather bizarre phenomenon, that when reading for the RF state with a VPCCMD_R_RF command, the Wifi interface is hardware blocked for some reason. Note that radio is always in off mode, so it’s a meaningless register on Yoga 2. This is handled in two places. First, empty ideapad_sync_rfk_state() completely, by turning it into
static void ideapad_sync_rfk_state(struct ideapad_private *priv) { }
This function reads VPCCMD_R_RF and calls rfkill_set_hw_state() accordingly, but on Yoga 2 it will always block everything, so what’s the point?
Next, in debugfs_status_show() which prints out /sys/kernel/debug/ideapad/status, remove the following three lines:if (!read_ec_data(priv->adev->handle, VPCCMD_R_RF, &value)) seq_printf(s, "Radio status:\t%s(%lu)\n", value ? "On" : "Off", value);
Having these changes made, the Wifi works properly, regardless of it was previously reported hardware blocked.
This can’t be submitted as a patch to the kernel, because presumably some laptops need the rfkill interface for Wifi through ideapad_laptop (or else, why was it put there in the first place?).
Also, maybe I should have done this for Bluetooth too? Don’t know. I don’t use Bluetooth right now, and the desktop applet seems to say all is fine with it anyhow.
Download the driver fix
For the lazy ones, I’ve prepared a little kit for compiling the relevant driver. I’ve taken the driver as it appears in kernel 3.16, more or less, and applied the changes above. And I then added a Makefile to make it compile easily. Since the kernel API changes rather rapidly, this will probably work well for kernels around 3.16 (that includes 3.13), and then you’ll have to apply the changes manually. If it isn’t fixed in the kernel itself by then.
Download it from here, untar it, change directory, and compile it with typing “make”. This works only if you have the kernel headers and gcc compiler installed, which is usually the case in recent distributions. So a session like this is expected:
$ make make -C /lib/modules/3.13.0-32-generic/build SUBDIRS=/home/eli/yoga-wifi-fix modules make[1]: Entering directory `/usr/src/linux-headers-3.13.0-32-generic' CC [M] /home/eli/yoga-wifi-fix/ideapad-laptop.o Building modules, stage 2. MODPOST 1 modules CC /home/eli/yoga-wifi-fix/ideapad-laptop.mod.o LD [M] /home/eli/yoga-wifi-fix/ideapad-laptop.ko make[1]: Leaving directory `/usr/src/linux-headers-3.13.0-32-generic'
Then replace the fresh ideapad-laptop.ko with the one the kernel uses. First, let’s figure out where to. The modinfo command help here:
$ modinfo ideapad_laptop
filename: /lib/modules/3.13.0-32-generic/kernel/drivers/platform/x86/ideapad-laptop.ko
license: GPL
description: IdeaPad ACPI Extras
author: David Woodhouse <dwmw2@infradead.org>
srcversion: BA339D663FA3B10105A1DC0
alias: acpi*:VPC2004:*
depends: sparse-keymap
vermagic: 3.13.0-32-generic SMP mod_unload modversions
parm: no_bt_rfkill:No rfkill for bluetooth. (bool)
So the directory is now known (marked in red). This leaves us with copying it into the right place:
$ sudo cp ideapad-laptop.ko /lib/modules/3.13.0-32-generic/kernel/drivers/platform/x86/
The new module is valid on the next reboot. Or the next insmod/modprobe, if you’re have the same allergy as myself regarding rebooting a Linux system.