IPv6 on MikroTik LTE/5G (RouterOS 7) with Vivacom (Bulgaria)

About

Vivacom is a Bulgarian mobile network operator. Internet access over LTE and 5G is provided in dual-stack mode (IPv4 and IPv6).

As of May 2026, only Vivacom among the Bulgarian national operators provides dual-stack mobile connectivity (IPv4 and IPv6) for general internet access on typical consumer and business mobile data plans. A1 and Yettel are far behind on IPv6 adoption; their mobile plans do not currently include IPv6 internet access in a comparable form. That market position can change; treat this paragraph as a snapshot in time when planning coverage or failover.

This note describes how to give home clients global IPv6 on the LAN when the router uses Vivacom mobile data. Any MikroTik device with an LTE or 5G modem interface and RouterOS 7 can follow the same steps; examples use lte1 and ether1, which you should map to the default names on your model. Paths and property names match recent RouterOS; if a line is rejected, compare with print detail on your exact build.

Vivacom products and prefixes

Vivacom's default APN name for mobile internet access is internet (normal dual-stack consumer data).

Two different products exist on the same network, with different outcomes for your LAN.

  • Dynamic global /64: typical consumer PDP context. The prefix can change after a reboot or a new data session. IPv6 on the PDP context is provided via SLAAC on the path exposed by ipv6-interface.

  • Static global /64: requires a Vivacom contract that includes the mobix-static APN (or equivalent naming on your subscription). The operator assigns a fixed prefix suitable for a small site router.

For home LAN clients you normally want the operator /64 to appear on the LAN side of the router. On MikroTik LTE/5G this is done with the ipv6-interface parameter on an LTE APN profile, not by copying the WAN /64 onto another interface by hand.

Topology assumed here

  • WAN: lte1 (LTE/5G), member of interface list WAN.
  • LAN: the examples bind Vivacom IPv6 to ether1 via ipv6-interface. IPv4 and DHCP for the home network can live on ether1 directly, or on a bridge that contains ether1.

A bridge is not required for Vivacom IPv6 itself. It is only useful when you want one LAN segment that will later include other interfaces (for example a VPN tunnel interface) alongside ether1. In that case you create bridge1, add ether1 as a port, put LAN addressing on bridge1, and when the VPN exists you add the VPN interface as another bridge port so it shares the same LAN and the same RAs.

Adjust names if your setup differs.

Method: bind PDP IPv6 to the LAN port

The modem delivers IPv6 for the PDP context. The parameter ipv6-interface selects which local interface carries that /64 for addressing and router advertisement toward the LAN. On many single-Ethernet LTE/5G CPE layouts that port is ether1 facing the house switch or gateway; use whichever Ethernet port your hardware documentation recommends for ipv6-interface.

If you use a bridge for the LAN, ipv6-interface should still target the same bridge port (typically ether1) that MikroTik expects for the LTE IPv6 offload, and that port must be a member of the bridge so Wi‑Fi, VPN, and Ethernet share one L2 domain and the same router advertisements.

1. APN profiles

Create or edit profiles in /interface lte apn. You need at least one profile referenced by lte1 via apn-profiles.

Example for the default Vivacom APN internet, with IPv6 presented on ether1:

/interface lte apn add name=internet apn=internet ipv6-interface=ether1 use-network-apn=no add-default-route=yes use-peer-dns=yes authentication=none

Example for static service (only if your contract provides it), still binding IPv6 to ether1:

/interface lte apn add name=mobix-static apn=mobix-static ipv6-interface=ether1 use-network-apn=no add-default-route=yes use-peer-dns=yes authentication=none

If you already have a row named default, edit it instead of adding duplicates. Use a full selector, for example:

/interface lte apn set [find where name="default"] apn=internet ipv6-interface=ether1 use-network-apn=no

2. Point the LTE interface at the profile you want

/interface lte set [find where default-name="lte1"] apn-profiles=internet

For static prefix service, set apn-profiles to the profile whose apn= is mobix-static, for example:

