User:Flamingradian
📱 | This user's main device is a Google Pixel 3a (google-sargo). |
---|
This user has a GitLab profile at gitlab.com instance. |
This user has a pmOS's GitLab profile. |
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 }