User:Flamingradian
This user's main device is a Google Pixel 3a (google-sargo). |
This user's gitlab.com GitLab profile is @flamingradian. |
This user's pmOS GitLab profile is @rdacayan. |
This user is @flamingradian on sineware.ca. |
This user is @flamingradian on matrix.org. |
This user has ported postmarketOS to 1 device. |
This user mainlined 1 devices. |
The tools that I have for debugging are a USB cable, a screwdriver, and a chair.
Owns Devices
Device | Notes |
---|---|
Google Pixel 3a (google-sargo) | PVT 1.0, daily driver + mainlining target |
Various information I found for mainlining
SMMU
There are 7 valid IOMMU stream match registers on the Pixel 3a provided by the bootloader:
<&apps_smmu 0x140 0xf> // for eMMC <&apps_smmu 0xa0 0xf> // unknown, might be for SD card <&apps_smmu 0xc0 0xf> // unknown <&apps_smmu 0x100 0xf> // unknown, might be for UFS HC <&apps_smmu 0x740 0x0> // for USB <&apps_smmu 0x880 0x8> // for MDSS <&apps_smmu 0xc80 0x8> // for MDSS
The vendor kernel preserves the bootloader's settings on these streams and doesn't specify what all of them are used for. If you're experiencing insane behaviour trying to add a device node (reboot not initiated by the kernel, bring down of all peripherals, etc.), then you might have some luck trying these.
Patch used to fetch these (this patch can also be used under GPL v2):
diff --git a/drivers/iommu/arm/arm-smmu/arm-smmu.c b/drivers/iommu/arm/arm-smmu/arm-smmu.c index 2ff7a72cf377..e2c7e123285d 100644 --- a/drivers/iommu/arm/arm-smmu/arm-smmu.c +++ b/drivers/iommu/arm/arm-smmu/arm-smmu.c @@ -2057,6 +2057,7 @@ static int arm_smmu_device_probe(struct platform_device *pdev) int num_irqs, i, err; u32 global_irqs, pmu_irqs; irqreturn_t (*global_fault)(int irq, void *dev); + u32 smr; smmu = devm_kzalloc(dev, sizeof(*smmu), GFP_KERNEL); if (!smmu) { @@ -2174,6 +2175,15 @@ static int arm_smmu_device_probe(struct platform_device *pdev) /* Check for RMRs and install bypass SMRs if any */ arm_smmu_rmr_install_bypass_smr(smmu); + for (i = 0; i < smmu->num_mapping_groups; i++) { + smr = arm_smmu_gr0_read(smmu, ARM_SMMU_GR0_SMR(i)); + + if (smr & ARM_SMMU_SMR_VALID) + dev_info(dev, "Stream match register: <&apps_smmu 0x%lx 0x%lx>", + FIELD_GET(ARM_SMMU_SMR_ID, smr), + FIELD_GET(ARM_SMMU_SMR_MASK, smr & ~ARM_SMMU_SMR_VALID)); + } + arm_smmu_device_reset(smmu); arm_smmu_test_smr_masks(smmu);
Dissecting the device tree on Android
It might be hard to find device tree nodes that prevent crashes on remote processors in the downstream device tree. For example, the Pixel 3a (SDM670) needs a modemsmem and smp2p sleepstate node for modem and audio, emitting unintelligible fatal error messages otherwise. To find these, it's useful to set up an environment where you can remove each device tree node (or a few at once, don't hold back too much!) from the same file and quickly pack a boot image. If the crash is caused by a missing device tree node, you'll see some crashes pop up in Android dmesg after a while.
Here is a Makefile and the commands to set up such an environment.
Makefile:
boot.img: cmdline dtb kernel ramdisk mkbootimg --output boot.img $(file < cmdline) flash: boot.img fastboot flash boot boot.img dtb: sdm670-google-sargo.dts gcc -E -x assembler-with-cpp $< | dtc -o $@
Command line:
$ unpack_bootimg --boot_img ../boot.img --out . --format mkbootimg > cmdline $ fdtoverlay -i dtb -o sdm670-google-sargo.dtb ../android_kernel_google_msm-4.9/.output/arch/arm64/boot/dts/google/sdm670-s4-pvt.dtbo $ dtc sdm670-google-sargo.dtb -o sdm670-google-sargo.dts $ fastboot erase dtbo
The C preprocessor is run on the DTS because it lets you use the #if 0
and #endif
directives and nest them.
DTBO masking
The DTBO on Android devices has virtually no use in mainline Linux, as the entire device tree is included in the boot.img. It often prevents devices from booting mainline when the DTBO fails to apply, and it is generally recommended to remove it for devices running mainline.
It is also possible to keep the DTBO without substantially affecting the device tree, although this is a bit hacky and unacceptable upstream. This could make netboot a bit more convenient to use.
Shell script:
#!/bin/sh syms="$(awk '/};/ { echo = 0 } { if (echo == 1) print $1 } /__fixups__ {/ { echo = 1 }' "$1")" cat <<EOF // SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) $(date +%Y), The Linux Foundation. All rights reserved. * * FIXME: Change the copyright and license if desired. Consider licensing under * BSD-3-Clause if you are permitted to do so and adding the original copyright * headers. */ / { __symbols__ { EOF printf '%s' "$syms" | awk '{ print "\t\t" $1 " = \"/masked/" $1 "\";" }' cat <<EOF }; masked { EOF printf '%s' "$syms" | awk '{ print "\t\tmasked_" $1 " = <&_masked_" $1 ">;" }' echo printf '%s' "$syms" | awk '{ print "\t\t_masked_" $1 ": " $1 " { status = \"disabled\"; };" }' cat <<EOF }; }; EOF
Command line:
$ dtc -o device-tree-overlay.dts android_kernel_google_msm-4.9/.output/arch/arm64/boot/dts/google/sdm670-s4-pvt.dtbo $ ./gen-dtbo-mask.sh device-tree-overlay.dts > sdm670-google-common-dtbo-mask.dtsi
The result can be included in the mainline device tree.
Restarting remote processors
The sensors registry is only probed when the SLPI/ADSP boots, so it can be useful to reboot a remote processor without rebooting the device.
First, you need to find the number identifying the remote processor through sysfs (the exact path will vary between SoCs):
$ ls /sys/devices/platform/soc@0/62400000.remoteproc/remoteproc/ remoteproc1
Then, you can reboot the remote processor (until the device boots again and the drivers race to get a unique identifier):
$ sudo sh -c 'printf stop > /dev/remoteproc1' $ sudo sh -c 'printf start > /dev/remoteproc1'
Local modifications
Local service: Charging capacity limits
A custom userspace program can limit the charging when the capacity is 80%:
// SPDX-License-Identifier: GPL-2.0-or-later OR CC-BY-SA-4.0 #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> struct airfry_context { int capacity_fd; int status_fd; }; static void on_charging_poll(struct airfry_context *ctx) { char buf[256]; long capacity; ssize_t len; lseek(ctx->capacity_fd, 0, SEEK_SET); len = read(ctx->capacity_fd, buf, 255); buf[len] = '\0'; capacity = strtol(buf, NULL, 10); lseek(ctx->status_fd, 0, SEEK_SET); if (capacity < 80) dprintf(ctx->status_fd, "Charging\n"); else dprintf(ctx->status_fd, "Not charging\n"); } int main(void) { struct airfry_context ctx; ctx.capacity_fd = open("/sys/class/power_supply/qcom-battery/capacity", O_RDONLY); if (ctx.capacity_fd == -1) { perror("Could not open capacity file"); return 1; } ctx.status_fd = open("/sys/class/power_supply/pm660-charger/status", O_WRONLY); if (ctx.status_fd == -1) { perror("Could not open status file"); return 1; } while (1) { on_charging_poll(&ctx); usleep(30000000); } return 0; }
This program reduces charging on SDM660 and SDM670 devices when the device hits 80%.
Note that the current is never set to 0 mA. A current of zero can heat up a different part of the device and contribute to overheating.
The meson build manifest:
project('lightning-rod', 'c') executable('lightning-rod', 'main.c', install : true)
The local service in /etc/init.d/lightning-rod
:
#!/sbin/openrc-run supervisor=supervise-daemon command=/usr/local/bin/lightning-rod description="Local service: Charging capacity limits"
Local service: Prohibit 2G
With VoLTE support configured, 2G can be disabled. Persistent settings aren't handled in ModemManager yet so it needs another small program:
// SPDX-License-Identifier: GPL-2.0-or-later OR CC-BY-SA-4.0 #include <gio/gio.h> #include <glib.h> #include <libmm-glib.h> #include <ModemManager-enums.h> #include <stdbool.h> static void on_modes_set(GObject *obj, GAsyncResult *res, gpointer data) { GError *err = NULL; mm_modem_set_current_modes_finish(MM_MODEM(obj), res, &err); if (err != NULL) { g_printerr("Failed to set modes: %s\n", err->message); return; } g_main_loop_quit(data); } static void on_object_added(MMManager *manager, GDBusObject *object, gpointer data) { MMModem *modem; modem = mm_object_get_modem(MM_OBJECT(object)); if (modem == NULL) return; mm_modem_set_current_modes(modem, MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, MM_MODEM_MODE_4G, NULL, on_modes_set, data); } static void coldplug_modem(gpointer memb, gpointer data) { bool *found = data; GError *err = NULL; MMModem *modem; modem = mm_object_get_modem(memb); if (modem == NULL) return; mm_modem_set_current_modes_sync(modem, MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, MM_MODEM_MODE_4G, NULL, &err); if (err != NULL) { g_printerr("Failed to set modes: %s\n", err->message); return; } *found = true; } int main(void) { GMainLoop *loop; GError *err = NULL; GDBusConnection *dbus; MMManager *manager; GList *objs; bool found = false; dbus = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &err); if (err != NULL) { g_printerr("Failed to create bearer: %s\n", err->message); return 1; } manager = mm_manager_new_sync(dbus, G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_DO_NOT_AUTO_START, NULL, &err); if (err != NULL) { g_printerr("Failed to create bearer: %s\n", err->message); return 1; } objs = g_dbus_object_manager_get_objects(G_DBUS_OBJECT_MANAGER(manager)); g_list_foreach(objs, coldplug_modem, &found); g_list_free_full(objs, g_object_unref); if (!found) { loop = g_main_loop_new(NULL, FALSE); g_signal_connect(manager, "object-added", G_CALLBACK(on_object_added), loop); g_main_loop_run(loop); } g_object_unref(manager); g_object_unref(dbus); return 0; }
The meson build manifest:
project('mmnogsm', 'c') libmm_glib = dependency('mm-glib') executable('mmnogsm', 'main.c', install : true, dependencies : [libmm_glib])
The local service in /etc/init.d/mmnogsm
:
#!/sbin/openrc-run supervisor=supervise-daemon command=/usr/local/bin/mmnogsm description="Local service: Prohibit 2G" depend() { need modemmanager }
Device-focused code
Some software supporting the Pixel 3a is specialized to the hardware or a specific environment.
Userspace code:
- 81voltd: userspace driver for supporting modem VoLTE firmware
- libssc: userspace driver for sensors
- hexagonrpcd: userspace driver for supporting DSP firmware
- make-dynpart-mappings: dynamic partition support (dual-boot)
- pd-mapper: userspace driver for supporting modem firmware
- rmtfs: userspace driver for supporting modem firmware
- tqftpserv: userspace driver for supporting modem firmware
Kernel code:
- drv2624: haptic feedback driver
- imx363: rear camera driver
- modemsmem: driver for supporting modem firmware
- panel-novatek-nt37700f: Pixel 3a XL panel driver
- panel-samsung-sofef00-bonito: Pixel 3a XL panel driver
- pinctrl-sdm670-lpass-lpi: sound card pin controller driver, should be boring
- qcom_fg: battery measurement driver, imported from MSM8996 and SDM845
- q6voice: call audio driver, imported from MSM8916
- sdm660-internal: sound card driver
- (upstreamed in kernel but not used widely) fastrpc: DSP communication driver
These have little interest outside Linux mobile and Qualcomm device support and may receive lower auditing or automated testing than other mainstream FOSS (e.g. Linux, ModemManager, NetworkManager). This list is a stub.
Power management
This Rust program samples the battery current to the standard output:
// SPDX-License-Identifier: GPL-3.0-only OR CC-BY-SA-4.0 use std::fs::File; use std::io::Read; use std::io::Result; use std::io::Seek; use std::io::Write; use std::time::Duration; use std::time::Instant; use std::thread::sleep; fn main() -> Result<()> { let mut f = File::open("/sys/class/power_supply/qcom-battery/current_now")?; let mut buf = String::new(); let interval = Duration::from_nanos(1444444444); let mut target = Instant::now() + interval; loop { buf.clear(); f.rewind()?; f.read_to_string(&mut buf)?; print!("{}", buf); sleep(target - Instant::now()); target += interval; } }
If you save the samples to a file (interrupt whenever you collected enough data), you can graph the samples in GNU Octave:
y = load("-ascii", "/tmp/current-log") / 1000 x = 0:1.444444444:((length(y) - 1) * 1.444444444) y_movmin = movmin(y, 27) y_movmean = movmean(y, 81) plot(x, y_movmin, x, y_movmean)
Contributing (Pixel 3a page draft)
It takes a lot of effort to run a port that is complete and reliable. Here are some ideas to contribute to the port.
This section is about the Pixel 3a port. You can also consider contributing anywhere to postmarketOS.
Testing
There are opportunities to test at a few different stages of development. The latest stage of development is documented first.
Stable releases
Stable release testing is a requirement for community devices like the Pixel 3a. User:Pabloyoyoista did this in v24.12, but more testing is also welcome.
The checklist template can be found at Kernel_upgrade_testing#Stable_release_testing.
Pending package upgrades
Package upgrades are submitted as merge requests to pmaports. You can test package upgrades that:
- are open
- upgrade the SDM670 kernel or aren't marked "Draft:"
- have successful pipelines
- affect your device (i.e. not a device-specific change to a different device)
You will need:
- an understanding of mrtest
- a burner installation that you can abandon
- a plan to reflash a usable OS
- active monitoring of merge requests
Steps:
- Wait for a merge request (or find one)
- Run mrtest on the burner installation
- Test features related to the merge request
Upgrades to the SDM670 kernel include upstream changes, and all hardware features might be related. Testing is welcome once they're open, even when they are drafts. User:Flamingradian usually tests kernel upgrades on the non-XL model with Phosh and OpenRC.
For full testing, a checklist template can be found at Kernel_upgrade_testing#Stable_release_testing.
Linux pre-releases
When a change gets accepted by the first Linux kernel maintainer, it can take 9-18 weeks to be included in a release. This generous testing period lets anyone test linux-next at any time and still potentially submit valuable bug reports.
You'll need to understand:
- files and directories
- git (
pull
,am [--continue|--abort|--quit]
,checkout [-f]
,fetch
,status
) - make
- pmbootstrap (
build --envkernel
,sideload
) - linux-patches repository.
You will also need:
- a burner installation that you can abandon
- a plan to reflash a usable OS
- a clone of the Linux kernel (multi-gigabyte download)
Overview:
- Patch the kernel sources
- Compile the kernel
- Deploy to your phone
- Test things
- Restore a usable state
The first thing to test is if all patches apply. Fetch the latest linux-next and checkout. Then apply the patches from the linux-patches repo according to the list-stable file.
If you get a merge conflict, you found a bug.
Second, you need to compile the kernel. You probably want to run:
$ make defconfig sdm845.config sdm670.config ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- O=.output $ make -j8 ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- O=.output
The ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu-
part can be omitted if you're using an ARM computer (e.g. a chromebook or smartphone). If you get an error, you found a bug.
With your burner installation running, plug in your phone, and run:
$ pmbootstrap build --envkernel linux-postmarketos-qcom-sdm670 $ pmbootstrap sideload linux-postmarketos-qcom-sdm670 $ ssh -t [email protected] doas reboot
If it doesn't boot, you found a bug. You can hold down the Power and Volume Down buttons to get to the bootloader menu.
When you're done testing, make sure to remove the last $(pmbootstrap config work)/packages/edge/aarch64/linux-postmarketos-qcom-sdm670-*
file and run pmbootstrap index
. That prevents any new installations from using an unreleased kernel.
Auditing
A lot of code is specialized for running postmarketOS on the Pixel 3a.
By reading code that is used right now, you can:
- get familiar with the port
- search for mistakes in the code
- search for discrepancies in the documentation
- monitor and review changes
- get code in line with upstream standards
- suggest design changes
You will need to understand the programming language of the code which you're auditing.
Reverse engineering
WARNING: Remember to respect intellectual property and applicable laws when doing anything in this section. |
A hardware feature that might require reverse engineering if it was never supported by this port.
You will need to understand:
- C programming (sans safety; your code will be private and run a few times)
- grep command
You will need:
- an Android-like installation with root (e.g. LineageOS with rooted debugging, rooted CalyxOS)
- time and effort
- problem solving skills
Depending on your feature, you may need to understand:
- QMI (for interacting with the modem)
- Devicetree (which specifies hardware, and controls how the drivers run)
You may also need to:
- * plan 4 hour sessions to run Android by saving and restoring postmarketOS, if the kernel driver gives too little information
- go from start to finish with your desired feature, to protect personal information in the intermediates
There is no systematic process for reverse engineering. The following subsections document some of the relevant processes, but can't be perfectly step-by-step.
Steps for finding the necessary GPS commands looked like:
- Finding the driver for a hardware feature
- Sniffing QMI requests on Android (2 hour session)
- Replaying requests (formatted in a text file) on postmarketOS
- Simplifying the requests file by removing most requests
- Analyzing the remaining requests
Steps for implementing VoLTE looked like:
- Finding the driver for a hardware feature
- Sniffing QMI on Android (2 hour session)
- Simplifying the messages on Android by blocking some messages
- Analyzing the messages that cause VoLTE to work
Steps for fixing a modem crash on startup looked like:
- Simplifying the Android devicetree
- Analyzing the node that fixes the crash that happens on postmarketOS
Steps for fixing an IOMMU fault by internal storage startup looked like:
- Simplifying the Linux mainline devicetree (inverted: code was simplified to not crash)
- Finding the IOMMU driver
- Simplifying the IOMMU driver
- Analyzing the driver code that causes the crash
- Simplifying the bootloader-initialized IOMMU streams
- Sniffing the remaining stream
The source code for Android's kernel contains many drivers. It's a major source of information and can be injected with sniffers. First, you need to find the driver(s) to read.
In the kernel, you can use grep -R
on the devicetrees (arch/arm64/boot/dts/qcom
and arch/arm64/boot/dts/google
) to search for keywords related to the feature. The device tree points to the driver with the compatible
property.
Running grep -R
with the compatible as input can locate the driver for the corresponding device tree node.
If you can't find the kernel drivers you're looking for, you can ask if anyone knows in Matrix and IRC (#postmarketos-mainline).
Driver-based sniffing
The kernel driver usually communicates with the hardware, but the useful logic is sometimes in a proprietary program in userspace. If this is the case, you will need to sniff the communication with the hardware.
Read the kernel code and find a point that has just the data you need. User programs usually reach the kernel driver at ioctl
functions or by writing to sysfs. For the modem, a network transport is used (see net/ipc_router/ipc_router_socket.c
).
Once you're there, you should insert a few things:
- buffer allocation
bin2hex
function call to convert the blob to printable hexadecimalpr_warn
statement
The printout should include a unique prefix, metadata, the data, and maybe a suffix.
Example:
hexbuf = kmalloc(total_len * 2 + 1, GFP_KERNEL); if (hexbuf == NULL) { pr_warn("msm_ipc debug: not enough memory\n"); goto no_log; } *bin2hex(hexbuf, msg->prev->data, total_len) = '\0'; pr_warn("msm_ipc debug: send data from node=%x port=%04x to node=%x port=%04x: %s, comm=%s\n", ..., hexbuf, current->comm); kfree(hexbuf); no_log:
Ensure Android is installed, then compile and deploy your modified kernel.
If you don't see a suffix in dmesg, check if the LOG_LINE_MAX
value is large enough in kernel/printk/printk.c
.
Replaying on Linux
After sniffing communications, you can try replaying them to the hardware on Linux mobile.
For I2C communications, you can use the i2c-tools
package in shell scripts to replay I2C communication. It can't be used if there is already a driver for the I2C device.
For QMI, you can use your own C program. You should only replay outgoing requests, and other outgoing messages need to be analyzed if they are needed for your feature.
If your feature doesn't work after replaying the communications, you might need to look for other kernel code that supports the feature.
Simplification
You should simplify the code you found. Simplifying reduces the size of the code to make the analysis less time-consuming.
You can use replayed communications and test on Linux mobile. If you can't replay (e.g. you need to simplify outgoing QMI responses), you can also test on Android and inhibit communications instead of removing something.
You will need:
- A test
- Code that passes the test
- Code that fails the test (can be empty, or a kernel driver using the same APIs)
The process involves removing code from one of the starting points, eventually ending up with working code that differs very little from the failing code:
- Copy your code to a temporary file.
- Guess which parts of the code are related.
- Choose a random part, and related parts, to remove.
- Test the new code.
- If the code fails, restore your code from the first step.
- Repeat until you can't remove anything.