User:Flamingradian

From postmarketOS Wiki
📱 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.
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 thermal limits

Automatic, safe charging is currently not implemented in the kernel. The best that can be done currently is a custom userspace program:

// SPDX-License-Identifier: GPL-2.0-or-later OR CC-BY-SA-4.0
/*
 * Copyright (c) 2024, Richard Acayan. All rights reserved.
 */

#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <upower.h>

struct airfry_context {
	guint timeout;

	int temp_fd;
	int capacity_fd;
	int current_fd;

	long current;
};

static gboolean on_charging_poll(gpointer data)
{
	struct airfry_context *ctx = data;
	char buf[255];
	long temp, capacity;
	long current = ctx->current;
	ssize_t len;

	lseek(ctx->temp_fd, 0, SEEK_SET);
	len = read(ctx->temp_fd, buf, 255);
	buf[len] = '\0';
	temp = strtol(buf, NULL, 10);

	lseek(ctx->capacity_fd, 0, SEEK_SET);
	len = read(ctx->capacity_fd, buf, 255);
	buf[len] = '\0';
	capacity = strtol(buf, NULL, 10);

	if (temp >= 44500)
		current = 500000;
	else if (ctx->current > 1000000 && temp >= 42500)
		current = 1000000;
	else if (ctx->current > 1500000 && temp >= 41500)
		current = 1500000;
	else if (ctx->current > 2000000 && temp >= 39500)
		current = 2000000;
	else if (ctx->current > 2500000 && temp >= 37500)
		current = 2500000;
	
	if (temp < 365)
		current = 3000000;
	else if (ctx->current < 2500000 && temp < 38500)
		current = 2500000;
	else if (ctx->current < 2000000 && temp < 40500)
		current = 2000000;
	else if (ctx->current < 1500000 && temp < 41500)
		current = 1500000;
	else if (ctx->current < 1000000 && temp < 43500)
		current = 1000000;

	if (capacity >= 80)
		current = 500000;

	ctx->current = current;
	lseek(ctx->current_fd, 0, SEEK_SET);
	dprintf(ctx->current_fd, "%ld\n", ctx->current);

	return G_SOURCE_CONTINUE;
}

static void on_device_state_changed(UpDevice *dev, GParamSpec *param, gpointer data)
{
	GValue val = G_VALUE_INIT;
	UpDeviceState state;
	struct airfry_context *ctx = data;

	g_object_get_property(G_OBJECT(dev), "state", &val);
	state = g_value_get_uint(&val);

	if (state == UP_DEVICE_STATE_CHARGING) {
		if (!ctx->timeout) {
			ctx->current = 3000000;
			ctx->timeout = g_timeout_add(1000, on_charging_poll, ctx);
		}
	} else if (state == UP_DEVICE_STATE_DISCHARGING) {
		if (ctx->timeout) {
			g_source_remove(ctx->timeout);
			ctx->timeout = 0;
		}
	}
}

static void coldplug_device(gpointer item, gpointer data)
{
	UpDevice *dev = UP_DEVICE(item);

	g_signal_connect(dev, "notify::state", G_CALLBACK(on_device_state_changed), data);
}

int main(void)
{
	GPtrArray *devs;
	UpClient *client;
	GMainLoop *loop;
	struct airfry_context ctx = {
		.timeout = 0,
	};

	ctx.temp_fd = open("/sys/devices/platform/soc@0/c440000.spmi/spmi-0/0-00/c440000.spmi:pmic@0:adc@3100/iio:device0/in_temp_msm_therm_input", O_RDONLY);
	if (ctx.temp_fd == -1) {
		perror("Could not open temperature file");
		return 1;
	}

	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.current_fd = open("/sys/class/power_supply/pm660-charger/current_max", O_WRONLY);
	if (ctx.current_fd == -1) {
		perror("Could not open current file");
		return 1;
	}

	client = up_client_new();
	devs = up_client_get_devices2(client);
	g_ptr_array_foreach(devs, coldplug_device, &ctx);

	loop = g_main_loop_new(NULL, FALSE);
	g_main_loop_run(loop);

	g_object_unref(client);

	return 0;
}

This program reduces charging on SDM660 and SDM670 devices. It also reduces charging when the device hits 80%, illustrating a tangible choice that can be made by the user of a free operating system.

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('airfry', 'c')

libupower = dependency('upower-glib')

executable('airfry',
  'main.c',
  install : true,
  dependencies : [libupower])

The local service in /etc/init.d/airfry:

#!/sbin/openrc-run

supervisor=supervise-daemon
command=/usr/local/bin/airfry
description="Local service: Charging thermal 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
/*
 * Copyright (c) 2024, Richard Acayan. All rights reserved.
 */

#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
}