Soft-Fixing a Kindle 8th Gen with a Dead Fuel Gauge
TL;DR: The
bd7181xfuel gauge IC on a Kindle 8th gen (model SY69JL) can fail in a way that makes the device believe the battery is always empty, even when it's fully charged. Since the replacement chip is difficult to find and replace without breaking the screen, this guide documents how to work around it entirely using a jailbreak and spoofing the battery level indicator via software.
You can also view the script used in Github.
Background and Problem Description
This is the story of a Kindle Paperwhite 8th generation (model SY69JL) that refused to stay on. The symptom was clear, even with a fully charged battery (measured at ~4.2 V with a multimeter), the device would report a low battery, show 0–7% capacity, and shut down within minutes of use. However this verifies the charging circuit working fine as the battery stays within its operating voltage range.
The usual suspects were ruled out first:
- Hard resets (40-second power button hold, with and without USB connected) had no lasting effect.
- The battery itself was healthy — voltage confirmed at 4.20 V.
- Bridging the
RSENSresistor (markedR030on the board, which is the current-sense shunt used by the fuel gauge) made things briefly better, but the problem always returned.
Root Cause: The Coulomb Counter
After reviewing the bd7181x datasheet (a ROHM PMIC that integrates five buck converters and a battery management unit), the actual culprit became clear.
The chip's coulomb counter — the circuit that tracks how much charge has been consumed — was reporting a constant ~1354 mA discharge current (current_now = -1354) regardless of actual consumption. A Kindle at idle draws a fraction of that. The chip was lying, and because it's the authoritative source for capacity in the Linux kernel's power_supply subsystem, the OS believed it.
/sys/class/power_supply/bd7181x_bat/capacity = 7
/sys/class/power_supply/bd7181x_bat/voltage_now = 4188 ← ~4.19 V, nearly full
/sys/class/power_supply/bd7181x_bat/status = Charging
The voltage was perfectly fine. The reported capacity was not. The fault was self-evident, a broken coulomb counter. The battery and the IC-reported state-of-charge completely different.
Replacing the chip was not an option. The bd7181x is not available on AliExpress, eBay, or any accessible market. I could either,
- live with it, therefore stashing the kindle on a drawer because it stayed on for around 30 minutes before powering off, after disconecting the battery and connecting again.
- Buy another broken kindle with a working charging chip and swap it. Risking damaging the screen because e-ink as i have read is prone to permanently damage it with heat.
- Bypass it, it seemed like the most appropiate option at the time, and i think it was the right option.
The Fix: Jailbreak + Virtual Fuel Gauge
The approach is conceptually simple:
- Jailbreak the Kindle (using WinterBreak).
- Access the device over SSH via USB, using USBNET.
- Create a shell script that reads the real battery voltage and maps it to the estimated capacity percentage.
- Use bind mounts to shadow the broken sysfs files (
capacity,battery_mah) with the calculated values. - Register the script as an Upstart service that launches before
powerd, so the OS never sees the bad values.
No hardware modification is needed beyond what you may have already attempted. The Kindle runs normally now, the battery indicator just reflects voltage-based estimation rather than the broken coulomb counter.
Prerequisites
- Kindle 8th gen (tested on model
SY69JL) - Jailbreak applied (WinterBreak or equivalent that gives root SSH access)
- SSHd running on the device
- A Linux machine (or WSL) with a micro-usb cable
Step 1 — Connect via SSH over USB
When you plug the Kindle into a Linux machine, it exposes a USB network interface (RNDIS/CDC-ECM). The Kindle assigns itself a static IP in the 192.168.15.0/24 range. You need to configure your host interface to match.
First, identify the interface name:
ip addr
Look for an interface that got an address in 169.254.x.x — that's the Kindle's USB interface. In this case it was enp0s20f0u4i1.
Reconfigure it:
sudo ip addr flush dev enp0s20f0u4i1
sudo ip addr add 192.168.15.201/24 dev enp0s20f0u4i1
sudo ip link set enp0s20f0u4i1 up
The Kindle should be reachable at 192.168.15.244:
ping 192.168.15.244
ssh root@192.168.15.244
When prompted for a password, just press Enter — the default root password is empty on jailbroken Kindles.
You'll be greeted with:
Welcome to Kindle!
Rootfs is mounted read-only. Invoke mntroot rw to switch back to a writable rootfs.
Step 2 — Inspect the Battery State
Before doing anything permanent, verify what the kernel is actually seeing:
for d in /sys/class/power_supply/*; do
echo "== $d =="
for f in capacity battery_mah status voltage_now present online health temp charge_now current_now; do
[ -f "$d/$f" ] && echo "$f = $(cat "$d/$f" 2>/dev/null)"
done
done
On a device with this failure, you'll see something like:
== /sys/class/power_supply/bd7181x_bat ==
capacity = 7 ← should be ~95–100%
status = Charging
voltage_now = 4188 ← 4.19 V, battery is fine
present = 1
health = Good
temp = 30
charge_now = 68
current_now = -1354 ← ~1.35 A drain reported — clearly wrong
The voltage is healthy. The capacity is nonsense. This confirms the diagnosis.
Step 3 — Install the Virtual Fuel Gauge Script
Remount the root filesystem as writable:
mntroot rw
If mntroot is not available:
mount -o remount,rw /
Now create the script. This version maps voltage to percentage using a lookup table, then uses bind mounts to shadow the two sysfs files the Kindle's power daemon (powerd) reads for battery level. The mount is only applied once; subsequent loop iterations just update the file contents, avoiding mount stack accumulation.
cat > /usr/local/bin/virtual-fuelgauge.sh <<'EOF'
#!/bin/sh
BAT="/sys/class/power_supply/bd7181x_bat"
FAKE_DIR="/tmp/battery-spoof"
FAKE_CAP="$FAKE_DIR/capacity"
FAKE_MAH="$FAKE_DIR/battery_mah"
DISABLE1="/var/local/DISABLE_BATTERY_SPOOF"
DISABLE2="/mnt/us/DISABLE_BATTERY_SPOOF"
mkdir -p "$FAKE_DIR"
voltage_to_percent() {
mv="$1"
# Normalize microvolt readings if needed
if [ "$mv" -gt 100000 ]; then
mv=$((mv / 1000))
fi
if [ "$mv" -ge 4180 ]; then echo 100
elif [ "$mv" -ge 4138 ]; then echo 95
elif [ "$mv" -ge 4096 ]; then echo 90
elif [ "$mv" -ge 4053 ]; then echo 85
elif [ "$mv" -ge 4011 ]; then echo 80
elif [ "$mv" -ge 3969 ]; then echo 75
elif [ "$mv" -ge 3927 ]; then echo 70
elif [ "$mv" -ge 3884 ]; then echo 65
elif [ "$mv" -ge 3842 ]; then echo 60
elif [ "$mv" -ge 3800 ]; then echo 55
elif [ "$mv" -ge 3757 ]; then echo 50
elif [ "$mv" -ge 3715 ]; then echo 45
elif [ "$mv" -ge 3673 ]; then echo 40
elif [ "$mv" -ge 3630 ]; then echo 35
elif [ "$mv" -ge 3588 ]; then echo 30
elif [ "$mv" -ge 3545 ]; then echo 25
elif [ "$mv" -ge 3500 ]; then echo 20
elif [ "$mv" -ge 3400 ]; then echo 15
elif [ "$mv" -ge 3300 ]; then echo 10
elif [ "$mv" -ge 3200 ]; then echo 5
elif [ "$mv" -ge 3100 ]; then echo 1
else echo 0
fi
}
is_mounted() { grep -q " $1 " /proc/mounts; }
unmount_all() {
while grep -q " $1 " /proc/mounts; do
umount "$1" 2>/dev/null || break
done
}
mount_fake_once() {
real="$(readlink -f "$1" 2>/dev/null)"
fake="$2"
[ -n "$real" ] && [ -f "$real" ] && [ -f "$fake" ] || return 0
is_mounted "$real" || mount --bind "$fake" "$real"
}
update_values_only() {
[ -d "$BAT" ] || exit 0
voltage="$(cat "$BAT/voltage_now" 2>/dev/null)"
[ -n "$voltage" ] || voltage="4000"
percent="$(voltage_to_percent "$voltage")"
echo "$percent" > "$FAKE_CAP"
echo "1000" > "$FAKE_MAH"
}
mount_all_once() {
update_values_only
mount_fake_once "$BAT/capacity" "$FAKE_CAP"
mount_fake_once "$BAT/battery_mah" "$FAKE_MAH"
}
disabled_forever() {
unmount_all "$(readlink -f "$BAT/capacity" 2>/dev/null)"
unmount_all "$(readlink -f "$BAT/battery_mah" 2>/dev/null)"
while true; do sleep 3600; done
}
case "$1" in
once)
[ -f "$DISABLE1" ] || [ -f "$DISABLE2" ] && exit 0
mount_all_once; exit 0 ;;
loop)
{ [ -f "$DISABLE1" ] || [ -f "$DISABLE2" ]; } && disabled_forever
mount_all_once
while true; do
{ [ -f "$DISABLE1" ] || [ -f "$DISABLE2" ]; } && disabled_forever
update_values_only
sleep 30
done ;;
*)
echo "Usage: $0 once|loop"; exit 1 ;;
esac
EOF
chmod +x /usr/local/bin/virtual-fuelgauge.sh
How the script works
The script has two modes:
once— applies the bind mounts immediately and exits. Used by the Upstart service'spre-starthook so the spoofed values are in place beforepowerdreads them.loop— applies the bind mounts once, then re-reads the real voltage every 30 seconds and updates the file content (without mounting again). This keeps the displayed percentage tracking the actual battery voltage throughout use.
The bind mount trick works because /sys files are just kernel interfaces. Mounting a tmpfs-backed file on top of them makes the kernel return our file's content whenever anything reads that path — including powerd.
Step 4 — Register the Upstart Service
The service must start before powerd reads the battery state. Upstart's start on starting powerd directive guarantees this ordering.
cat > /etc/upstart/battery-spoof.conf <<'EOF'
description "Virtual battery fuel gauge spoof"
start on starting powerd
stop on stopping powerd
respawn
respawn limit unlimited
pre-start exec /usr/local/bin/virtual-fuelgauge.sh once
exec /usr/local/bin/virtual-fuelgauge.sh loop
EOF
Reload Upstart and start the service immediately:
initctl reload-configuration
initctl start battery-spoof
Verify everything looks correct:
initctl status battery-spoof
cat /sys/class/power_supply/bd7181x_bat/voltage_now
cat /sys/class/power_supply/bd7181x_bat/capacity
cat /sys/class/power_supply/bd7181x_bat/charge_now
mount | grep bd7181x_bat
Expected output for mount | grep bd7181x_bat — exactly two lines, nothing more:
tmpfs on .../bd7181x_bat/capacity type tmpfs (rw,relatime,size=65536k)
tmpfs on .../bd7181x_bat/battery_mah type tmpfs (rw,relatime,size=65536k)
If you see more than two lines, the earlier version of the script (which mounted on every loop iteration) was running. Stop the service, clean up the stacked mounts, and restart:
initctl stop battery-spoof
BAT="$(readlink -f /sys/class/power_supply/bd7181x_bat)"
while grep -q "$BAT/capacity" /proc/mounts; do
umount "$BAT/capacity" 2>/dev/null || break
done
while grep -q "$BAT/battery_mah" /proc/mounts; do
umount "$BAT/battery_mah" 2>/dev/null || break
done
initctl start battery-spoof
Step 5 — Seal and Reboot (Warning)
(Warning!) Theorically you should do this, however i couldn't mount as ro and, as rebooting the kindle could lead to it not powering on again for a while or after i replugged it, i decided against it, but i leave the steps here to documentate it properly.
It should mount itself as ro after rebooting anyway so it isn't really necesary, but here we go.
Remount the root filesystem as read-only:
mntroot ro
Then reboot and reconnect over SSH to confirm the service started automatically:
reboot
# ... reconnect after boot ...
initctl status battery-spoof
cat /sys/class/power_supply/bd7181x_bat/capacity
If capacity reflects a value that is multiple of 5 (ends in either 5 or 0) it is working.
Kill Switch
If something goes wrong or you need to disable the spoof, you don't need to touch the filesystem. Create an empty file at either of these paths and reboot:
# From the Kindle shell
mntroot rw
touch /var/local/DISABLE_BATTERY_SPOOF
# Or from the USB storage visible on your computer
touch /mnt/us/DISABLE_BATTERY_SPOOF
To re-enable:
mntroot rw
rm -f /var/local/DISABLE_BATTERY_SPOOF
reboot
Caveats and Limitations
- (IMPORTANT) The spoof only works while the Kindle is on. If the device powers off due to genuinely depleted voltage, the spoof can't help — it requires the kernel to be running.
- Voltage-based estimation is not perfect. Li-ion discharge curves are non-linear and load-dependent. The lookup table here is a reasonable approximation for typical Kindle usage, but don't treat the percentage as a precise fuel gauge, it is just an estimation.
current_nowis not spoofed. The reported current draw insysfswill still show the bogus ~1.35 A reading if you check it directly. Onlycapacityandbattery_mahare overridden, which is whatpowerduses for its shutdown logic.- This was tested on an 8th gen Kindle (SY69JL). Other models may use different PMIC drivers, sysfs paths, or Upstart configurations.
Comentarios
Publicar un comentario