/interface lte set [find where default-name="lte1"] apn-profiles=mobix-static

3. Optional: bridge the LAN for a future VPN (or other extra interfaces)

Skip this entire subsection if you are happy with a single LAN port and no bridge.

When you expect a site-to-site or road-warrior VPN interface to become part of the same home LAN as ether1, create a bridge now, attach ether1, attach IPv4 (and any DHCP server) to the bridge, and later add the VPN interface as another bridge port. Vivacom IPv6 still uses ipv6-interface=ether1; the prefix and RAs reach the whole bridge because ether1 is inside it.

/interface bridge add name=bridge1 protocol-mode=rstp
/interface bridge port add bridge=bridge1 interface=ether1

Put LAN IPv4 on bridge1 if you use the bridge; otherwise keep it on ether1. Do not rely on addressing the same global /64 on both lte1 and the LAN without this LTE ipv6-interface integration; that is not the supported model for Vivacom dynamic /64 on the UE.

4. Interface lists and defconf firewall

Keep lte1 on WAN. Put the interface that receives home traffic on LAN (bridge1 if you use a bridge, otherwise ether1). The default RouterOS IPv4/IPv6 firewall examples are compatible with working LAN IPv6 once ipv6-interface is correct.

5. Router advertisement timing (optional)

Global ND is under /ipv6 nd with interface=all on a default install. Shorter RA intervals reduce the time for clients to see a new prefix after LTE comes up, for example:

/ipv6 nd print detail
/ipv6 nd set 0 ra-interval=3s-10s ra-delay=1s ra-lifetime=30m

DNS in RA:

/ipv6 nd set 0 advertise-dns=yes

Prefix lifetimes after a reboot (dynamic service only)

Router advertisements and prefix lifetimes

For the LTE-managed dynamic prefix, RouterOS creates a row under /ipv6 nd prefix print detail with valid-lifetime=infinity and preferred-lifetime=infinity. That is the Prefix Information option your LAN hosts learn from router advertisements.

You cannot override those lifetimes on that row: any attempt to shorten them fails, for example:

/ipv6 nd prefix set 0 preferred-lifetime=30m valid-lifetime=2h

RouterOS returns can not change dynamic prefix. So the router does not offer a supported way, on this path, to advertise the same dynamic prefix with finite preferred or valid lifetimes. Tuning /ipv6 nd set for interface=all (ra-interval, ra-delay, ra-lifetime, and so on) changes how often RAs are sent and default-router timing; it does not replace finite prefix lifetimes in the Prefix Information option while the prefix row stays dynamic and locked.

Client-side address lifetimes

Hosts reflect those RAs in their own address state. On Linux, ip addr (or ip -6 addr show) lists global addresses with valid_lft forever and preferred_lft forever when the last RA used infinite prefix lifetimes. After Vivacom assigns a new /64 (for example after a router reboot), you can briefly have two global addresses in two different /64 prefixes on the same interface: the old one is still forever in the kernel until policy or user action clears it, whilst the new prefix also appears. That window is painful for connectivity when traffic still sources from the stale prefix.

Mitigation

A static /64 from the mobix-static (or current equivalent) product avoids surprise prefix changes, so the stale-prefix problem largely disappears. For dynamic service only, expect some client stacks to be slow; there is no RouterOS knob observed here to force finite prefix lifetimes on the dynamic ND prefix.

CLI traps that confuse people

  • The word default is reserved in scripts; in finds prefer [find where name="default"] or set by numbers=0 from print.

  • Hyphenated values such as ipv4-ipv6 must be quoted in some contexts: ip-type="ipv4-ipv6". On many LTE builds only auto exists for ip-type; Vivacom IPv6 here is driven by ipv6-interface, not by guessing ip-type.

  • ? as interactive help may not work in your SSH client; use print detail and WinBox or WebFig to see properties.

Quick verification

/interface lte apn print detail
/interface lte print detail
/ipv6 address print detail
/ipv6 nd prefix print detail

