For the 8086:7560 unlock I was successfully able to unlock on the ThinkPad X1 Yoga Gen 7 using the script that is located here: https://gitlab.freedesktop.org/mobile-broadband/ModemManager/-/issues/751.
You can store the script as /etc/ModemManager/fcc-unlock.d/8086:7560
and make sure it is executable (chmod +x
). You will also need to change the header's shebang from python
to python3
.
There are going to be some workarounds needed especially if you use suspend/hibernate, since the modem itself is rather finicky.
One major issue is that once the modem is unpowered on the PCI bus it will not be able to be powered up again, needing to be turned on again. This affects the modem itself and the bus it is attached to.
You will need the following udev rule:
/etc/udev/rules.d/99-modem-suspend.rules
# The modem fails to wake up ever again after suspend
SUBSYSTEM=="pci", ATTR{vendor}=="0x1cf8", ATTR{device}=="0x8653", ATTR{power/control}="on", GOTO="pci_pm_end"
SUBSYSTEM=="pci", ATTR{vendor}=="0x8086", ATTR{device}=="0x7560", ATTR{power/control}="on", GOTO="pci_pm_end"
SUBSYSTEM=="pci", ATTR{vendor}=="0x8086", ATTR{device}=="0x51b8", ATTR{power/control}="on", GOTO="pci_pm_end"
# Use normal sleeping otherwise
SUBSYSTEM=="pci", ATTR{power/control}="auto"
LABEL="pci_pm_end"
Then you will need a script that runs at boot to set some initial properties of the device power saving so that the modem or Linux does not suspend the device but keeps it always powered.
/opt/keep-modem-awake.sh
#!/bin/sh
# Enable wakeups
## Bus the modem is on
if grep 'pci:0000:00:1c.0' < /proc/acpi/wakeup | grep disabled
then
echo "Enabling wakeup for bus owning Modem..." > /dev/kmsg
echo RP01 > /proc/acpi/wakeup
fi
## Modem
if grep 'pci:0000:08:00.0' < /proc/acpi/wakeup | grep disabled
then
echo "Enabling wakeup for modem..." > /dev/kmsg
echo PXSX > /proc/acpi/wakeup
fi
# Disable d3cold for the modem
# It is behind the bridge: 00:1c.0 PCI bridge: Intel Corporation Device 51b8 (rev 01)
# https://patchwork.kernel.org/project/linux-pci/patch/[email protected]/
echo "Disabling modem d3cold..." > /dev/kmsg
## The actual modem
echo 0 > /sys/bus/pci/devices/0000:08:00.0/d3cold_allowed
## The owning buses
echo 0 > /sys/devices/pci0000:00/0000:00:1c.0/d3cold_allowed
echo 0 > /sys/bus/acpi/devices/PNP0A08:00/device:4f/physical_node/d3cold_allowed
echo 0 > /sys/bus/acpi/devices/PNP0A08:00/device:4f/device:50/physical_node/d3cold_allowed
# Use ACPI to reset the PCI and not just power off and power on the device
echo "Setting modem reset method to ACPI..." > /dev/kmsg
echo acpi > /sys/bus/pci/devices/0000:08:00.0/reset_method
Then for running the script at boot:
/etc/systemd/system/keep-modem-awake.service
Description="Keep modem awake for suspend."
After=ModemManager.service NetworkManager.service
Wants=ModemManager.service NetworkManager.service
[Service]
ExecStart=/opt/keep-modem-awake.sh
[Install]
WantedBy=multi-user.target
For later scripts, you will need a script that can find the modem because when the system suspends and resumes, the modem ID will change each time.
/opt/find-modem.sh
#!/bin/sh
# Make sure path is set
export PATH="$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
for __i in $(seq 1 10)
do
__modem="$(/opt/find-modem-sub.sh | head -n 1)"
if [ -z "$__modem" ]
then
sleep 0.5
else
break
fi
done
if [ -z "$__modem" ]
then
exit 1
fi
echo "$__modem"
exit 0
/opt/find-modem-sub.sh
#!/bin/sh
# Make sure path is set
export PATH="$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
# Read in commands
mmcli -L | grep '\/Modem\/' | while read __line
do
echo "$__line" | sed 's/^.*\/Modem\/\([0-9]\{1,\}\).*$/\1/'
exit 0
done
exit 1
Then, if you plan to use suspend and hibernate, you will have to setup scripts accordingly for that so that they run before and after that occurs:
/opt/suspend-modem.sh
#!/bin/sh
# Make sure path is correct
export PATH="$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
# Get modem ID
__modem="$(/opt/find-modem.sh)"
if [ "$1" = "0" ]
then
echo "Telling NM to not use the modem..." > /dev/kmsg
nmcli c down NetworkName
# Make sure the connection is deactivated
echo "Waiting for NM to show disconnected..." > /dev/kmsg
while nmcli con show --active | grep NetworkName
do
sleep 0.5
done
# Deactivate the modem before suspend as the connection freezes and never comes back
echo "Disabling modem..." > /dev/kmsg
mmcli -m "$__modem" --disable
elif [ "$1" = "1" ]
then
echo "Disabling modem after resume..." > /dev/kmsg
mmcli -m "$__modem" --disable
echo "Entering low power mode after disable..." > /dev/kmsg
mmcli -m "$__modem" --set-power-state-low
echo "Performing enabling loops..." > /dev/kmsg
for __i in $(seq 1 5)
do
echo "Loop $__i..." > /dev/kmsg
echo "Turning on modem and enabling..." 1>&2
mmcli -m "$__modem" --set-power-state-on
if mmcli -m "$__modem" --enable
then
echo "Modem was enabled..." > /dev/kmsg
break
else
echo "Did not enable modem..." > /dev/kmsg
sleep 0.5
fi
done
echo "Telling NM to use the modem now..." > /dev/kmsg
nmcli c up NetworkName
fi
Then accordingly the following systemd, this one for resume:
rfkill-modem-resume.service
[Unit]
Description=rfkill modem before sleep
After=suspend.target hibernate.target hybrid-sleep.target suspend-then-hibernate.target
[Service]
Type=simple
ExecStart=/opt/suspend-modem.sh 1
[Install]
WantedBy=suspend.target hibernate.target hybrid-sleep.target suspend-then-hibernate.target
And this one for suspend:
rfkill-modem-suspend.service
[Unit]
Description=rfkill modem before sleep
Before=suspend.target hibernate.target hybrid-sleep.target suspend-then-hibernate.target
[Service]
Type=simple
ExecStart=/opt/suspend-modem.sh 0
[Install]
WantedBy=suspend.target hibernate.target hybrid-sleep.target suspend-then-hibernate.target
With these scripts and otherwise, I have had success in keeping the modem alive after multiple suspend and hibernates.
As an extra, you may also want to adjust the NetworkManager connection so that DNS and route priority are lower (higher values) than your main connection so that it is used as a fallback.