Jump to content

Google Pixel 7a (google-lynx)

From postmarketOS Wiki
Google Pixel 7a
Google Pixel 7a
Google Pixel 7a
Manufacturer Google
Name Pixel 7a
Codename google-lynx
Released 2023
Type handset
Hardware
Chipset Google Tensor G2
CPU Octa-core (2x2.85 GHz Cortex-X1
2x2.35 GHz Cortex-A78
4x1.80 GHz Cortex-A55)
GPU Mali-G710 MP7
Display FHD+ OLED 2400×1080 90Hz
Storage 128 GB
Memory 8 GB
Architecture aarch64
Software
Original software
The software and version the device was shipped with.
Android 13
Extended version
The most recent supported version from the manufacturer.
Android
postmarketOS
Mainline
Instead of a Linux kernel fork, it is possible to run (Close to) Mainline.
no
pmOS kernel
The kernel version that runs on the device's port.
6.x.x
Unixbench score
Unixbench Whetstone/Dhrystone score. See Unixbench.
0.0

The Google Pixel 7a is Google's 2023 mid-range smartphone, leveraging the capabilities of Google's in-house Tensor G2 chipset.

Contributors

Users owning this device

How to Unlock Bootloader

WARNING: Doing this will wipe ALL data on your device. Please backup all your personal data before continuing!

  1. In Settings > About Phone, scroll down and tap Build Number 7 times.
  2. In Settings > System > Developer Options, enable USB Debugging and tap OK.
  3. In Developer Options, enable OEM Unlocking. You will be prompted for your PIN (if applicable) and warned about the risks of OEM unlocking. Tap Enable.
  4. Backup any important data.
  5. Connect your device to a PC and open a terminal.
  6. Confirm your device is detected by typing adb devices.
  7. Authorize the device with the onscreen dialogue if it's listed as Unauthorized.
  8. Reboot to the bootloader with adb reboot bootloader.
  9. Unlock the bootloader with fastboot flashing unlock.
  10. Press continue on your device's screen.
  11. Reboot

How to enter flash mode

  1. Power off the device.
  2. Press and hold Volume Down and Power until Fastboot Mode appears.

You are now ready to flash!

Installation

TBD

Prerequisites

$ sudo apt install -y fastboot android-sdk-platform-tools python3-pip pmbootstrap
$ pip install payload_dumper mkbootimg

Initialize pmbootstrap

$ pmbootstrap init

Answer the prompts:

  • Work path: Press Enter (default)
  • pmaports path: Press Enter (default)
  • Channel: edge
  • Vendor: google
  • Device codename: lynx
  • Username: user
  • Provider (audio): Press Enter (default)
  • Provider (wifi): Press Enter (default)

Device Package

Create the device package directory:

$ mkdir -p ~/.local/var/pmaports/device/testing/device-google-lynx

deviceinfo

Create ~/.local/var/pmaports/device/testing/device-google-lynx/deviceinfo:

deviceinfo_format_version="0"
deviceinfo_name="Google Pixel 7a"
deviceinfo_manufacturer="Google"
deviceinfo_codename="google-lynx"
deviceinfo_year="2023"
deviceinfo_arch="aarch64"

deviceinfo_gpu_accelerated="true"
deviceinfo_chassis="handset"
deviceinfo_keyboard="false"
deviceinfo_external_storage="false"
deviceinfo_screen_width="1080"
deviceinfo_screen_height="2400"