You should see a global /64 on the LAN path (ether1, or bridge1 if the bridge carries the RA) consistent with Vivacom, and an ND prefix row for that LAN interface with autonomous=yes for SLAAC.

Summary

Goal Vivacom requirement MikroTik lever
IPv6 on home LAN with dynamic /64 Normal data APN (internet, Vivacom default) ipv6-interface=ether1 (or the LAN port your modem supports) on the active APN profile; lte1 apn-profiles pointing at that profile; optional bridge1 containing ether1 if you plan VPN or other ports on the same LAN
Same, but prefix stable across reboots Contract with mobix-static (or current Vivacom static product name) Same ipv6-interface pattern; apn-profiles selects the mobix-static profile

This document reflects lab and field behaviour for Vivacom LTE/5G on MikroTik routers with RouterOS 7; Vivacom may rename APNs or products. Confirm the exact APN string and static product name on your order form or with Vivacom business support before raising tickets.

DKEK shares and HSM smart cards - SmartCard-HSM perspective

This note describes what a DKEK is, how DKEK shares work for key management on SmartCard-HSM class devices, and how that relates to the Smart Card Shell (scsh) distribution. It is aimed at operators and integrators; it does not replace manufacturer documentation or a formal security analysis. The SmartCard-HSM site is https://www.smartcard-hsm.com/. Smart Card Shell 3 is distributed from https://www.openscdp.org/scsh3/download.html.

Terminology

On SmartCard-HSM devices, DKEK usually denotes Device Key Encryption Key: a 256-bit symmetric key held inside the device that acts as a key-encryption key (KEK) for wrapping sensitive key material when using backup, restore, or migration features.

The same three-letter acronym can appear elsewhere in cryptography with different meanings (for example some standards use KEK hierarchies without this exact name). In this document, DKEK refers to the SmartCard-HSM mechanism unless stated otherwise.

Why a DKEK exists

Many HSMs and smart-card HSMs never release private keys in plaintext. To move a key between devices, or to keep an offline backup, the implementation encrypts the key under a KEK that exists on the card. On SmartCard-HSM, that KEK is the DKEK.

Typical properties:

  • Keys generated on the card can be exported only if a DKEK key domain was configured during initialisation (or equivalent setup), depending on device options and policy.
  • If the device is re-initialised and the DKEK is wiped, encrypted backups that depended on the old DKEK cannot be recovered.
  • The DKEK is not intended to be held as a single secret by one person alone; the design allows splitting the material that builds the DKEK into shares.

What a DKEK share is

A DKEK share is a random 256-bit (32-byte) value. During setup the device is configured to expect a chosen number of shares. Each share is generated inside the device (using the card RNG), then passed to a custodian who stores it, usually as a password-encrypted file or using other formats such as a printable encoding.

The final DKEK is assembled by combining the shares with exclusive-or (XOR): each share is XORed into a running 256-bit value until all shares are imported. Published technical descriptions for SmartCard-HSM state that multiple shares are XORed into a single KEK value inside the key domain.

Important distinction: this is not Shamir secret sharing. With plain XOR composition, you normally need every share to reconstruct the DKEK. Published material also describes optional threshold schemes applied to the password that protects a share file (n-of-m password fragments), which is a separate layer from the XOR combination of random shares.

Roles in key management

  • Key domain: a logical grouping for keys and for the KEK material used to wrap them. Newer devices support multiple key domains; older or simpler profiles may expose a single DKEK domain.
  • Custodians: individuals who receive and protect distinct DKEK shares. Organisational policy decides how many shares to use (common examples in public guides are one to three shares).
  • Key check value (KCV): a short fingerprint used to confirm that the assembled DKEK on a device matches expectations before you rely on backup or import operations.

Off-card handling of shares (files and passwords)

Shares leave the device only in forms that depend on custodian-chosen secrets:

  • Password-based encryption of the share (for example .pbe files in command-line workflows).
  • Optional password threshold splitting, where several people hold fragments of the password needed to decrypt one share.
  • Alternative encodings (for example human-entered formats) aimed at offline storage.

