Activating Voice over LTE on Android

While looking into VoLTE for Android, you find tons of manuals, how to enable it on a phone where it is disabled.

The main parts are:

  • enable root (depends, sometime optional)
  • enable QC diag on USB
  • use the PDC tool on a lapop to switch the profile (windows, QXDM tool)

This usually works until the next sim switch or sometimes reboot of the phone.

What is the PDC?

PDC stands for Persistent Device Configuration. libqmi already supports talking to the PDC.

But how does it work?

Since every HowTo tells activate QC Diag, it must depend on QC Diag. QC Diag seems a door to a world with thousands doors and services (like in the matrix).

I'm using a Oneplus 6t with LineageOS and root enabled for this. Enable diag via: adb root; sleep 1; adb shell setprop sys.usb.config diag,adb

Somehow it didn't worked directly. The QXDM tools connected to my device, but the PDC tool didn't.

Later I tried a different command: adb root; sleep 1; adb shell setprop sys.usb.config diag,serial_cdev,rmnet,adb

Now the PDC tools shows it, but it doesn't connect, but with this error:

The PDC tool running and showing a warning: "QueryListConfigs failed with Gobi error code: 16(Malformed QMI response received)"

So it is using QMI, but over Diag?

That's very interesting. It would be very helpful while developing to access the QMI port from the dev machine instead of going over the phone OS. Also it would allow accessing it while running Android to peek and poke around.

After a reboot of the phone, it started to work with this adb line adb shell setprop sys.usb.config diag,serial_cdev,rmnet,adb.

And shows all profiles.

The PDC tool running and working. The tool is connected to the Device: "Qualcomm Wireless HS-USB Ethernet Adapter 9091".
In the middle is a table with the following columns: "Description, Type, Size, Sub0, Sub1, Sub2, Version, ID" and showing 
the profiles

So how does it work?