deviceinfo_flash_method="fastboot"
deviceinfo_kernel_cmdline="earlycon=exynos4210,0x10A00000 console=ttySAC0,115200 androidboot.console=ttySAC0 printk.devkmsg=on cma_sysfs.experimental=Y cgroup_disable=memory rcupdate.rcu_expedited=1 rcu_nocbs=all rcutree.enable_rcu_lazy page_pinner=on swiotlb=1024 cgroup.memory=nokmem rodata=on at24.write_timeout=100 log_buf_len=1024K android_arch_task_struct_size=512"
deviceinfo_generate_bootimg="true"
deviceinfo_bootimg_qcdt="false"
deviceinfo_bootimg_dtb_second="false"
deviceinfo_flash_offset_base="0x00000000"
deviceinfo_flash_offset_kernel="0x00008000"
deviceinfo_flash_offset_ramdisk="0x01000000"
deviceinfo_flash_offset_second="0x00000000"
deviceinfo_flash_offset_tags="0x00000100"
deviceinfo_flash_pagesize="2048"
deviceinfo_flash_sparse="true"

deviceinfo_bootimg_header_version="4"
deviceinfo_bootconfig="androidboot.load_modules_parallel=true androidboot.boot_devices=14700000.ufs"

deviceinfo_ab_partitions="true"
deviceinfo_ab_partition_suffix="_a"
deviceinfo_ab_partition_list="boot dtbo vendor_boot vendor_kernel_boot system system_ext product vendor vendor_dlkm"

deviceinfo_usb_idVendor="0x18d1"
deviceinfo_usb_idProduct="0x4ee3"

deviceinfo_flash_kernel_on_update="true"
deviceinfo_super_partitions="system system_ext product vendor vendor_dlkm"

deviceinfo_dtb="google/gs201-lynx"
deviceinfo_append_dtb="true"

deviceinfo_rootfs_image_sector_size="4096"

deviceinfo_generate_vendor_bootimg="true"
deviceinfo_generate_vendor_kernel_bootimg="true"

deviceinfo_flash_fastboot_partition_vendor_boot="true"
deviceinfo_flash_fastboot_partition_dtbo="true"

Critical settings explained:

  • deviceinfo_flash_pagesize="2048" — Must be 2048 for Pixel 7a, not 4096
  • deviceinfo_bootimg_header_version="4" — GKI v4 boot header required
  • deviceinfo_rootfs_image_sector_size="4096" — UFS storage uses 4KB sectors
  • deviceinfo_append_dtb="true" — DTB is embedded in vendor_kernel_boot

modules-initfs

Create ~/.local/var/pmaports/device/testing/device-google-lynx/modules-initfs:

# Storage - UFS
ufs-exynos-gs
ufs-pixel-fips140

# Samsung Exynos platform essentials
exynos-pmu-if
pinctrl-exynos-gs
exynos-cpupm
exynos-pm
exynos-pd
exynos-pd_el3
exynos-pd_hsi0

# I2C and GPIO
i2c-exynos5
i2c-acpm

# Serial console
exynos_tty

# Clock and timer
clk_exynos_gs
exynos_mct

# USB
dwc3-exynos-usb
xhci-exynos
phy-exynos-usbdrd-super

# USB gadget and networking
usbnet
cdc_ether
cdc_eem
cdc_ncm
cdc-acm

# USB Type-C
tcpci_max77759

# DMA
pl330
samsung-dma

# Thermal
gs_thermal

# GPU (Mali G710)
mali_kbase

# PMIC / regulator
s2mpg12-mfd
s2mpg12-regulator
s2mpg13-mfd
s2mpg13-regulator
max77779_pmic
max77779_pmic_irq
pmic_class

# Power management
exynos_devfreq
google-bms
google-charger
google-battery

Note: The watchdog module (s3c2410_wdt) is intentionally excluded. Loading it without petting the watchdog causes a 60-second reboot loop. The LOS kernel has a hardware watchdog that must be handled by the initramfs.

APKBUILD

Create ~/.local/var/pmaports/device/testing/device-google-lynx/APKBUILD:

pkgname=device-google-lynx
pkgdesc="Google Pixel 7a"
pkgver=1
pkgrel=1
url="https://postmarketos.org"
license="MIT"
arch="aarch64"
options="!check !archcheck"
depends="
	linux-postmarketos-gs201-6.1
	mkbootimg
	postmarketos-base
	firmware-google-lynx
