Unlocking the Qualcomm Snapdragon Sensor Core
Part 2: Collecting Information
Last time we got a step closer to working sensors on mainline Linux by powering on and booting the SLPI successfully. With that easy part out of the way, we can now deal with the hard part: Finding out how to communicate with it and getting something meaningful out of it. Usually when bringing up hardware in the mainline kernel, the downstream kernel drivers, and/or if available, datasheets are used to understand how the hardware works and how to operate it. With it having no open-source drivers nor public documentation however, figuring out how the SSC works will be a challenge on a whole new level.
Without any direct reference material, it would seem to be an impossible task at first, but when looking at it from a different perspective, even the proprietary HAL driver can be used as reference. After all, we know for a fact that it works, so there must be something that can be learned from it.
There are a few ways to understand how the HAL driver works without having its source code on hand. It is possible to disassemble the binary and try to read through assembly code, but getting any meaningful information that way will be quite cumbersome, not to mention the legal uncertainties surrounding this method. A much better alternative in this case would be to run the driver normally, then monitor its interactions with the SSC. After understanding what it does and what for, It should be possible to then imitate it and get identical results from the SSC.
Since it has to talk to hardware, it will have to interact with the kernel using syscalls. Tracing the syscalls it makes will be a good starting point, and for that, the Linux syscall tracer – usually known as strace
– is the perfect tool. Using strace
will allow for monitoring all interactions between the HAL driver and the Linux kernel, and by extension, the SLPI. It can also attach to already running processes, making it easy to collect some data without any preparation, as long as root privileges are available.
Watching the SSC
The main part of the HAL driver is the sensor service android.hardware.sensors@1.0-service
, which is started at boot by the init system. Before attaching strace
to it, its PID has to be found:
# pidof android.hardware.sensors@1.0-service
582
Then the -p
argument can be used to attach strace
to it:
# strace -p 582
strace: Process 582 attached
ioctl(3, BINDER_WRITE_READ, 0x7fc7f43d20) = 0
Quite underwhelming. Could this be the wrong process? Perhaps the right one can be found using htop
:
PID USER PRI NI VIRT RES SHR S CPU% MEM% TIME+ Command
610 system 20 0 11.4G 2732 2076 S 0.0 0.1 0:01.05 /vendor/bin/hw/android.hardware.sensors@1.0-service
612 system 20 0 11.4G 2732 2076 S 0.0 0.1 0:00.76 /vendor/bin/hw/android.hardware.sensors@1.0-service
613 system 20 0 11.4G 2732 2076 S 0.0 0.1 0:00.76 /vendor/bin/hw/android.hardware.sensors@1.0-service
771 system 20 0 11.4G 2732 2076 S 0.0 0.1 0:35.28 /vendor/bin/hw/android.hardware.sensors@1.0-service
773 system 20 0 11.4G 2732 2076 S 0.0 0.1 0:00.00 /vendor/bin/hw/android.hardware.sensors@1.0-service
774 system 20 0 11.4G 2732 2076 S 0.0 0.1 0:00.70 /vendor/bin/hw/android.hardware.sensors@1.0-service
780 system 20 0 11.4G 2732 2076 S 0.0 0.1 0:00.19 /vendor/bin/hw/android.hardware.sensors@1.0-service
818 system 20 0 11.4G 2732 2076 S 0.0 0.1 0:00.00 /vendor/bin/hw/android.hardware.sensors@1.0-service
819 system 20 0 11.4G 2732 2076 S 0.0 0.1 0:00.67 /vendor/bin/hw/android.hardware.sensors@1.0-service
820 system 20 0 11.4G 2732 2076 S 0.0 0.1 0:00.00 /vendor/bin/hw/android.hardware.sensors@1.0-service
821 system 20 0 11.4G 2732 2076 S 0.0 0.1 0:00.66 /vendor/bin/hw/android.hardware.sensors@1.0-service
827 system 20 0 11.4G 2732 2076 S 0.0 0.1 0:00.00 /vendor/bin/hw/android.hardware.sensors@1.0-service
...
There are actually many android.hardware.sensors@1.0-service
processes, so it appears that the sensor service has multiple threads running. Fortunately, strace
has a -f
argument to follow forks, allowing us to attach to all running threads of the sensor service at the same time:
# strace -p 582 -f
strace: Process 582 attached with 70 threads
[pid 2475] ppoll([{fd=107, events=POLLIN}, {fd=106, events=POLLIN}], 2, NULL, NULL, 0 <unfinished ...>
[pid 2473] ppoll([{fd=104, events=POLLIN}, {fd=103, events=POLLIN}], 2, NULL, NULL, 0 <unfinished ...>
[pid 1735] ppoll([{fd=101, events=POLLIN}, {fd=100, events=POLLIN}], 2, NULL, NULL, 0 <unfinished ...>
[pid 1734] ppoll([{fd=15, events=POLLIN}, {fd=14, events=POLLIN}], 2, NULL, NULL, 0 <unfinished ...>
[pid 982] futex(0x715777ad00, FUTEX_WAIT_BITSET_PRIVATE, 747816, NULL, FUTEX_BITSET_MATCH_ANY <unfinished ...>
[pid 980] futex(0x71576f55e8, FUTEX_WAIT_BITSET_PRIVATE, 1219316, NULL, FUTEX_BITSET_MATCH_ANY <unfinished ...>
[pid 977] ppoll([{fd=98, events=POLLIN}, {fd=97, events=POLLIN}], 2, NULL, NULL, 0 <unfinished ...>
[pid 973] ppoll([{fd=95, events=POLLIN}, {fd=94, events=POLLIN}], 2, NULL, NULL, 0 <unfinished ...>
[pid 972] futex(0x72078ff150, FUTEX_WAIT_BITSET_PRIVATE, 4294967294, NULL, FUTEX_BITSET_MATCH_ANY <unfinished ...>
[pid 976] rt_sigtimedwait([RTMIN], <unfinished ...>
[pid 975] futex(0x72078ff610, FUTEX_WAIT_BITSET_PRIVATE, 4294967294, NULL, FUTEX_BITSET_MATCH_ANY <unfinished ...>
[pid 970] ppoll([{fd=92, events=POLLIN}, {fd=91, events=POLLIN}], 2, NULL, NULL, 0 <unfinished ...>
[pid 969] futex(0x72078ff650, FUTEX_WAIT_BITSET_PRIVATE, 4294967294, NULL, FUTEX_BITSET_MATCH_ANY <unfinished ...>
[pid 966] futex(0x72078fd790, FUTEX_WAIT_BITSET_PRIVATE, 4294967294, NULL, FUTEX_BITSET_MATCH_ANY <unfinished ...>
[pid 967] ppoll([{fd=89, events=POLLIN}, {fd=88, events=POLLIN}], 2, NULL, NULL, 0 <unfinished ...>
[pid 963] ppoll([{fd=86, events=POLLIN}, {fd=85, events=POLLIN}], 2, NULL, NULL, 0 <unfinished ...>
[pid 962] futex(0x72078fd550, FUTEX_WAIT_BITSET_PRIVATE, 4294967294, NULL, FUTEX_BITSET_MATCH_ANY <unfinished ...>
[pid 958] futex(0x72078fdf90, FUTEX_WAIT_BITSET_PRIVATE, 4294967294, NULL, FUTEX_BITSET_MATCH_ANY <unfinished ...>
[pid 959] ppoll([{fd=83, events=POLLIN}, {fd=82, events=POLLIN}], 2, NULL, NULL, 0 <unfinished ...>
[pid 954] ppoll([{fd=80, events=POLLIN}, {fd=79, events=POLLIN}], 2, NULL, NULL, 0 <unfinished ...>
[pid 953] futex(0x72078ff310, FUTEX_WAIT_BITSET_PRIVATE, 4294967294, NULL, FUTEX_BITSET_MATCH_ANY <unfinished ...>
[pid 948] ppoll([{fd=77, events=POLLIN}, {fd=76, events=POLLIN}], 2, NULL, NULL, 0 <unfinished ...>
[pid 947] futex(0x72078ff710, FUTEX_WAIT_BITSET_PRIVATE, 4294967294, NULL, FUTEX_BITSET_MATCH_ANY <unfinished ...>
[pid 939] ppoll([{fd=71, events=POLLIN}, {fd=70, events=POLLIN}], 2, NULL, NULL, 0 <unfinished ...>
[pid 944] ppoll([{fd=74, events=POLLIN}, {fd=73, events=POLLIN}], 2, NULL, NULL, 0 <unfinished ...>
[pid 943] futex(0x72078fec10, FUTEX_WAIT_BITSET_PRIVATE, 4294967294, NULL, FUTEX_BITSET_MATCH_ANY <unfinished ...>
[pid 942] futex(0x72078ff010, FUTEX_WAIT_BITSET_PRIVATE, 4294967294, NULL, FUTEX_BITSET_MATCH_ANY <unfinished ...>
[pid 938] futex(0x72078ff090, FUTEX_WAIT_BITSET_PRIVATE, 4294967294, NULL, FUTEX_BITSET_MATCH_ANY <unfinished ...>
[pid 933] ppoll([{fd=68, events=POLLIN}, {fd=67, events=POLLIN}], 2, NULL, NULL, 0 <unfinished ...>
[pid 932] futex(0x72078ff1d0, FUTEX_WAIT_BITSET_PRIVATE, 4294967294, NULL, FUTEX_BITSET_MATCH_ANY <unfinished ...>
[pid 927] ppoll([{fd=65, events=POLLIN}, {fd=64, events=POLLIN}], 2, NULL, NULL, 0 <unfinished ...>
[pid 921] ppoll([{fd=62, events=POLLIN}, {fd=61, events=POLLIN}], 2, NULL, NULL, 0 <unfinished ...>
[pid 926] futex(0x72078fed50, FUTEX_WAIT_BITSET_PRIVATE, 4294967294, NULL, FUTEX_BITSET_MATCH_ANY <unfinished ...>
[pid 920] futex(0x72078fec90, FUTEX_WAIT_BITSET_PRIVATE, 4294967294, NULL, FUTEX_BITSET_MATCH_ANY <unfinished ...>
[pid 916] ppoll([{fd=56, events=POLLIN}, {fd=55, events=POLLIN}], 2, NULL, NULL, 0 <unfinished ...>
[pid 917] futex(0x72078fe150, FUTEX_WAIT_BITSET_PRIVATE, 4294967294, NULL, FUTEX_BITSET_MATCH_ANY <unfinished ...>
[pid 918] ppoll([{fd=59, events=POLLIN}, {fd=58, events=POLLIN}], 2, NULL, NULL, 0 <unfinished ...>
[pid 915] futex(0x72078fe790, FUTEX_WAIT_BITSET_PRIVATE, 4294967294, NULL, FUTEX_BITSET_MATCH_ANY <unfinished ...>
[pid 913] ppoll([{fd=53, events=POLLIN}, {fd=52, events=POLLIN}], 2, NULL, NULL, 0 <unfinished ...>
[pid 912] futex(0x72078fe350, FUTEX_WAIT_BITSET_PRIVATE, 4294967294, NULL, FUTEX_BITSET_MATCH_ANY <unfinished ...>
[pid 903] ppoll([{fd=50, events=POLLIN}, {fd=49, events=POLLIN}], 2, NULL, NULL, 0 <unfinished ...>
[pid 902] futex(0x72078fe010, FUTEX_WAIT_BITSET_PRIVATE, 4294967294, NULL, FUTEX_BITSET_MATCH_ANY <unfinished ...>
[pid 895] ppoll([{fd=47, events=POLLIN}, {fd=46, events=POLLIN}], 2, NULL, NULL, 0 <unfinished ...>
[pid 894] futex(0x72078fe8d0, FUTEX_WAIT_BITSET_PRIVATE, 4294967294, NULL, FUTEX_BITSET_MATCH_ANY <unfinished ...>
[pid 885] ppoll([{fd=44, events=POLLIN}, {fd=43, events=POLLIN}], 2, NULL, NULL, 0 <unfinished ...>
[pid 884] futex(0x72078febd0, FUTEX_WAIT_BITSET_PRIVATE, 4294967294, NULL, FUTEX_BITSET_MATCH_ANY <unfinished ...>
[pid 876] ppoll([{fd=41, events=POLLIN}, {fd=40, events=POLLIN}], 2, NULL, NULL, 0 <unfinished ...>
[pid 875] futex(0x72078fea90, FUTEX_WAIT_BITSET_PRIVATE, 4294967294, NULL, FUTEX_BITSET_MATCH_ANY <unfinished ...>
[pid 854] ppoll([{fd=35, events=POLLIN}, {fd=34, events=POLLIN}], 2, NULL, NULL, 0 <unfinished ...>
[pid 857] futex(0x72078feb90, FUTEX_WAIT_BITSET_PRIVATE, 4294967294, NULL, FUTEX_BITSET_MATCH_ANY <unfinished ...>
[pid 853] futex(0x72078fdfd0, FUTEX_WAIT_BITSET_PRIVATE, 4294967294, NULL, FUTEX_BITSET_MATCH_ANY <unfinished ...>
[pid 858] ppoll([{fd=38, events=POLLIN}, {fd=37, events=POLLIN}], 2, NULL, NULL, 0 <unfinished ...>
[pid 846] ppoll([{fd=32, events=POLLIN}, {fd=31, events=POLLIN}], 2, NULL, NULL, 0 <unfinished ...>
[pid 845] futex(0x72078fe410, FUTEX_WAIT_BITSET_PRIVATE, 4294967294, NULL, FUTEX_BITSET_MATCH_ANY <unfinished ...>
[pid 832] futex(0x72078fe290, FUTEX_WAIT_BITSET_PRIVATE, 4294967294, NULL, FUTEX_BITSET_MATCH_ANY <unfinished ...>
[pid 828] ppoll([{fd=26, events=POLLIN}, {fd=25, events=POLLIN}], 2, NULL, NULL, 0 <unfinished ...>
[pid 833] ppoll([{fd=29, events=POLLIN}, {fd=28, events=POLLIN}], 2, NULL, NULL, 0 <unfinished ...>
[pid 827] futex(0x72078fe250, FUTEX_WAIT_BITSET_PRIVATE, 4294967294, NULL, FUTEX_BITSET_MATCH_ANY <unfinished ...>
[pid 821] ppoll([{fd=23, events=POLLIN}, {fd=22, events=POLLIN}], 2, NULL, NULL, 0 <unfinished ...>
[pid 820] futex(0x72078fe0d0, FUTEX_WAIT_BITSET_PRIVATE, 4294967294, NULL, FUTEX_BITSET_MATCH_ANY <unfinished ...>
[pid 819] ppoll([{fd=20, events=POLLIN}, {fd=19, events=POLLIN}], 2, NULL, NULL, 0 <unfinished ...>
[pid 780] futex(0x72078fd410, FUTEX_WAIT_BITSET_PRIVATE, 4294967294, NULL, FUTEX_BITSET_MATCH_ANY <unfinished ...>
[pid 774] ppoll([{fd=12, events=POLLIN}, {fd=11, events=POLLIN}], 2, NULL, NULL, 0 <unfinished ...>
[pid 773] futex(0x72078fd8d0, FUTEX_WAIT_BITSET_PRIVATE, 4294967294, NULL, FUTEX_BITSET_MATCH_ANY <unfinished ...>
[pid 771] futex(0x72078fd4d0, FUTEX_WAIT_BITSET_PRIVATE, 4294967294, NULL, FUTEX_BITSET_MATCH_ANY <unfinished ...>
[pid 613] ppoll([{fd=8, events=POLLIN}, {fd=7, events=POLLIN}], 2, NULL, NULL, 0 <unfinished ...>
[pid 612] read(6, <unfinished ...>
[pid 610] pselect6(6, [5], NULL, [5], NULL, NULL <unfinished ...>
[pid 582] ioctl(3, BINDER_WRITE_READ <unfinished ...>
[pid 818] futex(0x72078fe210, FUTEX_WAIT_BITSET_PRIVATE, 4294967294, NULL, FUTEX_BITSET_MATCH_ANY <unfinished ...>
[pid 1734] <... ppoll resumed> ) = 1 ([{fd=14, revents=POLLIN}])
[pid 1734] recvfrom(14, "\4\226\5\3\0$\0\1\4\0\275\23zo\2\10\0\331\0\200R\203`\336\26\3\4\0\0\0\0\0"..., 56, MSG_DONTWAIT, {sa_family=AF_IB, sa_data="\0\0\2\0\0\0\1\0\0\0)\0\0\0\0\0\0\0"}, [20]) = 43
[pid 1734] rt_sigprocmask(0x539a1968 /* SIG_??? */, NULL, [USR2 RTMIN], 8) = 0
[pid 1734] rt_sigprocmask(0x539a1958 /* SIG_??? */, NULL, [USR2 RTMIN], 8) = 0
[pid 1734] futex(0x72078fd410, FUTEX_WAKE_PRIVATE, 2147483647) = 1
[pid 780] <... futex resumed> ) = 0
[pid 1734] ppoll([{fd=15, events=POLLIN}, {fd=14, events=POLLIN}], 2, NULL, NULL, 0 <unfinished ...>
[pid 780] futex(0x72078fd410, FUTEX_WAIT_BITSET_PRIVATE, 4294967294, NULL, FUTEX_BITSET_MATCH_ANY <unfinished ...>
[pid 1734] <... ppoll resumed> ) = 1 ([{fd=14, revents=POLLIN}])
[pid 1734] recvfrom(14, "\4\227\5\3\0$\0\1\4\0\23\24\177o\2\10\0\263\217\263\246\205`\336\26\3\4\0\0\0\0\0"..., 56, MSG_DONTWAIT, {sa_family=AF_IB, sa_data="\0\0\2\0\0\0\1\0\0\0)\0\0\0\0\0\0\0"}, [20]) = 43
[pid 1734] rt_sigprocmask(0x539a1968 /* SIG_??? */, NULL, [USR2 RTMIN], 8) = 0
[pid 1734] rt_sigprocmask(0x539a1958 /* SIG_??? */, NULL, [USR2 RTMIN], 8) = 0
[pid 1734] futex(0x72078fd410, FUTEX_WAKE_PRIVATE, 2147483647) = 1
[pid 1734] ppoll([{fd=15, events=POLLIN}, {fd=14, events=POLLIN}], 2, NULL, NULL, 0 <unfinished ...>
[pid 780] <... futex resumed> ) = 0
[pid 780] futex(0x72078fd410, FUTEX_WAIT_BITSET_PRIVATE, 4294967294, NULL, FUTEX_BITSET_MATCH_ANY <unfinished ...>
[pid 1734] <... ppoll resumed> ) = 1 ([{fd=14, revents=POLLIN}])
[pid 1734] recvfrom(14, "\4\230\5\3\0$\0\1\4\0o\24\204o\2\10\0\22\216\352\372\207`\336\26\3\4\0\0\0\0\0"..., 56, MSG_DONTWAIT, {sa_family=AF_IB, sa_data="\0\0\2\0\0\0\1\0\0\0)\0\0\0\0\0\0\0"}, [20]) = 43
[pid 1734] rt_sigprocmask(0x539a1968 /* SIG_??? */, NULL, [USR2 RTMIN], 8) = 0
[pid 1734] rt_sigprocmask(0x539a1958 /* SIG_??? */, NULL, [USR2 RTMIN], 8) = 0
[pid 1734] futex(0x72078fd410, FUTEX_WAKE_PRIVATE, 2147483647) = 1
[pid 780] <... futex resumed> ) = 0
[pid 1734] ppoll([{fd=15, events=POLLIN}, {fd=14, events=POLLIN}], 2, NULL, NULL, 0 <unfinished ...>
[pid 780] futex(0x72078fd410, FUTEX_WAIT_BITSET_PRIVATE, 4294967294, NULL, FUTEX_BITSET_MATCH_ANY <unfinished ...>
[pid 1734] <... ppoll resumed> ) = 1 ([{fd=14, revents=POLLIN}])
[pid 1734] recvfrom(14, "\4\231\5\3\0$\0\1\4\0\374\24\211o\2\10\0 \3357O\212`\336\26\3\4\0\0\0\0\0"..., 56, MSG_DONTWAIT, {sa_family=AF_IB, sa_data="\0\0\2\0\0\0\1\0\0\0)\0\0\0\0\0\0\0"}, [20]) = 43
[pid 1734] rt_sigprocmask(0x539a1968 /* SIG_??? */, NULL, [USR2 RTMIN], 8) = 0
[pid 1734] rt_sigprocmask(0x539a1958 /* SIG_??? */, NULL, [USR2 RTMIN], 8) = 0
[pid 1734] futex(0x72078fd410, FUTEX_WAKE_PRIVATE, 2147483647) = 1
[pid 1734] ppoll([{fd=15, events=POLLIN}, {fd=14, events=POLLIN}], 2, NULL, NULL, 0 <unfinished ...>
[pid 780] <... futex resumed> ) = 0
[pid 780] futex(0x72078fd410, FUTEX_WAIT_BITSET_PRIVATE, 4294967294, NULL, FUTEX_BITSET_MATCH_ANY <unfinished ...>
[pid 1734] <... ppoll resumed> ) = 1 ([{fd=14, revents=POLLIN}])
[pid 1734] recvfrom(14, "\4\235\5\3\0$\0\1\4\0-\26\235o\2\10\0\5\224\365\237\223`\336\26\3\4\0\0\0\0\0"..., 56, MSG_DONTWAIT, {sa_family=AF_IB, sa_data="\0\0\2\0\0\0\1\0\0\0)\0\0\0\0\0\0\0"}, [20]) = 43
[pid 1734] rt_sigprocmask(0x539a1968 /* SIG_??? */, NULL, [USR2 RTMIN], 8) = 0
[pid 1734] rt_sigprocmask(0x539a1958 /* SIG_??? */, NULL, [USR2 RTMIN], 8) = 0
[pid 1734] futex(0x72078fd410, FUTEX_WAKE_PRIVATE, 2147483647) = 1
[pid 1734] ppoll([{fd=15, events=POLLIN}, {fd=14, events=POLLIN}], 2, NULL, NULL, 0 <unfinished ...>
[pid 780] <... futex resumed> ) = 0
[pid 780] futex(0x72078fd410, FUTEX_WAIT_BITSET_PRIVATE, 4294967294, NULL, FUTEX_BITSET_MATCH_ANY
Now that is more like it. There is a lot going on, so filtering the syscalls would be a good idea. Since we are looking for data transferred between SSC and the sensor service, we can focus on sendto
and recvfrom
calls. This can be done using the -e
argument. The -xx
argument is also added to print all strings in hexadecimal format, and -s 1024
to increase the string size limit so that no characters get truncated:
# strace -p 582 -f -e trace=sendto,recvfrom -xx -s 1024
strace: Process 582 attached with 70 threads
Nothing?
This is being done with the screen off, so the silence might be due to the sensors being suspended to save power. One sensor should be active however; as the phone can be woken up by waving a hand in front of the proximity sensor. To confirm, the proximity sensor is covered then uncovered a few times:
[pid 977] recvfrom(97, "\x04\xad\x00\x05\x00\x1a\x00\x01\x01\x00\x1a\x02\x04\x00\xf2\xfe\xf4\x71\x03\x0c\x00\x00\x00\x01\x00\xa0\x3a\x29\xb2\x00\x00\x00\x00", 820, MSG_DONTWAIT, {sa_family=AF_IB, sa_data="\x00\x00\x02\x00\x00\x00\x09\x00\x00\x00\x3f\x00\x00\x00\x00\x00\x00\x00"}, [20]) = 33
[pid 977] recvfrom(97, "\x04\xae\x00\x05\x00\x1a\x00\x01\x01\x00\x1a\x02\x04\x00\x09\x26\xf5\x71\x03\x0c\x00\x00\x00\x00\x00\xa0\x3a\x29\xb2\x00\x00\x00\x00", 820, MSG_DONTWAIT, {sa_family=AF_IB, sa_data="\x00\x00\x02\x00\x00\x00\x09\x00\x00\x00\x3f\x00\x00\x00\x00\x00\x00\x00"}, [20]) = 33
[pid 582] sendto(14, "\x00\xc0\x01\x02\x00\x04\x00\x10\x01\x00\x01", 11, MSG_DONTWAIT, {sa_family=AF_IB, sa_data="\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x29\x00\x00\x00\xd8\x33\xf4\xc7"}, 20) = 11
[pid 1734] recvfrom(14, "\x02\xc0\x01\x02\x00\x29\x00\x02\x02\x00\x00\x00\x10\x04\x00\xe0\x2d\xf5\x71\x11\x08\x00\xfb\xcb\x93\x10\xab\x61\xde\x16\x13\x04\x00\x00\x00\x00\x00\x14\x08\x00\xa9\x13\x2e\xdc\x0e\x35\x00\x00", 56, MSG_DONTWAIT, {sa_family=AF_IB, sa_data="\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x29\x00\x00\x00\x00\x00\x00\x00"}, [20]) = 48
[pid 582] sendto(94, "\x00\x7e\x00\x02\x00\x26\x00\x01\x01\x00\x28\x02\x01\x00\x00\x03\x04\x00\x00\x00\x05\x00\x04\x0c\x00\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x05\x00\x00\x00\x00\x00\x01", 45, MSG_DONTWAIT, {sa_family=AF_IB, sa_data="\x00\x00\x02\x00\x00\x00\x09\x00\x00\x00\x3f\x00\x00\x00\x58\x34\xf4\xc7"}, 20) = 45
[pid 973] recvfrom(94, "\x04\x3f\x00\x05\x00\x1a\x00\x01\x01\x00\x1f\x02\x04\x00\x09\x26\xf5\x71\x03\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 820, MSG_DONTWAIT, {sa_family=AF_IB, sa_data="\x00\x00\x02\x00\x00\x00\x09\x00\x00\x00\x3f\x00\x00\x00\x00\x00\x00\x00"}, [20]) = 33
[pid 973] recvfrom(94, "\x02\x7e\x00\x02\x00\x09\x00\x02\x02\x00\x00\x00\x10\x01\x00\x1f", 820, MSG_DONTWAIT, {sa_family=AF_IB, sa_data="\x00\x00\x02\x00\x00\x00\x09\x00\x00\x00\x3f\x00\x00\x00\x00\x00\x00\x00"}, [20]) = 16
[pid 1734] recvfrom(14, "\x04\x15\x06\x03\x00\x24\x00\x01\x04\x00\x62\x3a\xf5\x71\x02\x08\x00\xa6\xa0\x66\x16\xab\x61\xde\x16\x03\x04\x00\x00\x00\x00\x00\x10\x08\x00\x0a\xe3\x00\xe2\x0e\x35\x00\x00", 56, MSG_DONTWAIT, {sa_family=AF_IB, sa_data="\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x29\x00\x00\x00\x00\x00\x00\x00"}, [20]) = 43
[pid 982] sendto(94, "\x00\x7f\x00\x00\x00\x00\x00", 7, MSG_DONTWAIT, {sa_family=AF_IB, sa_data="\x00\x00\x02\x00\x00\x00\x09\x00\x00\x00\x3f\x00\x00\x00\xb8\xf0\xa9\x53"}, 20) = 7
[pid 973] recvfrom(94, "\x02\x7f\x00\x00\x00\x05\x00\x02\x02\x00\x00\x00", 820, MSG_DONTWAIT, {sa_family=AF_IB, sa_data="\x00\x00\x02\x00\x00\x00\x09\x00\x00\x00\x3f\x00\x00\x00\x00\x00\x00\x00"}, [20]) = 12
[pid 977] recvfrom(97, "\x04\xaf\x00\x05\x00\x1a\x00\x01\x01\x00\x1a\x02\x04\x00\x69\x8f\xf5\x71\x03\x0c\x00\x00\x00\x01\x00\xa0\x3a\x29\xb2\x00\x00\x00\x00", 820, MSG_DONTWAIT, {sa_family=AF_IB, sa_data="\x00\x00\x02\x00\x00\x00\x09\x00\x00\x00\x3f\x00\x00\x00\x00\x00\x00\x00"}, [20]) = 33
[pid 977] recvfrom(97, "\x04\xb0\x00\x05\x00\x1a\x00\x01\x01\x00\x1a\x02\x04\x00\xce\xa3\xf5\x71\x03\x0c\x00\x00\x00\x00\x00\xa0\x3a\x29\xb2\x00\x00\x00\x00", 820, MSG_DONTWAIT, {sa_family=AF_IB, sa_data="\x00\x00\x02\x00\x00\x00\x09\x00\x00\x00\x3f\x00\x00\x00\x00\x00\x00\x00"}, [20]) = 33
[pid 977] recvfrom(97, "\x04\xb1\x00\x05\x00\x1a\x00\x01\x01\x00\x1a\x02\x04\x00\x94\x10\xf6\x71\x03\x0c\x00\x00\x00\x01\x00\xa0\x3a\x29\xb2\x00\x00\x00\x00", 820, MSG_DONTWAIT, {sa_family=AF_IB, sa_data="\x00\x00\x02\x00\x00\x00\x09\x00\x00\x00\x3f\x00\x00\x00\x00\x00\x00\x00"}, [20]) = 33
[pid 977] recvfrom(97, "\x04\xb2\x00\x05\x00\x1a\x00\x01\x01\x00\x1a\x02\x04\x00\x13\x19\xf6\x71\x03\x0c\x00\x00\x00\x00\x00\xa0\x3a\x29\xb2\x00\x00\x00\x00", 820, MSG_DONTWAIT, {sa_family=AF_IB, sa_data="\x00\x00\x02\x00\x00\x00\x09\x00\x00\x00\x3f\x00\x00\x00\x00\x00\x00\x00"}, [20]) = 33
[pid 977] recvfrom(97, "\x04\xb3\x00\x05\x00\x1a\x00\x01\x01\x00\x1a\x02\x04\x00\xdf\x1f\xf6\x71\x03\x0c\x00\x00\x00\x01\x00\xa0\x3a\x29\xb2\x00\x00\x00\x00", 820, MSG_DONTWAIT, {sa_family=AF_IB, sa_data="\x00\x00\x02\x00\x00\x00\x09\x00\x00\x00\x3f\x00\x00\x00\x00\x00\x00\x00"}, [20]) = 33
[pid 977] recvfrom(97, "\x04\xb4\x00\x05\x00\x1a\x00\x01\x01\x00\x1a\x02\x04\x00\x5f\x28\xf6\x71\x03\x0c\x00\x00\x00\x00\x00\xa0\x3a\x29\xb2\x00\x00\x00\x00", 820, MSG_DONTWAIT, {sa_family=AF_IB, sa_data="\x00\x00\x02\x00\x00\x00\x09\x00\x00\x00\x3f\x00\x00\x00\x00\x00\x00\x00"}, [20]) = 33
And sure enough, data starts coming in. A recvfrom
call is traced each time the proximity sensor is covered or uncovered. There are a few extra sendto
and recvfrom
calls after the first time the sensor is covered, so it seems that something happens when the screen turns on. That will not be important now though.
Reading the data
Right now all we have is a bunch of strace
output lines with traced syscalls and the arguments passed through them. Since we are after raw data, some work has to be done on those lines.
To start analyzing the data format, a single recvfrom
is taken:
[pid 977] recvfrom(97, "\x04\xb1\x00\x05\x00\x1a\x00\x01\x01\x00\x1a\x02\x04\x00\x94\x10\xf6\x71\x03\x0c\x00\x00\x00\x01\x00\xa0\x3a\x29\xb2\x00\x00\x00\x00", 820, MSG_DONTWAIT, {sa_family=AF_IB, sa_data="\x00\x00\x02\x00\x00\x00\x09\x00\x00\x00\x3f\x00\x00\x00\x00\x00\x00\x00"}, [20]) = 33
Then the data, which is the second argument of recvfrom
, is extracted and formatted nicely:
# printf "\x04\xb1\x00\x05\x00\x1a\x00\x01\x01\x00\x1a\x02\x04\x00\x94\x10\xf6\x71\x03\x0c\x00\x00\x00\x01\x00\xa0\x3a\x29\xb2\x00\x00\x00\x00" | xxd -g 1
00000000: 04 b1 00 05 00 1a 00 01 01 00 1a 02 04 00 94 10 ................
00000010: f6 71 03 0c 00 00 00 01 00 a0 3a 29 b2 00 00 00 .q........:)....
00000020: 00 .
These are the bytes received, put in a single line:
04 b1 00 05 00 1a 00 01 01 00 1a 02 04 00 94 10 f6 71 03 0c 00 00 00 01 00 a0 3a 29 b2 00 00 00 00
There is a prominent pattern that can be quickly identified here: There are several non-zero bytes followed by zeroes, such as b1 00
, 05 00
, 1a 00
, and 0c 00 00 00
. This is a strong indicator that this data is little-endian, meaning that integers that are 2 bytes or longer are represented in such a way that their least significant byte comes first, followed by the second least significant byte, and so on.
To interpret the data, it might help to start off with some guesses on the fields it is composed of. At a first glance, it looks like it can be split into these fields, resembled with a C struct:
struct prox_pkt {
u8 var1; // 04
u16 var2; // b1 00
u16 var3; // 05 00
u16 var4; // 1a 00
u8 var5; // 01
u16 var6; // 01 00
u8 var7; // 1a
u8 var8; // 02
u16 var9; // 04 00
u8 var10; // 94
u8 var11; // 10
u8 var12; // f6
u8 var13; // 71
u8 var14; // 03
u32 var15; // 0c 00 00 00
u16 var16; // 01 00
u8 var17; // a0
u8 var18; // 3a
u8 var19; // 29
u8 var20; // b2
u32 var21; // 00 00 00 00
};
struct prox_pkt pkt1 = {0x04, 0xb1, 0x05, 0x1a, 0x01, 0x01, 0x1a, 0x02, 0x04, 0x94, 0x10, 0xf6, 0x71, 0x03, 0x0c, 0x01, 0xa0, 0x3a, 0x29, 0xb2, 0x00};
Again, the little-endianness of the data is clearly seen in every u16
and u32
field, which are 2 bytes and 4 bytes long respectively. The zeroes helped with identifying the sizes of those fields. There might be additional 2 or 4 byte long fields that are holding large integers where their most significant bytes are non-zero, making them difficult to identify at this stage.
Next, the data is analyzed further by comparing 2 consecutive lines. The steps above are repeated with the next line:
[pid 977] recvfrom(97, "\x04\xb2\x00\x05\x00\x1a\x00\x01\x01\x00\x1a\x02\x04\x00\x13\x19\xf6\x71\x03\x0c\x00\x00\x00\x00\x00\xa0\x3a\x29\xb2\x00\x00\x00\x00", 820, MSG_DONTWAIT, {sa_family=AF_IB, sa_data="\x00\x00\x02\x00\x00\x00\x09\x00\x00\x00\x3f\x00\x00\x00\x00\x00\x00\x00"}, [20]) = 33
04 b2 00 05 00 1a 00 01 01 00 1a 02 04 00 13 19 f6 71 03 0c 00 00 00 00 00 a0 3a 29 b2 00 00 00 00
The format seems identical, so it should be possible to compare this with the previous line directly.
struct prox_pkt pkt1 = {0x04, 0xb1, 0x05, 0x1a, 0x01, 0x01, 0x1a, 0x02, 0x04, 0x94, 0x10, 0xf6, 0x71, 0x03, 0x0c, 0x01, 0xa0, 0x3a, 0x29, 0xb2, 0x00};
struct prox_pkt pkt2 = {0x04, 0xb2, 0x05, 0x1a, 0x01, 0x01, 0x1a, 0x02, 0x04, 0x13, 0x19, 0xf6, 0x71, 0x03, 0x0c, 0x00, 0xa0, 0x3a, 0x29, 0xb2, 0x00};
Most of the values remained constant. The only values that changed are var2
which increased by 1, var10
and var11
which changed somewhat significantly, and var16
which changed from 1 to 0.
Looking at a larger set of lines should make it possible to identify patterns and find constant values with more certainty, as well as find some of the hiding large integers mentioned earlier.
Formatting each line from the strace
output manually will take a lot of time. With some quick Python magic though:
import sys
import re
i = open(sys.argv[1], "r")
ilines = i.readlines()
for iline in ilines:
if "ENOMSG" in iline:
continue
if "sendto" not in iline and "recvfrom" not in iline:
continue
line = ""
line += re.findall("\d+", iline)[0] + "\t"
if "sendto" in iline:
line += "send"
elif "recvfrom" in iline:
line += "recv"
line += "\t"
fd = re.findall("\(\d\d", iline)
if len(fd) == 0:
continue
line += fd[0][1::] + "\t"
rawdata = re.findall("[^\"][x][0-9a-f][0-9a-f][^\"]+", iline)
if len(rawdata) == 0:
continue
rawdata = rawdata[0][2::].split("\\x")
data = []
for rawbyte in rawdata:
data.append(int(rawbyte, 16))
line += "|"
for byte in data:
line += chr(byte) if byte <= 127 and byte >= 32 else "."
line += "|\t\t"
for byte in data:
line += "{:02x}".format(byte) + " "
print(line)
i.close()
The strace
output can be quickly converted into a nice and easy to read format, including an ASCII representation that should reveal any text possibly carried in the packets:
977 recv 97 |.................q........:).....| 04 ad 00 05 00 1a 00 01 01 00 1a 02 04 00 f2 fe f4 71 03 0c 00 00 00 01 00 a0 3a 29 b2 00 00 00 00
977 recv 97 |...............&.q........:).....| 04 ae 00 05 00 1a 00 01 01 00 1a 02 04 00 09 26 f5 71 03 0c 00 00 00 00 00 a0 3a 29 b2 00 00 00 00
582 send 14 |...........| 00 c0 01 02 00 04 00 10 01 00 01
1734 recv 14 |.....)..........-.q........a.................5..| 02 c0 01 02 00 29 00 02 02 00 00 00 10 04 00 e0 2d f5 71 11 08 00 fb cb 93 10 ab 61 de 16 13 04 00 00 00 00 00 14 08 00 a9 13 2e dc 0e 35 00 00
582 send 94 |.~...&....(..................................| 00 7e 00 02 00 26 00 01 01 00 28 02 01 00 00 03 04 00 00 00 05 00 04 0c 00 ff ff 00 00 00 00 00 00 00 00 00 00 11 05 00 00 00 00 00 01
973 recv 94 |.?.............&.q...............| 04 3f 00 05 00 1a 00 01 01 00 1f 02 04 00 09 26 f5 71 03 0c 00 00 00 00 00 00 00 00 00 00 00 00 00
973 recv 94 |.~..............| 02 7e 00 02 00 09 00 02 02 00 00 00 10 01 00 1f
1734 recv 14 |.....$....b:.q.....f..a.................5..| 04 15 06 03 00 24 00 01 04 00 62 3a f5 71 02 08 00 a6 a0 66 16 ab 61 de 16 03 04 00 00 00 00 00 10 08 00 0a e3 00 e2 0e 35 00 00
982 send 94 |......| 00 7f 00 00 00 00 00
973 recv 94 |...........| 02 7f 00 00 00 05 00 02 02 00 00 00
977 recv 97 |..............i..q........:).....| 04 af 00 05 00 1a 00 01 01 00 1a 02 04 00 69 8f f5 71 03 0c 00 00 00 01 00 a0 3a 29 b2 00 00 00 00
977 recv 97 |.................q........:).....| 04 b0 00 05 00 1a 00 01 01 00 1a 02 04 00 ce a3 f5 71 03 0c 00 00 00 00 00 a0 3a 29 b2 00 00 00 00
977 recv 97 |.................q........:).....| 04 b1 00 05 00 1a 00 01 01 00 1a 02 04 00 94 10 f6 71 03 0c 00 00 00 01 00 a0 3a 29 b2 00 00 00 00
977 recv 97 |.................q........:).....| 04 b2 00 05 00 1a 00 01 01 00 1a 02 04 00 13 19 f6 71 03 0c 00 00 00 00 00 a0 3a 29 b2 00 00 00 00
977 recv 97 |.................q........:).....| 04 b3 00 05 00 1a 00 01 01 00 1a 02 04 00 df 1f f6 71 03 0c 00 00 00 01 00 a0 3a 29 b2 00 00 00 00
977 recv 97 |.............._(.q........:).....| 04 b4 00 05 00 1a 00 01 01 00 1a 02 04 00 5f 28 f6 71 03 0c 00 00 00 00 00 a0 3a 29 b2 00 00 00 00
Looking at the last six packets, some interesting patterns are found:
- The second byte in each packet (
var2
) is incremented by 1 on each packet. It might be some sort of a counter. - Bytes 14, 15 and 16 seem to be part of a single variable that is always increasing. Along with byte 17, they likely make up an unsigned 32-bit timestamp, as knowing the time when a sensor reading was taken is needed for calculating relative changes among other things, and there are no other bytes that might represent time here.
- The 24th Byte alternates between 1 and 0. This makes it almost certainly a boolean carrying the proximity sensor data, as it exactly matches the covering/uncovering of the sensor.
Another pattern can be found by taking a look at all the packets including the ones in the first sendto
/recvfrom
syscalls:
- The first byte is always 0 when a packet is sent, and either 2 or 4 when it is received. This might be a header with some specific meaning for each of those three values.
The other values remain constant, so it will not be possible to understand their purpose without some external reference or context that would give them meaning.
With the new findings, an updated struct prox_pkt
would look like this:
struct prox_pkt {
u8 header;
u16 counter;
u16 var3;
u16 var4;
u8 var5;
u16 var6;
u8 var7;
u8 var8;
u16 var9;
u32 timestamp;
u8 var11;
u32 var12;
bool sensor_val;
u8 var14;
u8 var15;
u8 var16;
u8 var17;
u8 var18;
u32 var19;
};
Some important fields have been identified so far, but the purpose of most still remains unknown.
The next step would be to identify the format of this packet, and for that, there are a couple of clues. First of all, the packets traced are moving to and from a Hexagon DSP on a Qualcomm SoC. These tend to use a certain protocol, whose packets – or messages – all have a unique header section. One that has, like in the beginning of our struct prox_pkt
, a u8
followed by three u16
s…
Introducing QMI
The Qualcomm MSM Interface is a protocol originally developed for interfacing with baseband processors on the MSM line of SoCs, that eventually ended up as the primary interface between the application processor and remote processors on Qualcomm SoCs such as MPSS, ADSP, and now SLPI.
QMI messages
Processors on the SoC communicate by sending messages over QMI, which are transferred through shared memory. As mentioned earlier, QMI messages have a common header:
/**
* struct qmi_header - wireformat header of QMI messages
* @type: type of message
* @txn_id: transaction id
* @msg_id: message id
* @msg_len: length of message payload following header
*/
struct qmi_header {
u8 type;
u16 txn_id;
u16 msg_id;
u16 msg_len;
} __packed;
To understand what these fields mean, it would help to understand a couple of things about QMI first:
- QMI messages are classified into three types:
- Request, which is sent from a processor to another, which is expected to send back a message of the second type:
- Response, Sent after receiving a request.
- Indication, which is sent without expecting a response.
- A pair of request and response or a single indication is called a transmission.
Going back to the QMI header:
-
type
can be one of three possible values, which match the three QMI message types:#define QMI_REQUEST 0 #define QMI_RESPONSE 2 #define QMI_INDICATION 4
-
txn_id
is a unique identifier for a transmission. It is generally incremented after each transmission, causing the counting pattern observed in the proximity sensor messages. This identifier allows for pairing requests with their responses; as matching pairs have the same identifier. -
msg_id
is used to identify a specific type of message. Messages of a certain type have the same format and purpose. This property will be explored later. -
msg_len
is the length of the message in bytes, header excluded.
Adjusting for the QMI header, struct prox_pkt
will look like this:
struct prox_pkt {
struct qmi_header;
u8 var5
u16 var6;
u8 var7;
u8 var8;
u16 var9;
u32 timestamp;
u8 var11;
u32 var12;
bool sensor_val;
u8 var14;
u8 var15;
u8 var16;
u8 var17;
u8 var18;
u32 var19;
};
Reading a QMI header
Taking the first proximity sensor message from before:
04 b2 00 05 00 1a 00 01 01 00 1a 02 04 00 13 19 f6 71 03 0c 00 00 00 00 00 a0 3a 29 b2 00 00 00 00
The header, which is the first seven bytes, can be extracted:
04 b2 00 05 00 1a 00
To read the header, the bytes are put in a struct qmi_header
. The first byte is u8 type
, and the following three pairs of bytes make up u16 txn_id, msg_id, msg_len
:
struct qmi_header prox_msg_hdr {
type = QMI_INDICATION,
txn_id = 0xb2,
msg_id = 0x05,
msg_len = 0x1a,
};
This message is SSC indicating the state of the proximity sensor, so its type
being QMI_INDICATION
makes sense as it would not need a response. txn_id
does not carry any significant meaning; as it is only used to track the message as it moves around, and since this message is an indication, there is no request to pair it to. msg_id
specifies the type of this message and identifies its format so that it can be decoded properly. This property will become useful at a later stage. Finally, msg_len
tells us that the following message should be 0x1a
, or 26 bytes long.
QMI encoding scheme
QMI messages are encoded in a Type–Length–Value (TLV for short) scheme, where data fields, which are usually called elements in QMI, are put in blocks, each having a type value that is used to identify the field, a length value specifying the length of the data carried in the field, and a value which holds the actual data.
Decoding QMI messages
Since QMI messages are TLV-encoded, many of the unknown variables in the struct prox_pkt
guessed earlier are in fact types and lengths rather than actual values. Now that the encoding scheme is known, it should be possible to decode the data captured in the previous stage. The message elements can be discovered by going through the data byte by byte and splitting them into TLV blocks.
Looking at the QMI encoding/decoding helper in Linux, it can be understood how the TLV blocks are encoded. The first thing in the file is a macro used to encode a TLV block:
#define QMI_ENCDEC_ENCODE_TLV(type, length, p_dst) do { \
*p_dst++ = type; \
*p_dst++ = ((u8)((length) & 0xFF)); \
*p_dst++ = ((u8)(((length) >> 8) & 0xFF)); \
} while (0)
This writes one byte for type
into a buffer, followed by two bytes for length
. These sizes are further confirmed after looking a bit deeper into the file:
#define TLV_LEN_SIZE sizeof(u16)
#define TLV_TYPE_SIZE sizeof(u8)
A message is now taken from before
04 b1 00 05 00 1a 00 01 01 00 1a 02 04 00 94 10 f6 71 03 0c 00 00 00 01 00 a0 3a 29 b2 00 00 00 00
and is split into TLV blocks by taking one byte for type
, two bytes for length
, then length
bytes for the actual value. This is repeated until all blocks are found:
04 b1 00 05 00 1a 00
01 01 00 1a
02 04 00 94 10 f6 71
03 0c 00 00 00 01 00 a0 3a 29 b2 00 00 00 00
The first line is the header which was discussed earlier. The following lines are the TLV blocks of elements carried in the message, with the first byte being the type, the next 2 bytes the length, and the remaining bytes the value. A closer look is taken at each element:
- The first element is a single byte, which makes it likely to be a
u8
. Its value is0x1a
, which remains constant across all messages traced before. Being the first element in the message, it might be some sort of sensor identifier. This can be confirmed later by capturing messages carrying data from other sensors, then comparing this value to the one in those messages. - The second element is 4 bytes long. This is the timestamp from before, which is now confirmed to be a
u32
. - The third element is 12 bytes long. It might be a
u8
array with a size of 12, but it could also be au16
array with a size of 6, or au32
array with a size of 3. Part of this element’s value is the proximity sensor valuesensor_val
that was discovered earlier. Considering that this message carries sensor data, and many sensors have 3 axes, the latter possibility is more likely even if the proximity sensor only outputs a boolean; as the message format might be the same for several, if not all sensor types.
Now to put these elements in a new struct:
struct prox_ind {
u8 sensor_id;
u32 timestamp;
u32 data[3];
};
That looks much better than the previous struct prox_pkt
.
Now to create a struct prox_ind
with the data from the message above:
struct prox_ind ind1 = {
.sensor_id = 0x1a,
.timestamp = 1911951508,
.data = {
1,
2989046432,
0
}
};
The consecutive message can also be decoded for comparison:
04 b2 00 05 00 1a 00 01 01 00 1a 02 04 00 13 19 f6 71 03 0c 00 00 00 00 00 a0 3a 29 b2 00 00 00 00
04 b2 00 05 00 1a 00
01 01 00 1a
02 04 00 13 19 f6 71
03 0c 00 00 00 00 00 a0 3a 29 b2 00 00 00 00
struct prox_ind ind2 = {
.sensor_id = 0x1a,
.timestamp = 1911953683,
.data = {
0,
2989046432,
0
}
};
The timestamp increased in the second message, and data[0]
changed to 0 indicating that the proximity sensor was uncovered. There is a strange large constant value in data[1]
though. It might be good to try to interpret that at some later stage.
Next steps
After developing a method to capture and decode messages transferred between the android sensor service and the SSC, more information can be collected on their operation by analyzing messages at different times, such as with the screen on or with an app using data from specific sensors. However, all of these discoveries would be useless without figuring out how the sensor service sends and receives these messages, and how to do the same thing on mainline Linux. This, along with a second method to capture messages, will be explored next time.