Menu
Linux on Microsoft Surface 2

Wi-Fi and Bluetooth together

How one firmware from the old Surface RT and a small mwifiex patch brought both radios to life.

The Surface 2 has a Marvell 88W8797 combo chip. Wi-Fi runs over SDIO, Bluetooth over UART-D, and one firmware for both radios loads over SDIO. The ACPI tables show the wiring:

LinePurpose
UART-D, 0x70006300Bluetooth with hardware RTS/CTS
PU6 (GPIO 166)Bluetooth-to-host wake
PK3 (GPIO 83)Host-to-Bluetooth wake
PK4, then PK2 or PK0Power for the whole chip, depends on board revision

An important detail: PK3 is neither reset nor power, only wake. Bluetooth has no separate power rail.

Why it did not work

The Bluetooth transport is built into each firmware and cannot be set by configuration. That left a dead end:

FirmwareBluetoothWi-Fi with mwifiex
linux-firmware FP66/FP68Over SDIO, no use on this boardWorks
Surface 2, FP62_WIN 14.62.28.p58Over UART, worksFails, different protocol

We checked 23 images across the history of linux-firmware, ChromiumOS, Steam Link and TI builds. None is a UART build for the 8797.

Step 1. Getting Bluetooth to answer

Earlier attempts all ended the same way: the chip raises CTS, the host sends 12 bytes, 0 come back. Trying other speeds, UART-C and UART-D changed nothing. Checking the real state of the lines revealed two causes.

First, the 8250 driver physically cannot reach 3 Mbaud at this clock. It needs serial-tegra, selected through the device tree:

serial@70006300 {
    clocks = <0x02 0x41>;
    compatible = "nvidia,tegra30-hsuart";
    interrupts = <0x00 0x5a 0x04>;
    reg = <0x70006300 0x100>;
    reg-shift = <0x02>;
    reset-names = "serial";
    resets = <0x02 0x41>;
    status = "okay";
};

Second, the host's RTS line stayed low. The chip reads that as "host not ready" and stays silent. With CRTSCTS hardware flow control, the chip finally answered HCI_Reset:

Conditions for an answer:
1. firmware with BT_UART;
2. UART-D on serial-tegra (nvidia,tegra30-hsuart);
3. RTS actually raised (CRTSCTS), 3,000,000 baud.

BlueZ then comes up with one command:

btattach -B /dev/ttyTHS1 -P h4 -S 3000000

But with the Surface 2's own firmware, Wi-Fi was gone.

Step 2. A dead end worth recording

We worked out which language the Wi-Fi half of this firmware speaks. It is Microsoft's WDI protocol. The firmware answered GET_ADAPTER_CAPABILITIES with a 1764-byte block, so a new Linux driver was possible in theory. We got as far as port creation and the first scan stages, but the estimate for the rest was weeks.

Step 3. Firmware from the older sibling

The Windows Wi-Fi driver model changed between 8.0 and 8.1, and the first Surface RT shipped with Windows RT 8.0. So its driver should follow the older model, close to mwifiex. It does: mwls97w8arm.sys carries classic MLAN stack strings, and inside it sits this firmware:

$Id: w8797-B0, RF878X, FP62_WIN7, 14.62.17.p215, BT_UART $

FP62_WIN7 marks a build for the classic model, and BT_UART is exactly what we need. The image is extracted by walking its chain of blocks. Each block has a 16-byte header: command, address, length and CRC.

def chain_end(data, start):
    pos = start
    while pos + 16 <= len(data):
        cmd, _addr, length, _crc = struct.unpack_from("<IIII", data, pos)
        if cmd not in (1, 4) or length > 1024:
            return None
        pos += 16 + length
        if cmd == 4:
            return pos if length == 0 else None
    return None

Step 4. The mwifiex patch

The first test was a half win: Bluetooth worked and the firmware loaded, but mlan0 did not come up. Firmware 14.62 is older than the driver expects and answers some commands with result 2, "not supported".

For scanning we switch to the legacy command below version 14.66:

/* SD8797 firmware 14.62.x (Windows builds with Bluetooth over UART)
 * lacks SCAN_EXT; 14.66+ (linux-firmware) supports it.
 */
if (adapter->ext_scan && adapter->fw_api_ver == 14 &&
    ((adapter->fw_release_number >> 8) & 0xff) < 66) {
    mwifiex_dbg(adapter, MSG,
                "firmware 14.%u: SCAN_EXT unsupported, using legacy scan\n",
                (adapter->fw_release_number >> 8) & 0xff);
    adapter->ext_scan = false;
}

Two optional commands stop being fatal. CHAN_REGION_CFG was the one halting initialisation:

/* Optional commands that older firmware does not implement: ignore
 * "not supported" instead of failing (fatal during initialization).
 */
if (le16_to_cpu(resp->result) == HostCmd_RESULT_NOT_SUPPORT &&
    (cmdresp_no == HostCmd_CMD_CHAN_REGION_CFG ||
     cmdresp_no == HostCmd_CMD_MGMT_FRAME_REG)) {
    mwifiex_dbg(adapter, MSG,
                "cmd %#x not supported by firmware, ignored\n",
                cmdresp_no);
    return 0;
}

Newer firmware never reaches these branches, so the driver behaves as before.

Result

In the second test Wi-Fi connected in 8 seconds at 65–72 Mbit/s on MCS7, with hci0 up at the same time. After the permanent install, receive reached 117 Mbit/s.

mwifiex_sdio mmc2:0001:1: driver_version = mwifiex 1.0 (14.62.17.p215)
mwifiex_sdio mmc2:0001:1: cmd 0x10c not supported by firmware, ignored
mwifiex_sdio mmc2:0001:1: info: associated to bssid 00:00:00:00:00:00 successfully

How it runs on the tablet

We do not change distribution files. An early service bind-mounts the firmware and modules over the stock ones, so rollback is instant: disable the service.

fw=/lib/firmware/mrvl/sd8797_uapsta.bin
cd /opt/surface-bt && sha256sum -c --quiet SHA256SUMS || exit 0
mount --bind /opt/surface-bt/fw-win7.bin $fw

Bluetooth attaches through a separate service that waits for mlan0, because the chip only comes alive after firmware loads:

[Unit]
After=surface-bt-overlay.service systemd-udev-settle.service
Before=bluetooth.service
[Service]
ExecStartPre=/bin/sh -c 'for i in $(seq 60); do [ -c /dev/ttyTHS1 ] && [ -e /sys/class/net/mlan0 ] && exit 0; sleep 1; done; exit 1'
ExecStart=/usr/bin/btattach -B /dev/ttyTHS1 -P h4 -S 3000000
Restart=on-failure

Limits

  • A Bluetooth scan caused about 11 % ping loss on Wi-Fi.
  • Command 0x23f returns an error, so channel width shows as HT20.
  • Firmware can hang with power saving on, so it stays off.
  • 5 GHz was not validated.

Getting the firmware: guide.