I have a USB sniffer around, a Cynthion (you could also use a Windows VM but since I've this tool around, I want to use it).

Showing a test setup of 2 laptops, 1 smartphone and 1 cynthion. The laptops and smartphone are connected to the cynthion

My Cynthion was already flashed with recent firmware. I use packetry to record the trace.

Paketry saves the trace as pcap and can be also used with wireshark.

While looking into it, I saw those frames in the usb output, with readable strings of the PDC profiles. My wireshark filter is just "usb".

wireshark-with-usb-volte-profile

In the lower corner, in the hexview you can find a name of a carrier profile: TGL_Comb_Attach-Lab-CMCC Now I'm looking into the whole USB packet.

Exporting the control USB packet from wireshark to look more into it.

01500080240104280028004400010200000010040026000000110400b86500001219001854474c5f436f6d625f4174746163682d4c61622d434d4343130400462001081404000000000016040046200108

014c008024010429002800400001020000001004002700000011040004b90000121500144e53494f545f566f4c54452d4c61622d434d4343130400552001081404000000000016040055200108

Now I'm trying to figure out, where the QMI header is in this packet start. Starting with the QMI header from libqmi/src/libqmi-glib/qmi-message.c (git rev: 22405e4c9e1d8f10748f085fc2ea4aa411cdd3aa).

struct full_message {
      guint8 marker;
      union {
          struct qmux_header qmux;
          struct qrtr_header qrtr;
      } header;
      union {
          struct control_message control;
          struct service_message service;
      } qmi;
} PACKED;

Replacing the unions by using QMUX and a service, because it must be a service. It could be also a QRTR message instead of QMUX, but I'll start trying to use QRTR. The struct will look:

struct full_message {
    guint8 marker;
    struct qmux_header {
        guint16 length;
        guint8 flags;
        guint8 service;
        guint8 client;
    }
    struct service_message {
        struct service_header {
            guint8 flags;
            guint16 transaction;
            guint16 message;
            guint16 tlv_length;
        }
        struct tlv tlv[];
    }
}

Taking the hex reprensentation:

01500080240104280028004400010200000010040026000000110400b86500001219001854474c5f436f6d625f4174746163682d4c61622d434d4343130400462001081404000000000016040046200108

It contains 81 byte or 0x51 bytes. Other infos we might know is the PDC service number (from libqmi).

// src/libqmi-glib/qmi-enums.h
QMI_SERVICE_PDC     = 0x24

Looking for a 0x24 in the data:

01 50 00 80 24 01 04280028004400010200000010040026000000110400b86500001219001854474c5f436f6d625f4174746163682d4c61622d434d4343130400462001081404000000000016040046200108

// Merging it into the struct:
    guint8 marker = 0x01;
    struct qmux_header {
        guint16 length; = 0x0050
        guint8 flags = 0x80;
        guint8 service = 0x24;
        guint8 client = 0x01;
    }

This looks already very good. The length 0x0050 is only 1 byte of our full length. Also the marker is always set to 0x01. But there isn't any data in front it. I would have expected some Diag encapsulation or prefix. Typical 0x7e in front of it or otherwise escaped.

Trying to validate the QMI data further. QMI is using TLV (Type-Length-Data) to split the values. With 1 byte type, 2 byte length, 'length' bytes value and QMI is usually encoding those sorted by the type.

struct tlv {
  guint8 type;
  guint16 length;
  guint8 value[];
};
[01] [5000 80 24 01] [04 2800 2800 4400] [010200000010040026000000110400b86500001219001854474c5f436f6d625f4174746163682d4c61622d434d4343130400462001081404000000000016040046200108]

// Merging it into the struct:
    guint8 marker = 0x01;
    struct qmux_header {
        guint16 length; = 0x0050
        guint8 flags = 0x80;
        guint8 service = 0x24;
        guint8 client = 0x01;
    }
    struct service_message {
        struct service_header {
            guint8 flags = 0x04;
            guint16 transaction = 0x0028;
            guint16 message = 0x0028;
            guint16 tlv_length = 0x0044;
        }
        struct tlv tlv[]; // <- this should be 0x0044 bytes long, which also matches our remaining data.
    }

Decoding further by hand:

(Only TLV part: T Length Value)
[01 0200 0000   10 0400 26000000 11 0400 b8650000 12 1900 1854474c5f436f6d625f4174746163682d4c61622d434d4343 13 0400 46200108 14 0400 00000000 16 0400 46200108]

Type   Len  Value
    01 0200 0000
    10 0400 27000000
    11 0400 04b90000
    12 1500 144e53494f545f566f4c54452d4c61622d434d4343
    13 0400 55200108
    14 0400 00000000
    16 0400 55200108

So is correct and matches \o/. But why isn't there a QC Diag header in front?

The pcap shows the packets are exchanged with endpoint: (wIndex 0x2 which is part of the 2nd Interface). To know more about this endpoint, we must look at the USB descriptor and the choosen configuration. Look for the GET DESCRIPTOR Response CONFIGURATION packet, here is a wireshark snippet from the trace:

USB Link Layer
USB URB
CONFIGURATION DESCRIPTOR
INTERFACE DESCRIPTOR (0.0): class Vendor Specific
    bLength: 9
    bDescriptorType: 0x04 (INTERFACE)
    bInterfaceNumber: 0
    bAlternateSetting: 0
    bNumEndpoints: 2
    bInterfaceClass: Vendor Specific (0xff)
    bInterfaceSubClass: 0xff
    bInterfaceProtocol: 0x30
    iInterface: 0
ENDPOINT DESCRIPTOR
    bLength: 7
    bDescriptorType: 0x05 (ENDPOINT)
    bEndpointAddress: 0x81  IN  Endpoint:1
    bmAttributes: 0x02
    wMaxPacketSize: 512
    bInterval: 0
ENDPOINT DESCRIPTOR
    bLength: 7
    bDescriptorType: 0x05 (ENDPOINT)
    bEndpointAddress: 0x01  OUT  Endpoint:1
    bmAttributes: 0x02
    wMaxPacketSize: 512
    bInterval: 0
INTERFACE DESCRIPTOR (1.0): class Vendor Specific
    bLength: 9
    bDescriptorType: 0x04 (INTERFACE)
    bInterfaceNumber: 1
    bAlternateSetting: 0
    bNumEndpoints: 3
    bInterfaceClass: Vendor Specific (0xff)
    bInterfaceSubClass: 0x00
    bInterfaceProtocol: 0x00
    iInterface: 0
UNKNOWN DESCRIPTOR
UNKNOWN DESCRIPTOR
UNKNOWN DESCRIPTOR
UNKNOWN DESCRIPTOR
ENDPOINT DESCRIPTOR
    bLength: 7
    bDescriptorType: 0x05 (ENDPOINT)
    bEndpointAddress: 0x83  IN  Endpoint:3
    bmAttributes: 0x03
    wMaxPacketSize: 10
    bInterval: 9
ENDPOINT DESCRIPTOR
    bLength: 7
    bDescriptorType: 0x05 (ENDPOINT)
    bEndpointAddress: 0x82  IN  Endpoint:2
    bmAttributes: 0x02
    wMaxPacketSize: 512
    bInterval: 0
ENDPOINT DESCRIPTOR
    bLength: 7
    bDescriptorType: 0x05 (ENDPOINT)
    bEndpointAddress: 0x02  OUT  Endpoint:2       <- This is our target. Scroll up to which interface this endpoint belongs.
        0... .... = Direction: OUT Endpoint
        .... 0010 = Endpoint Number: 0x2
    bmAttributes: 0x02
    wMaxPacketSize: 512
    bInterval: 0

So our endpoint 0x02 is part of the bInterface 1 with the interface class "Vendor Specific (0xff)", bInterfaceSubClass: 0x00, bInterfaceProtocol: 0x00.

After looking into it further. This interface is the rmnet part the adb shell setprop sys.usb.config diag,serial_cdev,rmnet,adb. rmnet is the same mode which most of the Qualcomm based mini-pcie and m.2 devices uses over USB.

libqmi can just interact with it if the linux kernel is exposing it as /dev/cdc-wdm. The linux kernel depends on the usb id and the amount of interface.

Sadly I hoped to find a path to QMI over QC Diag.

Unlock VoLTE by using libqmi on Linux

Now putting everything together: We will use a Linux on a Laptop to change the profile via QMI on a OnePlus6T with LineageOS on it (and root).

I've inserted a different Simcard into the Oneplus 6T and VoLTE doesn't work.

qmicli -d /dev/cdc-wdm0 --pdc-list-configs=software

And looking for the active profile:

Configuration 2:
    Description: Oversea-Commercial_DS
    Type:        software
    Size:        78772
    Status:      Active
    Version:     0x801F13A
    ID:          87:CA:4A:4D:52:48:CF:BF:13:2A:DB:C4:08:B2:2A:29:E0:CA:A5:67

Ok, the Oversae-Commercial_DS doesn't work. Here is a complete list of my device:

qmicli -d /dev/cdc-wdm0 --pdc-list-configs=software
Total configurations: 25
Configuration 1:
    Description: Telefonica_UK_Commercial
    Type:        software
    Size:        107336
    Status:      Inactive
    Version:     0x8010C9D
    ID:          E6:84:2A:02:AE:DA:D1:1A:08:14:E1:F4:CF:93:ED:6D:E9:7E:68:DC

Configuration 2:
    Description: Norway_Telia_Commercial
    Type:        software
    Size:        109448
    Status:      Active
    Version:     0x8012432
    ID:          BA:0F:0B:0E:9E:BC:0C:38:29:7F:C0:D9:AB:B9:9C:80:9E:7D:B2:AB

Configuration 3:
    Description: Oversea-Commercial_DS
    Type:        software
    Size:        78772
    Status:      Inactive
    Version:     0x801F13A
    ID:          87:CA:4A:4D:52:48:CF:BF:13:2A:DB:C4:08:B2:2A:29:E0:CA:A5:67

Configuration 4:
    Description: Telenor_Denmark_Commercial
    Type:        software
    Size:        105796
    Status:      Inactive
    Version:     0x8014326
    ID:          75:69:C0:4B:1C:63:AF:5C:3E:5A:9C:AA:0E:6D:BF:9A:D1:BB:64:D7

Configuration 5:
    Description: ROW_Commercial
    Type:        software
    Size:        45556
    Status:      Inactive
    Version:     0x8010809
    ID:          81:8A:AB:E9:B5:CD:F9:5C:89:FB:04:06:ED:E4:6C:6B:85:D7:20:3A

Configuration 6:
    Description: YTL_Commercial
    Type:        software
    Size:        49412
    Status:      Inactive
    Version:     0x8012D0C
    ID:          30:7F:09:F0:1B:0A:CD:A1:63:5C:BC:F8:AA:BB:5E:AC:18:48:D1:17

Configuration 7:
    Description: TaiwanMobile_Commercial
    Type:        software
    Size:        99208
    Status:      Inactive
    Version:     0x8014108
    ID:          3C:C1:1C:E0:A5:9B:73:C2:1E:8E:D6:0D:87:20:2E:DB:78:D8:91:0A

Configuration 8:
    Description: FarEastOne_Taiwan_Commercial
    Type:        software
    Size:        105312
    Status:      Inactive
    Version:     0x8014009
    ID:          BA:13:DC:4D:F5:3D:47:73:17:62:21:B8:2F:09:F5:2B:22:37:74:25

Configuration 9:
    Description: ChunghwaTel_Taiwan_Commercial
    Type:        software
    Size:        104792
    Status:      Inactive
    Version:     0x8014F09
    ID:          56:6E:CB:75:81:FA:C3:3F:65:46:8F:8F:16:2D:8F:D3:4D:5A:06:9C

Configuration 10:
    Description: MTNL-BSNL
    Type:        software
    Size:        78088
    Status:      Inactive
    Version:     0x801ED31
    ID:          3A:67:96:BB:B4:E1:07:40:FB:75:11:A2:E0:1A:5E:EC:27:C8:77:64

Configuration 11:
    Description: Volte_OEM_Lab
    Type:        software
    Size:        71016
    Status:      Inactive
    Version:     0x801F221
    ID:          D8:EF:7F:E2:16:24:38:15:AB:FD:BA:CE:2F:B6:A8:A3:49:30:C4:62

Configuration 12:
    Description: Volte_OEM_PTCRB
    Type:        software
    Size:        58864
    Status:      Inactive
    Version:     0x801EE2D
    ID:          0D:14:B5:E9:D2:52:AA:21:6C:FD:7D:C0:63:91:B8:6F:38:6F:CF:D3

Configuration 13:
    Description: CDMAless-Verizon
    Type:        software
    Size:        109344
    Status:      Inactive
    Version:     0x8010108
    ID:          FE:D6:7A:D3:0C:2F:8A:49:B0:A3:00:9C:80:09:A5:84:47:83:FA:7B

Configuration 14:
    Description: Commercial-TMO
    Type:        software
    Size:        70656
    Status:      Inactive
    Version:     0x8010536
    ID:          2F:1D:0C:4C:28:2D:50:FC:03:84:D8:9E:F3:92:81:C7:DB:A5:1A:7E

Configuration 15:
    Description: VoLTE-ATT
    Type:        software
    Size:        69028
    Status:      Inactive
    Version:     0x8010348
    ID:          12:A1:7C:8A:11:6E:A9:2F:D1:A7:98:57:AB:7A:E7:A3:E0:71:D5:99

Configuration 16:
    Description: FirstNet
    Type:        software
    Size:        69084
    Status:      Inactive
    Version:     0x8010337
    ID:          E6:E0:E0:E4:52:27:E0:C8:FC:6D:41:6D:FD:6D:31:E1:29:AC:C5:9E

Configuration 17:
    Description: Netherlands-VoLTE-Vodafone
    Type:        software
    Size:        105220
    Status:      Inactive
    Version:     0x80104F6
    ID:          EC:5E:DD:F2:73:13:73:23:1A:04:00:D6:F6:17:EB:FA:D3:56:A7:B5

Configuration 18:
    Description: Telia_Sweden
    Type:        software
    Size:        108948
    Status:      Inactive
    Version:     0x8012411
    ID:          86:DB:03:CF:2C:8E:13:43:D9:D4:42:69:0C:05:95:AF:77:A6:2D:A3

Configuration 19:
    Description: Telia_Finland
    Type:        software
    Size:        107208
    Status:      Inactive
    Version:     0x8012446
    ID:          14:1C:D1:A5:B3:82:B0:88:76:43:26:DC:2B:52:96:5D:17:ED:BD:1C

Configuration 20:
    Description: Telia_Denmark
    Type:        software
    Size:        108744
    Status:      Inactive
    Version:     0x8012486
    ID:          6B:7E:98:85:3B:64:BB:94:CF:10:E5:3B:C0:66:3E:00:BD:DE:6A:C5

Configuration 21:
    Description: Telenor_Sweden_Commercial
    Type:        software
    Size:        108956
    Status:      Inactive
    Version:     0x8014305
    ID:          6C:60:AD:96:58:90:36:F3:6E:63:00:B1:AB:3B:99:3B:D6:FD:E4:77

Configuration 22:
    Description: H3G_UK_Commercial
    Type:        software
    Size:        106264
    Status:      Inactive
    Version:     0x8012A75
    ID:          FD:E7:BF:6E:BF:F6:53:37:49:7B:A0:8E:37:8D:ED:D1:7D:26:D1:B6

Configuration 23:
    Description: H3G_Denmark_Commercial
    Type:        software
    Size:        105944
    Status:      Inactive
    Version:     0x8012A98
    ID:          74:64:58:13:06:F6:6B:20:0A:A2:0B:1B:EB:D2:5A:CB:9E:75:B4:CD

Configuration 24:
    Description: Elisa_Finland
    Type:        software
    Size:        106220
    Status:      Inactive
    Version:     0x8014418
    ID:          5A:A8:80:FD:97:47:95:9B:D1:F4:E5:9C:4B:74:A0:6F:C5:10:B4:AB

Configuration 25:
    Description: Commercial-EE
    Type:        software
    Size:        107940
    Status:      Inactive
    Version:     0x8012220
    ID:          4D:67:BF:E2:C6:F9:E6:90:F9:90:A1:28:2D:AD:2A:FC:BD:CB:44:91

So which one will give me working VoLTE? I've no idea, but I'll throw a random dice and pick Norway_Telia_Commercial.

The Telia profile works for my german simcard. But better is Volte_OEM_Lab. The Telia profile also sets a specific APN when attaching to the LTE network (even when its configured on Android for a different one).

qmicli -d /dev/cdc-wdm0 --pdc-activate-config=software,D8:EF:7F:E2:16:24:38:15:AB:FD:BA:CE:2F:B6:A8:A3:49:30:C4:62
[/dev/cdc-wdm0] Successfully requested config activation

Great! Now let's take a look into the debug menu reachable via *#*#4636#*#* and select 'Phone information' and on the 3 dots menu 'IMS Service Status'.

There you can see:

IMS Registration

Yay, it works.

links

social