SM7150 Mainlining

From postmarketOS
🚧 This page is a work-in-progress. Some information contained within may be inaccurate or incomplete.

The Qualcomm SM7150 series of chips are premium mid-range SoCs released in 2019 and 2020, with mainline support originally added to the Xiaomi POCO X3 NFC

Status

An overview of this SoCs features can be seen on its page here.

Getting started

Requirements

  • SM7150-based device
  • Extracted device tree from (preferably) stock Android or TWRP
  • pmbootstrap (from git)
  • Basic knowledge about Linux, Git, C, Device Trees, ...
  • Willingness to learn about "mainlining" and to figure out things on your own

Extracting downstream device tree blob

The device tree blob (dtb) will help you find out which hardware your device has, how the regulators have been setup, where the memory regions are and much more. It is not possible to copy the downstream dtb and use it in mainline, therefore you have to write your own device tree source (dts) for the mainline kernel.

In order to extract the dtb, you will have to boot up the downstream kernel. Preferably you would extract it from the stock Android ROM that came with your phone so it would include the device tree blob overlays (dtbo). Using something like TWRP will also work in case you can't boot your stock ROM anymore.

To extract it, run dd if=/sys/firmware/fdt of=/sdcard/device.dtb with root from an ADB shell and pull the file to your computer with adb pull /sdcard/device.dtb.

Since this file is a compiled blob of the device tree, you will have to decompile it into a human-readable format next. You can do that with the device tree compiler (dtc). Simply install it from your package manager and run dtc -I dtb -O dts -o device.dts device.dtb

Now you can read the full source code of the device tree from your device. Keep this file saved somewhere, as you will need it in your mainlining adventure.

Device Package

Before you can build the mainline kernel for your device, you need to setup the postmarketOS device package. Avoid creating the device/kernel package directly through pmbootstrap, because that will result in a package for a downstream kernel. You can start with the following examples from device-xiaomi-surya:

  • device/testing/device-<vendor>-<codename>/APKBUILD: [1]
  • device/testing/device-<vendor>-<codename>/deviceinfo: [2]

To find the correct values for deviceinfo use pmbootstrap bootimg_analyze boot.img on a boot.img file from the firmware of your device. You can most likely copy the values from the example as they are pretty much the same for most devices.

After modifying the deviceinfo file, run pmbootstrap checksum device-<vendor>-<codename> to generate the checksums.

Build Kernel

To build the kernel, use envkernel.sh, a script for pmbootstrap that will make compiling and packaging the kernel easier. You can only find this script in the git version of pmbootstrap, so follow the instructions from here if you use a pre-packaged version.

$ cd path/to/sm7150-mainline/linux
$ source path/to/pmbootstrap/helpers/envkernel.sh

# Initialize kernel configuration
$ make defconfig sm7150.config

# Build speed will vary from computer to computer
$ make -j$(nproc)

# Create postmarketOS package with your built kernel
$ pmbootstrap build --envkernel linux-postmarketos-qcom-sm7150

This is how you can build and test local changes for the kernel from now on. Now we will continue setting up an initial device tree for your device.

Later on, if you want to change the config, for example to build a new display driver as a module, you can run make menuconfig.

Initial device tree

Your initial device tree does not need much for your device to boot. A minimal device tree needs:

  • qcom,board-id property for your bootloader to find the dtb
  • Memory regions
  • Some way of output (SimpleFB, ramoops or UART)
  • Protected GPIO pins (Fingerprint and NFC pins, should be the same for most devices)

Make sure the properties with //FIXME are correct for your device. You can find the corresponding values in the device tree you extracted, but beware that decimal numbers have been converted into hexadecimal numbers.

The root node of your device tree should look like this:

// SPDX-License-Identifier: GPL-2.0-only

/dts-v1/;

#include "sm7150.dtsi"

/ {
 	model = "Device Name"; //FIXME
 	compatible = "<vendor>,<codename>", "qcom,sm7150"; //FIXME
	chassis-type = "handset"; //FIXME

	qcom,msm-id = <0x16d 0>;
	qcom,board-id = <34 0>; //FIXME
}