"
makedepends="devicepkg-dev"
source="
	deviceinfo
	modules-initfs
"

build() {
	devicepkg_build $startdir $pkgname
}

package() {
	devicepkg_package $startdir $pkgname
}

Generate checksums:

$ cd ~/.local/var/pmaports/device/testing/device-google-lynx
$ pmbootstrap checksum device-google-lynx

Kernel Package

The Pixel 7a uses a prebuilt 6.1 kernel extracted from LineageOS 23.2. Building from source requires Google's Bazel build system and the full LOS kernel manifest. For porting, the prebuilt kernel is used.

Create the kernel package directory:

$ mkdir -p ~/.local/var/pmaports/device/testing/linux-postmarketos-gs201-6.1

APKBUILD

Create ~/.local/var/pmaports/device/testing/linux-postmarketos-gs201-6.1/APKBUILD:

_flavor="postmarketos-gs201-6.1"
pkgname=linux-$_flavor
pkgver=6.1.145
pkgrel=0
_krel="6.1.145-android14-11-gec45f20f38ea-aba4c29fc158"
pkgdesc="Prebuilt 6.1.145 kernel for Google Tensor G2 (gs201)"
arch="aarch64"
url="https://github.com/LineageOS/android_kernel_google_gs-6.1_manifest"
license="GPL-2.0-only"
options="!check !strip !archcheck !tracedeps pmb:cross-native"

source="
	get-kernel.sh
	vmlinuz
	gs201-lynx.dtb
	kernel.release
	modules.tar.gz
"

builddir="$srcdir"

package() {
	install -Dm644 "$srcdir"/vmlinuz "$pkgdir"/boot/vmlinuz
	install -Dm644 "$srcdir"/gs201-lynx.dtb "$pkgdir"/boot/dtbs/google/gs201-lynx.dtb
	mkdir -p "$pkgdir"/lib/modules
	tar xzf "$srcdir"/modules.tar.gz -C "$pkgdir"/lib/modules
	sed -i "s|/vendor/lib/modules/|/lib/modules/$_krel/|g" "$pkgdir"/lib/modules/"$_krel"/modules.dep
	install -D "$srcdir/kernel.release" "$pkgdir/usr/share/kernel/$_flavor/kernel.release"
}

get-kernel.sh

Create ~/.local/var/pmaports/device/testing/linux-postmarketos-gs201-6.1/get-kernel.sh:

#!/bin/sh
# Downloads latest LineageOS build and extracts kernel + modules + DTB
set -e

KERNEL_DIR="$(dirname "$(readlink -f "$0")")"

pip install --break-system-packages payload_dumper mkbootimg 2>/dev/null || true

echo "Looking up latest LineageOS build for lynx..."
URL=$(curl -s "https://download.lineageos.org/api/v1/lynx/nightly/1" | \
    python3 -c "import sys,json; print(json.load(sys.stdin)['response'][0]['url'])")

ZIP="$KERNEL_DIR/$(basename "$URL")"

if [ ! -f "$ZIP" ]; then
    echo "Downloading $URL ..."
    curl -L -o "$ZIP" "$URL"
else
    echo "Already downloaded: $ZIP"
fi

WORKDIR="$KERNEL_DIR/.work"
rm -rf "$WORKDIR"
mkdir -p "$WORKDIR"

echo "Extracting payload from zip..."
unzip -o "$ZIP" payload.bin -d "$WORKDIR" >/dev/null

echo "Extracting partition images..."
python3 -m payload_dumper "$WORKDIR/payload.bin" \
    --partitions boot,vendor_kernel_boot,vendor_dlkm \
    --out "$WORKDIR/images"

echo "Extracting kernel..."
BOOT_DIR="$WORKDIR/boot_unpacked"
mkdir -p "$BOOT_DIR"
unpack_bootimg --boot_img "$WORKDIR/images/boot.img" --out "$BOOT_DIR" 2>/dev/null

