Fingerprint sensors on tuxified Android phones: Impossible?

Part 1: Finding a way

Yassine Oudjana

Posted on 12th Dec 2021

Fast authentication is one aspect where Linux phones are still lacking. As of now, the only common way to unlock a phone running Linux is to use a PIN or password. While that may have security benefits to some, it is inconvenient for many, who would rather use a faster method to unlock their phones such as a fingerprint sensor. Possibly the only phone running Linux that can be unlocked with a fingerprint at the moment is a PinePhone fitted with a prototype fingerprint sensor case. Linux phones such as the PinePhone (by default at least) and the Librem 5 might not have fingerprint sensors, but what about Android phones running Linux? Nowadays even the cheapest Android phones have some sort of fingerprint sensor, so why don’t we simply run Linux on those to get fingerprint authentication on a Linux phone? Making an attempt at enabling the fingerprint sensor of an actual Android device on the mainline Linux kernel should provide us with some answers.

The Xiaomi Mi Note 2 will be used as a subject in this experiment. It has a fingerprint sensor built into its home button, and it can already run the mainline Linux kernel to a great extent, so enabling the fingerprint sensor would not take long, or would it?

Enabling the fingerprint sensor

Collecting information

As usual, the first place to look for hardware information is the vendor device tree found in the downstream kernel source. This node seems interesting:

	fpc_fpc1020 {
		compatible = "fpc,fpc1020";
		fpc,irq-gpio	= <&tlmm 121 0x2001>;
		fpc,fp-id-gpio = <&pm8994_gpios 11 0x00>;
		pinctrl-names = "pmx_fp_active", "pmx_fp_suspend";
		pinctrl-0 = <&fp_active>;
		pinctrl-1 = <&fp_suspend>;
		vcc_spi-supply	= <&pm8994_s4>;
		vdd_ana-supply	= <&pm8994_s4>;
		vdd_io-supply	= <&pm8994_s4>;
	};

The official product page from Fingerprint Cards is the first match when searching for fpc1020:

The FPC1020 is a new compact CMOS fingerprint sensor with several significant advantages. It delivers best-in-class image quality with 256 gray-scale values in every single programmable pixel. The sensor with its 3D pixel-sensing technology can read virtually any finger – dry or wet. Thanks to the extremely hard and durable surface coating capable of withstanding more than 10 million finger placements, the FPC1020 is protected against ESD well above 30 kV as well as against scratches and everyday wear and tear.

Looks well enough like a fingerprint sensor.

This line gives us another hint:

		vcc_spi-supply	= <&pm8994_s4>;

It makes clear that this sensor communicates over a Serial Peripheral Interface, or SPI for short. Strangely however, this node is a child of the root node, when it is supposed to be a subnode of the SPI master node the sensor is wired up to. In fact, there is no indication to be found in this device tree of which SPI master this sensor is connected to. This leaves us with the other main source of hardware information: Schematics.

Looking at the SoC GPIO page, the connection between fingerprint sensor and SoC is quickly identified by the FP prefix on the pin names:

The MSM8996 pin controller driver then makes it clear that SPI 5 is exposed on these pins:

static const char * const blsp_spi5_groups[] = {
	"gpio81", "gpio82", "gpio83", "gpio84",
};

Enabling the SPI master

If this works, it should become possible to communicate with the fingerprint sensor.

The SoC DTS is lacking a node for SPI 5, so it is the first thing that should be added. Pin state nodes are added to configure the pin controller, then a node for the SPI master is added:

--- a/arch/arm64/boot/dts/qcom/msm8996.dtsi
+++ b/arch/arm64/boot/dts/qcom/msm8996.dtsi
@@ -1507,6 +1507,30 @@ cdc_reset_sleep: cdc-reset-sleep {
 				output-low;
 			};
 
