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:

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.

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).

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".

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:

Yay, it works.