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.
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.
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.
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.
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?
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 whichmight help us achieving our goal
mmcli -L
mbim-proxy
which hold's the modem device openmbim-proxy
mbim-proxy
it might work and you can use qmicli
. Sometimes... And sometimes it is stuck.qmicli
can use mbim-proxy
so you don't have to bother trying to find way how to stopIf 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
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??
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'
With connection named O2 in place (you can set it up i.e. with Network Manager's connection editor):
$ nmcli con up O2
Yes!
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
lte-modem-fix
script.sudo mmcli -L
. Use lte-modem-fix
if not and check again (it will take the modem couple seconds to appear).sudo qmicli -d /dev/cdc-wdm0 --device-open-proxy --device-open-mbim --uim-get-card-status
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.