+			blsp1_spi5_default: blsp1-spi5-default {
+				spi {
+					pins = "gpio81", "gpio82", "gpio84";
+					function = "blsp_spi5";
+					drive-strength = <12>;
+					bias-disable;
+				};
+
+				cs {
+					pins = "gpio83";
+					function = "gpio";
+					drive-strength = <16>;
+					bias-disable;
+					output-high;
+				};
+			};
+
+			blsp1_spi5_sleep: blsp1-spi5-sleep {
+				pins = "gpio81", "gpio82", "gpio83", "gpio84";
+				function = "gpio";
+				drive-strength = <2>;
+				bias-pull-down;
+			};
+
 			blsp2_spi6_default: blsp2-spi5-default {
 				spi {
 					pins = "gpio85", "gpio86", "gpio88";
@@ -3093,6 +3117,23 @@ blsp1_i2c5: i2c@757a000 {
 			status = "disabled";
 		};
 
+		blsp1_spi5: spi@7579000 {
+			compatible = "qcom,spi-qup-v2.2.1";
+			reg = <0x07579000 0x600>;
+			interrupts = <GIC_SPI 99 IRQ_TYPE_LEVEL_HIGH>;
+			clocks = <&gcc GCC_BLSP1_QUP5_SPI_APPS_CLK>,
+				 <&gcc GCC_BLSP1_AHB_CLK>;
+			clock-names = "core", "iface";
+			pinctrl-names = "default", "sleep";
+			pinctrl-0 = <&blsp1_spi5_default>;
+			pinctrl-1 = <&blsp1_spi5_sleep>;
+			dmas = <&blsp1_dma 20>, <&blsp1_dma 21>;
+			dma-names = "tx", "rx";
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "disabled";
+		};
+
 		blsp2_dma: dma@7584000 {
 			compatible = "qcom,bam-v1.7.0";
 			reg = <0x07584000 0x2b000>;
--

Now to enable it in the device DTS:

&blsp1_spi5 {
	status = "okay";
};

Enabling this SPI master ends up freezing the device as soon as the its driver probes and tries to initialize it. But why?

Perhaps taking a closer look at how fingerprint authentication is handled in Android would provide some answers as to why this happened.

Fingerprint sensors in Android

Fingerprint sensors are generally managed in Android’s Hardware Abstraction Layer, or HAL, which mostly consists of proprietary software provided by the vendor. The HAL has some sort of daemon which communicates with the Trusted Execution Environment (TEE for short), where all the real magic happens. The daemon loads a trustlet—a small signed program into the TEE where it is executed. This trustlet includes the actual driver for the fingerprint sensor, and is what scans and matches fingerprints. The daemon would request authentication from the trustlet, which would then scan and match a fingerprint in the secure world, and send a message back to the normal world indicating whether authentication was successful or not. So the real work happens in the secure world, and the normal world only gets to know whether to grant or deny access.

TEE? Secure world? Normal world? What?

To put simply, ARM CPUs have a hardware mechanism known as TrustZone that allows for running two operating systems in parallel, one being the normal OS (Linux in this case) running in the normal world, as well as a trusted OS running in the secure world and providing the system with a trusted execution environment. The normal world’s bus and memory access can be restricted so that select busses and memory ranges become only accessible in the secure world. This is the case with the SPI master that we tried to access from Linux earlier, the access of which was restricted to the secure world. Trying to access it from the normal world caused an exception which halted the execution of Linux. The trusted OS might have printed some error messages over debug UART when that happened, but it is unfortunately inaccessible on this production device, at least without some delicate hardware modifications, so there is no way to confirm that at the moment.

What can be done?

It would seem that the only option to make use of the fingerprint sensor on Linux would be to do it the Android way. Similar to other TEE implementations, the Qualcomm Secure Execution Environment – or QSEE – has a mechanism to handle communications with the normal OS called QSEECOM, short for QSEE COMmunicator, which the mainline Linux kernel is lacking a driver for. Writing a driver in the mainline kernel for QSEECOM should be possible thanks to the availability of downstream driver source code, but another driver, possibly in userspace, will be needed to replace the Android HAL daemon and handle loading and communicating with the proprietary fingerprint sensor trustlet provided by the vendor. Unfortunately, it would be quite difficult to make this second driver due to the lack of open source code or documentation.

The end?

Could this really be a dead end? Or is there still something that can be done? Backtracking a bit might help us find a way.

The SPI master inside the SoC connects to the fingerprint sensor through a set of GPIO pins. These pins are also usually secured on Android phones, making it a necessary step when porting the mainline kernel to a phone with a fingerprint sensor to mark them in the device tree as reserved GPIOs, so that the pin controller driver does not try to initialize them or modify their states and end up causing something similar to what happened after enabling the secured SPI master. Somehow, the Mi Note 2 can run the mainline kernel just fine without reserving any pins. Could this actually mean that the pins the fingerprint sensor is wired to are not secured on this device?

Skipping the hardware SPI master

If the only thing that is secured is the SPI master, then it should be possible to simply skip it all together and use the GPIO pins directly using bit banging to form a software-based SPI master. Software SPI is much slower than its hardware counterpart, but it should still be more than enough for a fingerprint sensor.

Linux has a spi-gpio driver that can do this, and all that is needed to use it is to add a node to the device tree, specifying the GPIO pins to use for SPI. In this case, the four GPIO pins originally used by SPI 5 are used:

	spi {
		compatible = "spi-gpio";

		mosi-gpios = <&tlmm 81 0>;
		miso-gpios = <&tlmm 82 0>;
		cs-gpios = <&tlmm 83 GPIO_ACTIVE_LOW>;
		sck-gpios = <&tlmm 84 0>;
		num-chipselects = <1>;
	};

Booting with this added to the device tree caused no issues. This does not mean it will necessarily work, but is a good sign nevertheless. Now we can proceed to write a driver for the fingerprint sensor.

Writing a driver

$ ls linux/drivers
accessibility  char         dio       greybus     interconnect  memory    nvdimm    pnp         rtc        target       virtio
acpi           clk          dma       hid         iommu         memstick  nvme      power       s390       tc           visorbus
amba           clocksource  dma-buf   hsi         ipack         message   nvmem     powercap    sbus       tee          vlynq
android        comedi       edac      hv          irqchip       mfd       of        pps         scsi       thermal      vme
ata            connector    eisa      hwmon       isdn          misc      opp       ps3         sh         thunderbolt  w1
atm            counter      extcon    hwspinlock  Kconfig       mmc       parisc    ptp         siox       tty          watchdog
auxdisplay     cpufreq      firewire  hwtracing   leds          most      parport   pwm         slimbus    uio          xen
base           cpuidle      firmware  i2c         macintosh     mtd       pci       rapidio     soc        usb          zorro
bcma           crypto       fpga      i3c         mailbox       mux       pcmcia    ras         soundwire  vdpa
block          cxl          fsi       idle        Makefile      net       perf      regulator   spi        vfio
bluetooth      dax          gnss      iio         mcb           nfc       phy       remoteproc  spmi       vhost
bus            dca          gpio      infiniband  md            ntb       pinctrl   reset       ssb        video
cdrom          devfreq      gpu       input       media         nubus     platform  rpmsg       staging    virt

Wait, where do fingerprint sensor drivers go?

It appears that the Linux kernel does not have a fingeprint subsystem, and the only way to use a fingeprint sensor at the moment is through userspace drivers, most of which are part of libfprint. While userspace drivers might be good enough for polled USB sensors, they would not be enough for this device where handling interrupts and regulators is necessary, something that is not easily done in userspace. Libfprint has a few drivers for SPI sensors, but they are all made for ACPI platforms where the driver does not have to worry about regulators and can simply rely on ACPI to do the work. The current aim is to find out if it is possible at all to communicate with the sensor from Linux, so for now, a small SPI class driver placed anywhere in the kernel should be enough.

Collecting information

As always, the main sources of information about the hardware are datasheets in first place, followed by downstream driver source code. A quick search for FPC1020 reveals source code of a downstream driver. Not only that, but this repository happens to also include a datasheet for FPC1140 which is part of the FPC1020 series. Perfect.

The datasheet includes a register map containing information about all the available registers on the sensor. This register looks like a good starting point: If the sensor is working, reading this register would succeed, and the read value should match the format shown in the datasheet.

Writing the initial driver

Being a temporary driver, diving into the details at this point is unnecessary. Initialization steps and other parts such as the fpc1020_access_reg function used to handle SPI transfers needed for accessing registers are not covered here. The final revision of this temporary driver can be found here.

Now, to read the hwID register:

	error = fpc1020_access_reg(chip, FPC102X_REG_HWID, FPC102X_REG_HWID_SIZE,
				&hw_id, false);
	if (error) {
		dev_err(chip->dev, "Failed to read HW ID: %d\n", error);
		return error;
	}
	dev_info(chip->dev, "Chip type: FPC1%x%X\n", hw_id >> 4, hw_id & 0xf);

Here comes the moment of truth:

[   50.284353] fpc1020 spi0.0: Chip type: FPC1140C

No crashes, no freezes and no errors so far. Looks like it worked, and now we know that this phone has a FPC1140 revision C.

In fact, after adding some extra code, a fingerprint can be captured and printed as kernel messages, a tiny section of which can be seen in the cover image of this post.

So these four GPIO pins are definitely not secured. But why? Was it a workaround for a last-minute bug that the engineers at Xiaomi did not have the time to fix properly? Did someone simply forget to secure them? Perhaps we will never know. This also seems to be the case on the Mi 5, another Xiaomi device with the MSM8996 SoC which was able to boot the mainline kernel without reserving any pins. Following this pattern, this might also be the case in the remaining three devices of the Xiaomi MSM8996 platform: Mi 5s, Mi Mix and Mi 5s Plus.

The existence of a downstream driver for this fingeprint sensor is also interesting, as it could mean that there are Android phones where the fingerprint sensor is driven by Linux in the normal world by default.

What comes next

Now that a way to communicate with the sensor is found, it is time to write a proper driver, and eventually be able to authenticate using a fingerprint. As mentioned earlier, Linux has no fingerprint subsystem, and this sensor will require handling regulators and interrupts that must be done in kernel space. This leaves us with two options: either to write a small kernel driver only to handle regulators and somehow also deal with interrupts, then do the rest in userspace, or to make a fingerprint subsystem, which would provide a base to support this and other fingerprint sensors in the kernel. This choice will, however, have to wait for the next part of this series.