Surface Go 2 and LTE modem (updated)

2 months ago / about 5 min read / in #surface, #linux

I might have been little too eager to have Linux running when I got my hands on a new Surface Go 2 tablet.

As my Surface's main job is to support me when I'm away from home, a connectivity is a must. Fortunately my model has LTE modem built-in. My hopes were it would be enough to just insert SIM with data plan and be done.

SIM inserted, clicked on nm-applet and... nothing. Just usual Wi-Fi networks from neighbors. Still optimistic, my first steps were to consult amazing linux-surface project. It already helped me getting Surface up and running in no time.

Enabling the modem

There is dedicated section for enabling LTE modem and now I see it. There is some bug which is causing the modem to disappear sometimes or doesn't appear at all.

I modified it a little and removed restart for ModemManager (which was useful later) and stored it to /usr/local/bin/lte-modem-fix.

#!/bin/bash
readonly CDC_NCM_DIR=/sys/bus/usb/devices/2-3/2-3:1.0/net/wwp0s20f0u3/cdc_ncm

for val in 16383 16384; do
    for max in rx_max tx_max; do
        echo "$val" | sudo tee "${CDC_NCM_DIR}/${max}"
    done
done

Don't forget to set it executable sudo chmod 0755 /usr/local/bin/lte-modem-fix.

Ok, so lets try it:

$ lte-modem-fix
16383
16383
16384
16384
$ systemctl restart ModemManager.service

It seems OK, no errors. But still nothing, I can't connect to my provider. Let's double check the instructions... and here it is. One small paragraph almost at the end of the instructions which crashed my hopes:

If the script contains the correct interface name and USB device ID for your modem, but the modem is still not showing up in ModemManager, then reboot into Windows 10 and make sure that the LTE modem is configured to use your SIM card and not the eSIM. Then reboot into Linux and run the above script again.

It looks like Surface Go 2 LTE modem has support not only for normal SIM but also for eSIM. Which sound really great, it might came handy when eSIMs will be thing here. Not so great is the fact that eSIM is the default. And according to the paragraph above I simply have to boot to Windows to change it. Which would be really easy if I had dual boot. Or knew that before I wiped the Windows and installed Linux.

I started searching around and ultimately found LTE/4G/mobile support issue on jakeday/linux-surface kernel page (which is mentioned in wiki instructions and I overlooked it apparently). I tried to ask there if anybody succeeded to enable it from Linux but doesn't seem so.

Experiments

Ok, so I have to install Windows, switch the SIM and install/restore Linux again. Not happening, I rather tether LTE through my phone than doing such tedious tasks.

First idea was to check for modem documentation. There might be something useful. According to the linux-surface wiki the model is Qualcomm Snapdragon X16. Unfortunately nothing interesting there. I found some scraps from manufacturers of this model's hardware implementations, but nothing really useful.

AT commands, mmcli and ModemManager

Next I tried to play with ModemManager and its mmcli. At that moment I also realized, that this is a modem, isn't it? What about AT commands?

First let's check if we have a modem. mmcli is showing the modem correctly:

$ sudo mmcli -L
   /org/freedesktop/ModemManager1/Modem/0 [Surface] Surface

To send AT commands you have to start ModemManager in debug mode apparently.

sudo systemctl stop ModemManager
~/bin/lte_model_fix.sh
sleep 5
sudo ModemManager --debug

Oh my, why I didn't try this before? There is so much interesting stuff in ModemManager log. Most messages just stating that things are not working but there are also technical terms I can search for later!

This AT command might help with switching SIM:

$ sudo mmcli --modem=/org/freedesktop/ModemManager1/Modem/0 --command="ATIANTSEL?"
error: command failed: 'GDBus.Error:org.freedesktop.ModemManager1.Error.Core.Connected: No AT port available to run command'

But it looks like there is no AT port available. Either the modem doesn't support AT commands entered this way or I'm doing something wrong.

I spent at least half an hour trying to convince it to obey me but - probably lacking authority - it was ultimately a dead end.

Let's learn more