The security of an encrypted share file depends on password strength, resistance to offline guessing, and physical protection of the storage medium.

PKCS#12 import, DKEK shares, and protected card memory (Smart Card Shell Key Manager)

In the Key Manager sources shipped with Smart Card Shell 3 (under keymanager/ in the unpacked distribution), the Key Manager loads PKCS#12 (.p12) files with BouncyCastle (KeyStore("BC", "PKCS12", ...)). The PKCS#12 password only decrypts the container on the host; it is not the DKEK. The shell then has to move private keys into the SmartCard-HSM without sending them in plaintext over the card interface. That step uses the same DKEK-based wrap format as other tooling: the host builds a key blob with DKEK.encodeKey(privateKey, publicKeyFromCertificate), and HSMKeyStore.importRSAKey / importECCKey call unwrapKey on the device so the key material is loaded into protected storage and PKCS#15-style metadata is written. Certificates are stored separately (storeEndEntityCertificate for end-entity certs, storeCACertificate when the P12 entry has no usable private key).

The excerpts below cite concrete paths and line numbers from one unpacked tree; after installation your top-level directory name typically matches the release you downloaded (for example scsh-3.x.y), and line numbers may shift in other releases.

Two implementations exist side by side; both hinge on assembling the same 256-bit DKEK on the host as the card holds when unwrapKey runs.

Shares entered on the host (classic “Import from PKCS#12” in keymanager.js)

The handler importPKCS12 asks how many DKEK shares to apply (often one). For each share, the operator supplies material through the same paths as ordinary DKEK share import (password-protected file, n-of-m password fragments, or PaperKey). Each 32-byte share is XORed into the host DKEK instance. After all shares are combined, the tool opens the PKCS#12 file, extracts the private key and certificate, forms the wrapped blob, and calls importRSAKey or importECCKey followed by certificate storage.

Share assembly and wrapping:

KeyManager.prototype.importPKCS12 = function(node) {
    var str = Dialog.prompt(KeyManager.DKEK_NO_OF_SHARES, "1");
    if (str == null) {
        return;
    }

    var shares = parseInt(str);

    var dkek = new DKEK(this.crypto);
    for (var cnt = 0; cnt < shares; cnt++) {
        var dkekshare = this.inputDKEKShare();
        dkek.importDKEKShare(dkekshare);
        dkekshare.clear();
    }
            if (key != null) {
                print("Importing key and certificate...");

                var pubkey = cert.getPublicKey();
                var blob = dkek.encodeKey(key, pubkey);

                if (pubkey.getComponent(Key.MODULUS)) {
                    hkey = this.ks.importRSAKey(alias, blob, pubkey.getSize());
                    var signalgo = Crypto.RSA_PSS_SHA256;
                } else {
                    hkey = this.ks.importECCKey(alias, blob, pubkey.getSize());
                    var signalgo = Crypto.ECDSA_SHA256;
                }

                this.ks.storeEndEntityCertificate(alias, cert);

                // Test import
                var msg = new ByteString("Hello World", ASCII);

                var signature = hkey.sign(signalgo, msg);

                assert(this.crypto.verify(pubkey, signalgo, msg, signature), "Signature verification of imported key failed");
                print("Import completed");
            } else {
                print("Importing certificate...");

                this.ks.storeCACertificate(alias, cert);

            }
            this.createOutline();
        } while (aliases.length > 1 && Dialog.prompt("Import more keys ?"));

    } while (Dialog.prompt("Import more PKCS#12 files ?"));
    dkek.clear();

For this path to succeed, the SmartCard-HSM must already contain the same DKEK that those shares define. In practice that means the device was initialised for that key domain and all required shares were imported onto the card earlier; the Key Manager session only recombines the shares on the PC so it can encrypt the import blob consistently with the firmware’s unwrap. If the host DKEK and the card DKEK differ, unwrap fails or the import is rejected.

Transient key domain for PKCS#12 (220-importp12-plugin.js)