mkdir -p "$KERNEL_DIR/boot"
cp "$BOOT_DIR/kernel" "$KERNEL_DIR/boot/vmlinuz" 2>/dev/null || \
    cp "$BOOT_DIR/Image" "$KERNEL_DIR/boot/vmlinuz" 2>/dev/null

echo "Extracting DTB..."
VK_DIR="$WORKDIR/vk_unpacked"
mkdir -p "$VK_DIR"
unpack_bootimg --boot_img "$WORKDIR/images/vendor_kernel_boot.img" --out "$VK_DIR" 2>/dev/null

mkdir -p "$KERNEL_DIR/boot/dtbs/google"
cp "$VK_DIR/dtb" "$KERNEL_DIR/boot/dtbs/google/gs201-lynx.dtb" 2>/dev/null || \
    cp "$BOOT_DIR/dtb" "$KERNEL_DIR/boot/dtbs/google/gs201-lynx.dtb" 2>/dev/null

echo "Extracting modules..."
MODULES_OUT="$KERNEL_DIR/lib/modules"
mkdir -p "$MODULES_OUT"
TMP_MNT="$WORKDIR/vendor_dlkm_mnt"
mkdir -p "$TMP_MNT"

sudo mount -t erofs -o ro,loop "$WORKDIR/images/vendor_dlkm.img" "$TMP_MNT" 2>/dev/null || \
    sudo mount -t ext4 -o ro,loop "$WORKDIR/images/vendor_dlkm.img" "$TMP_MNT" 2>/dev/null || true

if mountpoint -q "$TMP_MNT" 2>/dev/null; then
    cp -a "$TMP_MNT/lib/modules/"* "$MODULES_OUT/" 2>/dev/null || true
    sudo umount "$TMP_MNT"
fi
rmdir "$TMP_MNT" 2>/dev/null

cd "$MODULES_OUT"
tar czf "$KERNEL_DIR/modules.tar.gz" .

RELEASE=$(ls "$MODULES_OUT" 2>/dev/null | head -1)
echo "$RELEASE" > "$KERNEL_DIR/kernel.release"

rm -rf "$WORKDIR"
echo "Done! Kernel + DTB + modules extracted to $KERNEL_DIR"

Make executable and run:

$ chmod +x ~/.local/var/pmaports/device/testing/linux-postmarketos-gs201-6.1/get-kernel.sh
$ cd ~/.local/var/pmaports/device/testing/linux-postmarketos-gs201-6.1
$ ./get-kernel.sh
$ pmbootstrap checksum linux-postmarketos-gs201-6.1

Build Rootfs

$ pmbootstrap install --password 147147

The rootfs image is created at:

~/.local/var/pmbootstrap/chroot_native/home/pmos/rootfs/google-lynx.img

Flashing

Important: The Pixel 7a bootloader enforces Android Verified Boot (AVB). The LineageOS vbmeta chain must be used. Custom-signed boot images do not pass verification. Only the vendor_kernel_boot partition can be customized.

Extract all LOS partition images:

$ cd ~/.local/var/pmaports/device/testing/linux-postmarketos-gs201-6.1
$ python3 -m payload_dumper .work/payload.bin --out /tmp/los_images

Flash everything:

# Put phone in fastboot mode (Volume Down + Power)

# Flash rootfs
$ fastboot flash userdata ~/.local/var/pmbootstrap/chroot_native/home/pmos/rootfs/google-lynx.img

# Flash LOS boot chain
$ fastboot flash boot_a /tmp/los_images/boot.img
$ fastboot flash init_boot_a /tmp/los_images/init_boot.img
$ fastboot flash vendor_kernel_boot_a /tmp/los_images/vendor_kernel_boot.img
$ fastboot flash dtbo_a /tmp/los_images/dtbo.img