Save this file as sm7150-<vendor>-<codename>.dts inside arch/arm64/boot/dts/qcom and add your device tree to the makefile at arch/arm64/boot/dts/qcom/Makefile.

Reserved memory regions

Reserved memory regions are important to make the hypervisor happy (not crash the phone) and to have SimpleFB working. This part is tricky, since you have to compare every memory region in the extracted device tree with arch/arm64/boot/dts/qcom/sm7150.dtsi in the mainline kernel.

Look for the reserved-memory node in the extracted device tree and sm7150.dtsi in the mainline kernel and compare every address and size inside the reg property with each other. In case you find differences, delete the offending node and add this piece of code below the header includes in your device tree:

/*
 * Delete following upstream (sm7150.dtsi) reserved
 * memory mappings which are different for this device.
 */
/delete-node/ &ipa_fw_mem; //FIXME
/delete-node/ &ipa_gsi_mem; //FIXME
/delete-node/ &gpu_mem; //FIXME

And set the new reserved memory region inside the root node:

/ {
	/* ... */

	reserved-memory {
		#address-cells = <2>;
		#size-cells = <2>;
		ranges;

		ipa_fw_mem: memory@98f80000 { //FIXME
			reg = <0x0 0x98f80000 0x0 0x10000>; //FIXME
			no-map;
		};

		ipa_gsi_mem: memory@98f90000 { //FIXME
			reg = <0x0 0x98f90000 0x0 0x5000>; //FIXME
			no-map;
		};

		gpu_mem: memory@98f95000 { //FIXME
			reg = <0x00 0x98f95000 0x00 0x2000>; //FIXME
			no-map;
		};
	};
};

Keep the reserved memory tidy by sorting them after the length of the address

A full example can be seen here. Don't copy-paste this, as your reserved memory regions are probably different.

Protected GPIO pins

The fingerprint sensor and NFC are hooked up to secured GPIO pins, which are being observed and controlled by the TrustZone. This is to allow secure authentication and wireless payments.

Usually, the protected pins are not defined in the device tree (except for Pixel 4a), but this does not matter because most devices share the same protected GPIO pins. If you have schematics for your or a similar device you can verify the protected pins and if you don't, you can just try these out:

  • <59 4> (from Google Pixel 4a)
  • <0 4>, <59 4> (from Xiaomi Redmi Note 10 Pro)

Add this to the bottom your mainline device tree:

&tlmm {
	gpio-reserved-ranges = <59 4>; //FIXME
};

59 is the starting GPIO of the range and 4 is the range. This means that pins 59, 60, 61, 62 are not being touched by Linux.

Getting output

You can chose either of these methods to get output from the kernel. Using SimpleFB is recommended, as it is the easiest to set up.

SimpleFB

You can use simple framebuffer to reuse the frambuffer which the bootloader politely left set up. The bootloader does this to display the splash continuously throughout the boot process.

Look for a memory region called cont_splash_region in your downstream device tree. It should look like this:

cont_splash_region@9c000000 {    
    reg = <0x00 0x9c000000 0x00 0x1700000>;
    label = "cont_splash_region"; 
    phandle = <0x4d0>;
};

Here we can see that 0x9c000000 is the address for the framebuffer.

Now add the node for SimpleFB with the right address and resolution for your device in the mainline device tree:

/ {
	/* ... */
	chosen {
		#address-cells = <2>;
		#size-cells = <2>;
		ranges;
		framebuffer@9c000000 { //FIXME
			compatible = "simple-framebuffer";
			reg = <0x0 0x9c000000 0x0 (1080 * 2400 * 4)>; //FIXME
			width = <1080>; //FIXME
			height = <2400>; //FIXME
			stride = <(1080 * 4)>; //FIXME
			format = "a8r8g8b8";
			clocks = <&gcc GCC_DISP_HF_AXI_CLK>,
				 <&gcc GCC_DISP_SF_AXI_CLK>;
		};
	};
};

And add a reserved memory region for your address. You don't have to delete any node beforehand because the framebuffer is not specified in sm7150.dtsi.

/ {
	/* ... */

	reserved-memory {
		#address-cells = <2>;
		#size-cells = <2>;
		ranges;

		framebuffer_region@9c000000 { //FIXME
			reg = <0x0 0x9c000000 0x0 (1080 * 2400 * 4)>; //FIXME
			no-map;
		};
	};
};