The plug-in “Import from PKCS#12” uses a different pattern when an empty key domain slot is available: it creates a fresh DKEK key domain that expects a single share, generates one random 32-byte share on the host, imports that share into the card, XORs it in the host DKEK object, performs the same encodeKey / importRSAKey or importECCKey / certificate store sequence, then clears the host state and calls deleteKEK on that key domain identifier.

Key-domain creation, share import, and teardown:

    var kdid = -1;
    do {
        kdid++;
        var kd = sc.queryKeyDomainStatus(kdid);
        if ((kd.sw == 0x6A86) || (kd.sw == 0x6D00)) {
            Dialog.prompt("No empty key domain found.");
            return
        }
    } while (kd.sw != 0x6A88);

    // Create DKEK domain with random DKEK
    sc.createDKEKKeyDomain(kdid, 1);
    var share = crypto.generateRandom(32);
    sc.importKeyShare(kdid, share);

    // Create DKEK encoder and import share
    var dkek = new DKEK(crypto);
    dkek.importDKEKShare(share);
    } while (Dialog.prompt("Import more PKCS#12 files ?"));
    dkek.clear();
    sc.deleteKEK(kdid);

Here the share is created for a one-off import: the card and host agree on a temporary DKEK solely to wrap the PKCS#12 private key for unwrapKey. The plug-in then deletes the key-encryption key in that domain (deleteKEK); the imported asymmetric keys remain in the device’s normal key store, while the short-lived DKEK used for the transfer is not left in place for backup or migration unless you also use a longer-lived key-domain configuration elsewhere.

How many shares appear in practice

  • One share is the minimal case: a single random or custodian-held 32-byte value XORed into the DKEK.
  • Multiple shares mean the loop runs several times on the host (importDKEKShare for each); the card must have received the same number of shares during its own setup so the XOR result matches.

Operational distinction worth preserving

  • PKCS#12 password: unwraps the file on the workstation only; choose it independently of DKEK share passwords.
  • DKEK shares: define the KEK the card uses to accept wrapped private-key blobs; organisational controls (files, PaperKey, n-of-m on a share password) apply here, not to the PKCS#12 file’s own password.

Relationship to the Smart Card Shell installer

Smart Card Shell 3 is distributed as an IzPack-based, self-contained JAR (alongside zip archives) from OpenSCDP / CardContact. The downloadable artefact name follows the release, for example scsh-3.x.y-installer.jar. Current installers and archives are published at https://www.openscdp.org/scsh3/download.html. The JAR packs a large core payload (resources/packs/pack-Core) that contains JavaScript modules for card tooling.

Among those modules is a DKEK helper used with SmartCard-HSM:

  • Module path in the bundle: scsh/sc-hsm/DKEK (see require("scsh/sc-hsm/DKEK") inside the SmartCard-HSM support code).
  • It implements host-side operations that correspond to command-line tools for manipulating DKEK shares outside the card.

What the bundled DKEK helper does (implementation-level)

The following points summarise behaviour visible in the embedded DKEK.js source inside pack-Core. They describe the host library, not necessarily every firmware detail inside the chip.

  1. Internal state
    The helper keeps a 32-byte value initialised to zero. Importing a share XORs it into that value, matching the XOR assembly model.

  2. Key check value
    The KCV is derived as the first eight bytes of SHA-256 over the 32-byte DKEK value. This lets operators compare devices without revealing the full key.

  3. Wrapping keys under the DKEK
    From the 32-byte DKEK, two AES keys are derived with SHA-256 and distinct domain separation constants (labels 00000001 and 00000002 in hex) for encryption and integrity (CMAC-style use appears in the wrapping code paths). This matches the documented pattern that the DKEK seeds separate encryption and integrity keys for protected blobs.

  4. Encrypting a share for storage

    • A random eight-byte salt is generated.
    • A key and IV are produced from the salt and password using a stretching routine based on repeated MD5 processing (the code attempts a single-call MD5 with a very large iteration count, with a slower fallback loop).
    • The plaintext share is padded and encrypted with AES-CBC.
    • The on-disk form begins with the ASCII prefix Salted__ followed by the salt and ciphertext, similar in spirit to common OpenSSL password-based encryption envelopes.
  5. Card protocol surface in the same module family
    The SmartCard-HSM host wrapper issues secure-message APDUs with instruction 0x52 and function variants such as creating a DKEK key domain (P1 = 0x01) and importing a share (P1 = 0x00). Wrap and unwrap operations use other implementation-specific INS codes in the same source. Exact allowable parameters depend on firmware and initialisation state.

