Menyu
Microsoft Surface 2 da Linux

Wi-Fi va Bluetooth bir vaqtda

Eski Surface RT proşivkasi va mwifiex ga kiçik patç ikkala radioni qanday işga tuşirdi.

Bu material hozirça rus tilida.

В Surface 2 стоит комбо-чип Marvell 88W8797. Wi-Fi подключён по SDIO, Bluetooth по UART-D, а прошивка на оба радио одна и загружается по SDIO. Из таблиц ACPI видно разводку:

ЛинияНазначение
UART-D, 0x70006300Bluetooth с аппаратным RTS/CTS
PU6 (GPIO 166)Сигнал пробуждения от Bluetooth к хосту
PK3 (GPIO 83)Сигнал пробуждения от хоста к Bluetooth
PK4, затем PK2 или PK0Питание всего чипа, зависит от ревизии платы

Важная деталь: PK3 не сброс и не питание, это только пробуждение. Отдельного питания у Bluetooth нет.

Почему не работало

Транспорт Bluetooth вшит в сборку прошивки, конфигурацией он не выбирается. Получался тупик:

ПрошивкаBluetoothWi-Fi с mwifiex
linux-firmware FP66/FP68По SDIO, на этой плате не работаетРаботает
Surface 2, FP62_WIN 14.62.28.p58По UART, работаетНе работает, другой протокол

Мы проверили 23 образа: всю историю linux-firmware, ÇromiumOS, Steam Link и сборки TI. UART-версии для 8797 там нет.

Шаг 1. Заставляем Bluetooth ответить

Предыдущие попытки заканчивались одинаково: чип поднимает CTS, хост отправляет 12 байт, в ответ 0. Перебор скоростей, UART-C и UART-D ничего не давал. Когда мы проверили реальное состояние линий, причин оказалось сразу две.

Во-первых, драйвер 8250 физически не может дать 3 Мбод на этой тактовой частоте. Нужен serial-tegra, и он включается через 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";
};

Во-вторых, линия RTS со стороны хоста оставалась опущенной. Чип видит это как «хост не готов» и молчит. С аппаратным управлением потоком CRTSCTS чип наконец ответил на HCI_Reset:

Условия ответа:
1. прошивка с BT_UART;
2. UART-D через serial-tegra (nvidia,tegra30-hsuart);
3. поднятая линия RTS (CRTSCTS), 3 000 000 бод.

Стек BlueZ поднимается одной командой:

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

Но с родной прошивкой Surface 2 пропал Wi-Fi.

Шаг 2. Тупик, который стоит описать

Мы разобрали, на каком языке говорит Wi-Fi-часть этой прошивки. Оказалось, что это протокол Microsoft WDI. Прошивка ответила на GET_ADAPTER_CAPABILITIES блоком в 1764 байта, то есть теоретически можно написать для Linux новый драйвер. Мы дошли до создания порта и первых этапов сканирования, но объём работы оценивался в недели.

Шаг 3. Прошивка от старшего брата

Модель драйвера Wi-Fi в Windows поменялась между 8.0 и 8.1, а первый Surface RT вышел под Windows RT 8.0. Значит, его драйвер должен быть построен по старой модели, близкой к mwifiex. Так и оказалось: в mwls97w8arm.sys есть строки классического стека MLAN, а внутри лежит вот такая прошивка:

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

FP62_WIN7 означает сборку под классическую модель, BT_UART означает то, что нам нужно. Образ извлекается простым поиском цепочки блоков. У каждого блока заголовок из 16 байт: команда, адрес, длина и 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

Шаг 4. Патч mwifiex

Первый тест порадовал наполовину: Bluetooth заработал, прошивка загрузилась, но интерфейс mlan0 не поднялся. Прошивка 14.62 старше, чем ожидает драйвер, и отвечает на часть команд кодом 2, «не поддерживается».

Для сканирования переходим на старую команду, если версия ниже 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;
}

Две необязательные команды перестают быть фатальными. Именно CHAN_REGION_CFG останавливала инициализацию:

/* 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;
}

На новых прошивках эти ветки не срабатывают, поведение драйвера не меняется.

Результат

Во втором тесте Wi-Fi подключился за 8 секунд и показал 65–72 Мбит/с на MCS7, а hci0 работал одновременно с ним. После постоянной установки приём достиг 117 Мбит/с.

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

Как это устроено на планшете

Файлы дистрибутива мы не меняем. Ранний сервис монтирует прошивку и модули поверх штатных через mount --bind, поэтому откат мгновенный: достаточно отключить сервис.

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 подключается отдельной службой, которая ждёт появления mlan0, ведь чип оживает только после загрузки прошивки:

[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

Ограничения

  • Поиск Bluetooth-устройств давал около 11 % потерь ping по Wi-Fi.
  • Команда 0x23f возвращает ошибку, поэтому ширина канала отображается как HT20.
  • С включённым энергосбережением прошивка может зависнуть, поэтому оно выключено.
  • 5 ГГц не проверялись.

Получить прошивку: инструкция.