Ramoops

Ramoops saves the logs into RAM using the persistant storage (pstore) backend. Make sure your recovery (e.g. TWRP) has the necessary kernel configs enabled, because otherwise you won't be able to see the kernel log. You can check your current kernel config with zcat /proc/config.gz | grep CONFIG_PSTORE.*=y from ADB shell, it should have these configs enabled:

CONFIG_PSTORE=y
CONFIG_PSTORE_CONSOLE=y
CONFIG_PSTORE_PMSG=y
CONFIG_PSTORE_RAM=y

If your recovery does not have these configs enabled, you will need to build your own recovery with them enabled.

Search for the ramoops node in the downstream device tree. It will tell you the address and the different sizes that are needed. You can copy-paste this node under the reserved-memory node in mainline after deleting the phandle property.

The mainline kernel has the necessary configs already enabled, so after booting mainline and rebooting into recovery by forcefully holding down the key combination you should have the kernel log saved in /sys/fs/pstore. You can output the log with cat /sys/fs/pstore/dmesg-ramoops-0 from ADB shell.

The ramoops method can be unreliable, since the logs can disappear if your RAM looses power during the rebooting process.

UART

Icon TODO: Explain how to get logs from UART

Remote Filesystem memory

Remote Filesystem, also known as RMTFS, is used for allocating and exposing regions of shared memory with remote processors. This memory region is needed by the IPA remote processor to get the modem working.

Even though the device tree defines this region as dynamically allocated, it always has the same address. You can find out the address and size by checking the output of the commands below in Android or TWRP. This example has been taken from Xiaomi Mi 9T.

davinci:/ # cat /sys/class/uio/uio0/maps/map0/addr
0x00000000f2e01000

davinci:/ # cat /sys/class/uio/uio0/maps/map0/size
0x0000000000200000

This tells us that the region begins at 0xf2e01000 with the size of 0x200000. The node in the mainline device tree should look like this based on the output above:

/ {
	/* ... */

	reserved-memory {
		#address-cells = <2>;
		#size-cells = <2>;
		ranges;

		/*
		 * The rmtfs memory region in downstream is 'dynamically allocated'
		 * but given the same address every time. Hard code it as this address is
		 * where the modem firmware expects it to be.
		 */
		rmtfs_mem: memory@f2e01000 {
			compatible = "qcom,rmtfs-mem";
			reg = <0 0xf2e01000 0 0x200000>;
			no-map;

			qcom,client-id = <1>;
			qcom,vmid = <15>;
		};
	};
};

Building

Now that you have your initial device tree, build the kernel again. It shouldn't take as long as before. If you get errors regarding your device tree, go to the corresponding line and correct it.

Booting

Because new Android versions use dtbos, which the stock bootloader will merge with the mainline dtb and therefore corrupt it, you will have to erase it first. It is recommended to take a backup of the dtbo partition in case you want to boot Android again.

Boot into recovery mode and take a backup of the dtbo partition with dd if=/dev/block/by-name/dtbo of=/sdcard/dtbo.img. Afterwards pull the file to your computer to a safe place with adb pull /sdcard/dtbo.img. If your device uses A/B partitioning scheme you can switch to an inactive slot (fastboot set_active a/b) and erase dtbo there without taking a backup.

Unless your device is from Samsung, you should be able to just boot the kernel without flashing it: pmbootstrap flasher boot.

If this does not work or you have a device that does not use fastboot, flash the kernel with pmbootstrap flasher flash_kernel (If you want to boot Android again, take a back up of the boot partition).

Assuming everything went well, you should see logs on the screen or in ramoops! The next steps are to get regulators, USB and UFS working so you can get a shell on your device through telnet/ssh.

Icon TODO: Explain how to boot with UEFI

Contributing

Once you feel like your device tree is ready, you can submit a pull request to sm7150-mainline/linux on GitHub. Make sure your commit titles are named correctly to clearly understand what has been added/changed. You can look at previous commits as a reference.

Have questions?

Join us over at #sm7150-mainline:matrix.org on Matrix. We're happy to help :)