Practical guidance (non-exhaustive)

  • Decide how many shares you need based on organisational control, not on convenience alone. More shares increase operational friction; fewer shares increase concentration of risk.
  • Treat encrypted share files as highly sensitive; offline guessing attacks are a real concern if files leak.
  • Record KCVs and key-domain identifiers as part of your key-management records so restores can be validated.
  • Distinguish DKEK key domains from XKEK-style domains on devices that support both: XKEK domains use different agreement mechanisms (documented authenticated ECDH-based setups), whereas DKEK domains are built from imported shares as described here.

References and further reading

Limitations of this note

Firmware evolves; APDU layouts, maximum numbers of shares, and domain features depend on device generation and version. Always verify behaviour against the documentation for your exact device and software stack. This document does not analyse side-channel resistance, PKCS#11 policy, or organisational compliance requirements.

Random window resizing on MSI MS-158K laptops: a Rocky Linux fix

1. Problem

On MSI gaming laptops built on the MS-158K motherboard and running Rocky Linux 9 or 10, all open windows shrink briefly at random intervals and then snap back to their original size. There is no obvious trigger and the timing is irregular. The board carries two AMD GPUs: a Radeon Vega (Cezanne) integrated GPU at 0000:07:00.0 which drives the built-in screen, and a discrete Radeon RX 5500M (Navi 14) at 0000:03:00.0 which has no connected display output. The only trace left in the kernel log is:

[19809.613998] [drm] kiq ring mec 2 pipe 1 q 0
[19809.616987] amdgpu 0000:03:00.0: [drm] Cannot find any crtc or sizes
[19809.617003] amdgpu 0000:03:00.0: amdgpu: ring gfx_0.0.0 uses VM inv eng 0 on hub 0
... (full ring reinitialization output)
[19809.620157] amdgpu 0000:03:00.0: [drm] Cannot find any crtc or sizes

2. Diagnosis

Step 1: Identify which GPU owns the display

The MS-158K board carries two AMD GPUs: a Radeon RX 5500M discrete GPU and a Radeon Vega integrated GPU. Check which DRM card has a connected output:

for card in /sys/class/drm/card*/; do
  echo "$card: $(cat ${card}*/status 2>/dev/null | sort -u)"
done

The output should look like this:

/sys/class/drm/card0/: disconnected   ← Radeon RX 5500M, PCI 0000:03:00.0, no display
/sys/class/drm/card1/: connected      ← Radeon Vega, PCI 0000:07:00.0, eDP-1 built-in screen

card0 is the Radeon RX 5500M with no connected output. card1 is the Radeon Vega driving the laptop's built-in screen.

Step 2: Correlate the log pattern

Expanding the dmesg context around each occurrence of the log message reveals a consistent pattern appearing immediately before every window disruption:

sudo dmesg | grep -B 20 "kiq ring mec"

Every occurrence follows this sequence:

[  55.515940] pci_bus 0000:03: Allocating resources        ← dGPU waking from suspend
[  55.515971] amdgpu 0000:03:00.0: amdgpu: PSP is resuming...
[  55.753346] amdgpu 0000:03:00.0: amdgpu: SMU is resumed successfully!
[  55.754555] [drm] kiq ring mec 2 pipe 1 q 0              ← full GPU reinit
[  55.757627] amdgpu 0000:03:00.0: [drm] Cannot find any crtc or sizes
...

The line pci_bus 0000:03: Allocating resources appears immediately before every resume cycle, confirming that the kernel PCI bus is repeatedly reallocating resources as the dGPU wakes from the D3 (powered-off) power state.