# Flash vbmeta (both slots)
$ fastboot flash vbmeta_a /tmp/los_images/vbmeta.img
$ fastboot flash vbmeta_b /tmp/los_images/vbmeta.img
$ fastboot flash vbmeta_system_a /tmp/los_images/vbmeta_system.img
$ fastboot flash vbmeta_system_b /tmp/los_images/vbmeta_system.img
$ fastboot flash vbmeta_vendor_a /tmp/los_images/vbmeta_vendor.img
$ fastboot flash vbmeta_vendor_b /tmp/los_images/vbmeta_vendor.img

$ fastboot set_active a

Booting

The device cannot boot pmOS normally. It must boot to LineageOS recovery, then pmOS is accessed via ADB and chroot:

# Boot to recovery
$ fastboot reboot recovery

# Tap "Allow" on the phone for ADB authorization

# Mount pmOS and enter chroot
$ adb shell
# mount -t ext4 /dev/block/by-name/userdata /data
# mount --bind /dev /data/dev
# mount --bind /proc /data/proc
# mount --bind /sys /data/sys
# chroot /data /bin/ash

To resize the rootfs to use all available userdata space:

# resize2fs /dev/block/by-name/userdata

Technical Details

Boot Chain

The Pixel 7a uses GKI v4 boot image headers with this partition layout:

Partition Contents Size AVB
boot Kernel Image (LZ4 compressed), no ramdisk 64 MB Chained to LOS key
init_boot Android init binary (recovery) 8 MB Chained to LOS key
vendor_kernel_boot DTB + kernel modules (210 .ko files) 64 MB Hash descriptor
dtbo Board-specific device tree overlay 16 MB Hash descriptor
vbmeta AVB chain root (flags=3, verification disabled) 8 KB Signed LOS key

Ramdisk Concatenation Order

The bootloader concatenates ramdisks in this order:

boot (empty) -> init_boot (Android init) -> vendor_kernel_boot

The Android init binary from init_boot claims PID 1 before any code from vendor_kernel_boot can execute. This is why pmOS cannot boot without recovery mode.

Why Normal Boot Fails

  1. The Android init (PID 1) starts and cannot find the Android system partition
  2. After ~39 seconds, Android init calls reboot
  3. The device returns to fastboot

The init= kernel parameter is ignored by this kernel. Custom boot.img with ramdisk causes an instant kernel panic. The init_boot partition cannot be replaced (AVB chain requires LOS key).

USB Networking

The kernel has these USB gadget drivers built-in:

CONFIG_USB_CONFIGFS_NCM=y
CONFIG_USB_CONFIGFS_ECM=y
CONFIG_USB_CONFIGFS_EEM=y
CONFIG_USB_F_NCM=y
CONFIG_USB_F_ECM=y

NCM functions can be created via configfs and activated on the UDC (11210000.dwc3), but the USB host never re-enumerates. The PID stays at 4ee0 (fastboot) instead of switching to 4ee3 (network). Recovery mode uses FunctionFS (FFS) for ADB which works because the Android init stack handles enumeration.

Super Partition

The super partition (8.53 GB) contains logical partitions:

Partition Size
system 1.2 GB
system_ext 534 MB
product 2.1 GB
vendor 801 MB
vendor_dlkm 63 MB

The lpmake tool can create a super image, but the bootloader does not create logical partitions from it on this device. The LOS OTA mechanism via recovery sideload is required for proper super partition setup.

Path to Normal Boot

  1. Clone the full LOS kernel manifest:
    git clone https://github.com/LineageOS/android_kernel_google_gs-6.1_manifest
  2. Build with Bazel, adding CONFIG_INITRAMFS_SOURCE pointing to a pmOS initramfs cpio
  3. Enable vendor USB drivers (exynos DWC3, PHY) from the separate vendor repos
  4. Embed the pmOS initramfs into the kernel binary

A custom kernel without vendor drivers was built and confirmed to boot: the USB PID changed from 4ee0 to a different value, proving the kernel with embedded initramfs executes. The vendor driver source is in separate repos under the LOS manifest.