Jump to content

User:Flamingradian

From postmarketOS Wiki
Revision as of 02:37, 3 April 2025 by Flamingradian (talk | contribs) (remove copyright headers, add license identifier for power management program)
📱 This user's main device is a Google Pixel 3a (google-sargo).
pmOS logo This user has a pmOS's GitLab profile.
Element logo This user has a Matrix account.
Element logo This user has a Matrix account.
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)