Step 3: Confirm the power management state

cat /sys/bus/pci/devices/0000:03:00.0/power/control
cat /sys/bus/pci/devices/0000:03:00.0/power/runtime_status

This confirms the dGPU is under auto runtime power management, causing it to cycle between suspended and active states repeatedly.

3. Root cause

The Radeon RX 5500M at PCI address 0000:03:00.0 is repeatedly suspended and resumed by the kernel's PCI runtime power management. Each time it resumes, the kernel fires a DRM hotplug event. The Wayland compositor (GNOME Shell) reacts to that hotplug event by briefly reconfiguring the display state — which causes all windows to shrink momentarily and then restore once the compositor determines that no display change has actually taken place.

The Cannot find any crtc or sizes message is a consequence, not the cause: the Radeon RX 5500M has no connected display outputs on the MS-158K, so every time the driver queries for CRTCs after resuming, it finds none.

4. Solution

Stop the Radeon RX 5500M from entering runtime suspend by setting its power control to on.

Step 1: Test immediately (no reboot required)

echo "on" > /sys/bus/pci/devices/0000:03:00.0/power/control

If the window shrinking stops after running this command, the diagnosis is confirmed.

Step 2: Make the fix permanent via udev

A udev rule ensures the setting is applied automatically on every boot. Using the vendor ID (0x1002, AMD) and device ID (0x7340, Navi 14) is more reliable than matching on the PCI address alone, as the address can vary between kernels and firmware revisions:

cat > /etc/udev/rules.d/99-dgpu-pm.rules << 'EOF'
SUBSYSTEM=="pci", ATTR{vendor}=="0x1002", ATTR{device}=="0x7340", ATTR{power/control}="on"
EOF

Apply the rule without rebooting:

udevadm control --reload-rules
udevadm trigger

Step 3: Verify

cat /sys/bus/pci/devices/0000:03:00.0/power/control

Expected output:

on

Watch dmesg to confirm the resume cycles have stopped:

sudo dmesg -w | grep -E "Allocating resources|PSP is resuming|kiq ring"

No further output should appear once the fix is in place.

5. Notes

  • This issue has been observed on MSI gaming laptops based on the MS-158K motherboard running Rocky Linux 9 and 10. The board pairs a Radeon Vega (Cezanne) integrated GPU with a Radeon RX 5500M (Navi 14) discrete GPU. The discrete GPU has no connected display output and is left under automatic PCI power management by default, which triggers the problem.
  • The udev rule matches on the AMD vendor ID (0x1002) and the Navi 14 device ID (0x7340). This is more dependable than matching on the PCI address 0000:03:00.0, which could in principle shift between firmware updates. To find the correct IDs on another machine, run lspci -nn | grep -i vga and read the [vendor:device] values from the output.
  • Setting power control to on stops the Radeon RX 5500M from sleeping, which raises idle power consumption slightly and may reduce battery life a little. If this is a concern and the GPU is never used for rendering, blacklisting the amdgpu module for that device is an alternative — though this requires some care to avoid affecting the Radeon Vega.
  • If tlp or power-profiles-daemon is installed, it may override the udev rule after boot. Check with systemctl status tlp power-profiles-daemon. Should overriding occur, a systemd service with After=tlp.service (or the relevant daemon) can re-apply the setting, following the same approach described in the Bluetooth dongle fix post.
  • The symptom — all windows shrinking briefly then snapping back — is easy to mistake for a window manager fault, a compositor bug, or a monitoring tool accidentally resizing windows. The key diagnostic clue is the pci_bus: Allocating resources line in dmesg appearing immediately before each window disruption.

A practical guide to fixing Bluetooth mouse freezes caused by USB power management on Linux

1. Problem

A Bluetooth mouse connected via a USB dongle was freezing for 1-2 seconds intermittently. The cause was Linux USB power management suspending the Bluetooth dongle to save power.

2. Solution

Disable USB autosuspend for the Bluetooth dongle via a udev rule.