Anyway thanks to debug mode of ModemManager, I observed something about MBIM and QMI. After some digging and reading detailed, but ultimately not so useful documents (i.e. MB Multi-SIM operations at Microsoft's Doc), I ended up with important discovery. Which I should have found out much sooner :)

Quoting from this gist:

MBIM, or Mobile Broadband Interface Model, is an official USB standard created by the USB Implementors Forum. QMI or Qualcomm Mobile Station Modem Interface was developed by Qualcomm and is only supported by Qualcomm chips.

QMI is for Qualcomm, I have Qualcomm, maybe this interface might be supported in Linux somehow?

QMI

And yes, it is. There is library libqmi, CLI tool qmicli and couple more projects around. And what's best, it seems I have qmicli already installed as dependency of something else, probably the ModemManager.

kepi@midget ~ $ yay libqmi
$ yay libqmi
4 aur/libqmi-git 1.23.1+68+ga48f7ee-1 (+0 0.00)
    QMI modem protocol helper library
3 aur/libqmi-qmi-over-mbim-netctl 1.0.0-4 (+2 0.00)
    QMI modem protocol helper library with experimental qmi-over-mbim patch
2 aur/libqmi-qmi-over-mbim latest-1 (+3 0.00) (Orphaned) (Out-of-date: 2016-08-12)
    QMI modem protocol helper library with experimental qmi-over-mbim patch
1 extra/libqmi 1.26.8-1 (1.7 MiB 33.3 MiB) (Installed)
    QMI modem protocol helper library
==> Packages to install (eg: 1 2 3, 1-3 or ^4)

If you are saving space like me (just found out I don't have man on Surface), you can find it online: qmicli.1. And it's interesting reading, there are arguments mentioning slots, slot statuses etc. This might be it.

From what I found modem should be available at /dev/cdc-wdm0 device and qmicli should be able to interact with it.

$ qmicli -d /dev/cdc-wdm0 --get-service-version-info
error: couldn't open the QmiDevice: Cannot open device file '/dev/cdc-wdm0': Permission denied
$ sudo qmicli -d /dev/cdc-wdm0 --get-service-version-info
error: couldn't open the QmiDevice: Operation timed out: device is closed

Weird, the device is closed.

I don't want to bother you with dozens attempts which almost succeeded so many times. Fortunately, small steps, I was progressing and found out that:

  • qmicli can work in MBIM mode, nice, we have two protocols available which

might help us achieving our goal

  • playing around, I often ended up with stuck modem and nothing shown up in mmcli -L
  • Modem Manager starts mbim-proxy which hold's the modem device open
  • Stopping Modem Manager doesn't stop mbim-proxy
  • If you kill mbim-proxy it might work and you can use qmicli. Sometimes... And sometimes it is stuck.
  • Finally qmicli can use mbim-proxy so you don't have to bother trying to find way how to stop

If your mmcli -L doesn't look like following snippet, just try your lte-modem-fix script to "revive" the modem.

$ sudo mmcli -L
   /org/freedesktop/ModemManager1/Modem/0 [Surface] Surface

Switching SIM slot

One of first options I found was something with SIM Power. Maybe slot isn't powered and that is why ModemManager can't switch to it?

Without looking, I blind shot and tried to power on SIM 2 (do not do that at home):

$ sudo qmicli -d /dev/cdc-wdm0 --device-open-proxy --device-open-mbim --uim-sim-power-on=2
[/dev/cdc-wdm0] Successfully performed SIM power on

It did something, but meanwhile I discovered more interesting argument in manual --uim-get-card-status and realized, that SIM 2 wasn't what I need:

$ sudo qmicli -d /dev/cdc-wdm0 --device-open-proxy --device-open-mbim --uim-get-card-status
[/dev/cdc-wdm0] Successfully got card status
Provisioning applications:
        Primary GW:   session doesn't exist
        Primary 1X:   session doesn't exist
        Secondary GW: session doesn't exist
        Secondary 1X: session doesn't exist
Slot [1]:
        Card state: 'present'
        UPIN state: 'not-initialized'
                UPIN retries: '0'
                UPUK retries: '0'
Slot [2]:
        Card state: 'error: no-atr-received (3)'
        UPIN state: 'not-initialized'
                UPIN retries: '0'
                UPUK retries: '0'

It looks like eSIM is Slot 2 and our SIM is in Slot 1. It's present, but not initialized.

Let's use another argument, --uim-switch-slot and try to switch to the first slot:

$ sudo qmicli -d /dev/cdc-wdm0 --device-open-proxy --device-open-mbim --uim-switch-slot=1
[/dev/cdc-wdm0] Successfully switched slots

Now holding my breath...

$ sudo qmicli -d /dev/cdc-wdm0 --device-open-proxy --device-open-mbim --uim-get-card-status
[/dev/cdc-wdm0] Successfully got card status
Provisioning applications:
        Primary GW:   session doesn't exist
        Primary 1X:   session doesn't exist
        Secondary GW: session doesn't exist
        Secondary 1X: session doesn't exist
Slot [1]:
        Card state: 'present'
        UPIN state: 'not-initialized'
                UPIN retries: '0'
                UPUK retries: '0'
        Application [1]:
                Application type:  'usim (2)'
                Application state: 'detected'
                Application ID:
                        A0:00:00:00:87:10:02:F4:20:F0:01:89:00:00:01:FF
                Personalization state: 'unknown'
                UPIN replaces PIN1: 'no'
                PIN1 state: 'not-initialized'
                        PIN1 retries: '0'
                        PUK1 retries: '0'
                PIN2 state: 'not-initialized'
                        PIN2 retries: '0'
                        PUK2 retries: '0'
        Application [2]:
                Application type:  'unknown (0)'
                Application state: 'detected'
                Application ID:
                        A0:00:00:00:63:50:4B:43:53:2D:31:35
                Personalization state: 'unknown'
                UPIN replaces PIN1: 'no'
                PIN1 state: 'not-initialized'
                        PIN1 retries: '0'
                        PUK1 retries: '0'
                PIN2 state: 'not-initialized'
                        PIN2 retries: '0'
                        PUK2 retries: '0'
Slot [2]:
        Card state: 'error: no-atr-received (3)'
        UPIN state: 'not-initialized'
                UPIN retries: '0'
                UPUK retries: '0'

Whoa, it worked??

Update - Primary Gw says session doesn't exist

I'm not sure why, but this wasn't all. During my experiments I probably did something wrong and ended up with session doesn't exist in Primary GW and Application state for Application [1] was detected instead ready.

What is even weirder, modem isn't working even in Windows in this state. I recently tried Win2Go to be able to update firmware but Windows states only Insert SIM without any possibility to actually do anything.

Anyway, I found handy sdm845-modem-prepare.sh gist. It is intended for different modem, but worked just the same. Here with small modifications:

$ systemctl stop Modemamanager # to be sure that something is not blocking the device
$ AID=""; while [ -z "$AID" ]; do AID=$(sudo qmicli -d /dev/cdc-wdm0 --uim-get-card-status | grep -E "([A-Z0-9]{2}:){15}[A-Z0-9]{2}" | xargs); sleep 2; done; echo $AID
A0:00:00:00:87:10:02:FF:47:F0:01:89:00:00:01:FF
$ sudo qmicli -d /dev/cdc-wdm0 --uim-change-provisioning-session="slot=1,activate=yes,session-type=primary-gw-provisioning,aid=$AID"
[/dev/cdc-wdm0] Successfully changed provisioning session

And now, it looks really promising:

[/dev/cdc-wdm0] Successfully got card status
Provisioning applications:
	Primary GW:   slot '1', application '1'
	Primary 1X:   session doesn't exist
	Secondary GW: session doesn't exist
	Secondary 1X: session doesn't exist
Slot [1]:
	Card state: 'present'
	UPIN state: 'not-initialized'
		UPIN retries: '0'
		UPUK retries: '0'
	Application [1]:
		Application type:  'usim (2)'
		Application state: 'ready'
		Application ID:
			A0:00:00:00:87:10:02:FF:47:F0:01:89:00:00:01:FF
		Personalization state: 'ready'
		UPIN replaces PIN1: 'no'
		PIN1 state: 'disabled'
			PIN1 retries: '3'
			PUK1 retries: '10'
		PIN2 state: 'enabled-not-verified'
			PIN2 retries: '3'
			PUK2 retries: '10'

Finally connected

With connection named O2 in place (you can set it up i.e. with Network Manager's connection editor):

$ nmcli con up O2

Yes!

Bonus: lte-modem-fix on boot

I really don't want to use lte-modem-fix manually after every restart, lets set up Systemd timer to automate it. All credits to solarfl4re for his post.

First create file /etc/systemd/system/surfacego2-modemfix.service

[Unit]
Description=Properly initialize Qualcomm X16 modem

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/bin/sh /usr/local/bin/lte-modem-fix
ExecStartPost=systemctl restart ModemManager.service

And than matching timer in /etc/systemd/system/surfacego2-modemfix.timer

[Unit]
Description=Start the service to fix the Surface modem with a delay

[Timer]
OnBootSec=15sec

[Install]
WantedBy=timers.target

Don't forget to reload systemd and enable the timer.

$ sudo systemctl daemon-reload
$ sudo systemctl enable /etc/systemd/system/surfacego2-modemfix.timer

Recap

  1. Prepare your lte-modem-fix script.
  2. Check if the LTE modem is available with sudo mmcli -L. Use lte-modem-fix if not and check again (it will take the modem couple seconds to appear).
  3. Check SIM card status with: sudo qmicli -d /dev/cdc-wdm0 --device-open-proxy --device-open-mbim --uim-get-card-status
  4. Switch to slot 1 with sudo qmicli -d /dev/cdc-wdm0 --device-open-proxy --device-open-mbim --uim-switch-slot=1

If it isn't working, take a look at Update - Primary Gw says session doesn't exist chapter above. It might help you.

Really long road and many hours spent to discover that single command did the trick :) I hope my struggles might help anyone a little.