Step 1: Identify the Bluetooth Dongle

lsusb | grep -i bluetooth

In our case:

Bus 002 Device 006: ID 0bda:a728 Realtek Semiconductor Corp. Bluetooth Radio

Step 2: Verify udev Attribute Names

udevadm info -a /sys/bus/usb/devices/2-13/ | grep -E "idVendor|idProduct"

This confirmed the device uses ATTR (not ATTRS) for its own attributes.

Step 3: Create the udev Rule

cat > /etc/udev/rules.d/99-bluetooth-dongle.rules << EOF
ACTION=="add", SUBSYSTEM=="usb", ATTR{idVendor}=="0bda", ATTR{idProduct}=="a728", TEST=="power/control", ATTR{power/control}="on"
EOF

Step 4: Apply the Rule

udevadm control --reload-rules
udevadm trigger

Step 5: Verify

for dev in /sys/bus/usb/devices/*/; do
    name=$(cat "$dev/product" 2>/dev/null || echo "unknown")
    control=$(cat "$dev/power/control" 2>/dev/null)
    echo "$name  control=$control"
done | grep -i bluetooth

Expected output:

Bluetooth Radio  control=on

3. Troubleshooting: udev Rule Overridden by Power Manager

If the mouse continues to freeze after applying the udev rule, a power management service may be resetting the USB power control back to auto. Check for running power management daemons:

systemctl status tlp
systemctl status power-profiles-daemon
systemctl status tuned

In our case power-profiles-daemon was active and overriding the udev rule. The fix is to create a systemd service that enforces the setting after the daemon starts:

cat > /etc/systemd/system/usb-bluetooth-power.service << 'EOF'
[Unit]
Description=Disable USB autosuspend for Bluetooth dongle
After=power-profiles-daemon.service

[Service]
Type=oneshot
ExecStart=/bin/bash -c 'echo on > /sys/bus/usb/devices/2-13/power/control'
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target
EOF

systemctl enable --now usb-bluetooth-power.service

Verify the service is active and the setting is correct:

systemctl status usb-bluetooth-power.service
cat /sys/bus/usb/devices/2-13/power/control

Expected output: service active (exited) and control=on.

4. Further Fix: Enforcing the Setting with a systemd Timer

If the mouse continues to freeze, power-profiles-daemon is periodically resetting the power control back to auto after boot. A oneshot service only runs once and cannot counter this. The solution is a systemd timer that re-enforces the setting every 30 seconds:

cat > /etc/systemd/system/usb-bluetooth-power.service << 'EOF'
[Unit]
Description=Disable USB autosuspend for Bluetooth dongle

[Service]
Type=oneshot
ExecStart=/bin/bash -c 'echo on > /sys/bus/usb/devices/2-13/power/control'
EOF

cat > /etc/systemd/system/usb-bluetooth-power.timer << 'EOF'
[Unit]
Description=Enforce USB autosuspend off for Bluetooth dongle

[Timer]
OnBootSec=10
OnUnitActiveSec=30s

[Install]
WantedBy=timers.target
EOF

systemctl daemon-reload
systemctl enable --now usb-bluetooth-power.timer

Verify the timer is running:

systemctl status usb-bluetooth-power.timer

5. Notes

  • Always verify the correct udev attribute style with udevadm info -a first — using ATTRS instead of ATTR is a common mistake that prevents the rule matching.
  • Power management daemons such as power-profiles-daemon and tlp can override udev rules — use the systemd service workaround if this occurs.
  • The device path /sys/bus/usb/devices/2-13/ may differ on your system — verify it with the vendor/product ID loop shown in the verification step above.
  • The same approach applies to any USB device suffering from autosuspend issues.
Creative Commons - Attribution 2.5 Generic. Powered by Blogger.

IPv6 on MikroTik LTE/5G (RouterOS 7) with Vivacom (Bulgaria)

About Vivacom products and prefixes Topology assumed here Method: bind PDP IPv6 to the LAN port 1. APN profiles 2. Point the LTE...

Search This Blog

Translate