I Just Wanted to Check the Battery: Rooting and Resurrecting a Galaxy A71 from Linux
I had a Samsung Galaxy A71 5G lying around.
It’s not particularly new. It’s not particularly valuable. And, before I factory-reset it, Samsung had been periodically warning me that its battery was deteriorating.
After the factory reset, however, the warning disappeared.
Which made me curious.
Had the battery magically healed itself?
Obviously not.
So I started poking around.
My original plan was extremely reasonable:
Check the actual battery health and see how much life the internal flash storage has left.
A few hours later I had:
- unlocked the bootloader;
- downloaded Samsung’s original firmware;
- patched it with Magisk;
- discovered that Odin4 wouldn’t talk properly to this phone from Linux;
- switched to Thor;
- found two bugs in Thor;
- patched and rebuilt Thor;
- started flashing the phone;
- watched it die halfway through a 6.8 GiB partition;
- discovered a Linux USB memory limitation;
- flashed it again;
- recovered Android;
- obtained root;
- interrogated the UFS controller directly;
- pulled Samsung’s internal battery-health data;
- and disabled close to a hundred pieces of Samsung, Google, Microsoft, Meta and carrier cruft.
So, you know.
A quick battery check.
The patient
The phone was an international Galaxy A71 5G:
Samsung Galaxy A71 5G
Model: SM-A716B
Android: 13
Firmware: A716BXXS9FXA1
CSC: XSG
Storage: 128 GB UFS
My computer runs Xubuntu 24.04.
The phone had already been factory-reset, which made it an unusually good victim for experimentation. There was no data I cared about losing.
That detail becomes rather important later.
It started with dumpsys battery
ADB gives you a decent amount of battery information without root:
adb shell dumpsys battery
Samsung reported the battery as healthy.
More interestingly, buried in Samsung’s additional battery data was:
mSavedBatteryAsoc = 90
That immediately caught my attention.
ASOC is Samsung’s battery state-of-health estimate.
The phone that had previously been nagging me about battery deterioration appeared to believe its battery was still at approximately 90% health.
I wanted more.
Specifically, I wanted access to the actual battery fuel-gauge telemetry and the UFS health descriptors.
Android politely responded to those attempts with some variation of:
Permission denied
Fair enough.
Root it is.
Unlocking the bootloader
Samsung’s OEM Unlock option was available.
Unlocking a Samsung bootloader triggers a factory reset, but the phone was already disposable from a data perspective, so I went ahead.
After the unlock and another setup cycle, Developer Options showed:
OEM unlocking: Enabled
and the toggle was greyed out.
Good.
The bootloader was properly unlocked.
There is one important caveat here: unlocking and modifying Samsung devices can trip Knox and has consequences for Samsung security features. Anyone following along should understand that before doing this to a phone they actually depend on.
This isn’t a “click these commands and everything will definitely be fine” guide.
It’s a record of what happened on my particular SM-A716B.
Getting firmware without visiting a sketchy firmware mirror
For Magisk root on this Samsung, I wanted the exact stock firmware currently installed on the phone.
I used Bifrost to retrieve the firmware directly from Samsung’s servers.
The build was:
A716BXXS9FXA1
The firmware ZIP contained the familiar Samsung collection:
BL_A716B....tar.md5
AP_A716B....tar.md5
CP_A716B....tar.md5
CSC_OJM_A716B....tar.md5
HOME_CSC_OJM_A716B....tar.md5
Very roughly:
BL bootloader / low-level firmware
AP Android, boot, recovery, system partitions
CP modem/baseband
CSC regional/carrier configuration
The AP archive was enormous because it contained things like:
boot.img.lz4
recovery.img.lz4
dtbo.img.lz4
super.img.lz4
metadata.img.lz4
vbmeta.img.lz4
...
super.img is where modern Android keeps the dynamic system partitions, and on this firmware it was going to become the villain of the story.
But we hadn’t met yet.
Magisk patches AP
I installed Magisk 30.7 and gave it the complete Samsung AP archive.
Magisk generated:
magisk_patched-30700_....tar
One nice detail became important later.
The original Samsung archive contained:
boot.img.lz4
vbmeta.img.lz4
while Magisk’s patched archive contained:
boot.img
vbmeta.img
alongside other images that remained .lz4 compressed.
That is perfectly valid.
It would also expose a bug in the flashing tool we were about to use.
Surely flashing Samsung firmware from Linux is solved by now
Samsung’s official flashing tool is Odin.
Samsung does not officially provide Odin for Linux, but there are open-source implementations.
I started with Odin4.
It built after a couple of modern Linux annoyances involving Qt and the compiler toolchain, and eventually I had:
odin4 version 7.3.0-a46321b
The phone appeared normally over USB in Download Mode:
04e8:685d Samsung Electronics
Odin4 found the right USB interface and endpoints.
Then came the encouraging bit:
Starting handshake
Handshake successful.
Excellent.
Followed immediately by the less encouraging bit:
USB bulk read failed
LIBUSB_ERROR_TIMEOUT (-7)
Failed to begin session.
I stopped ModemManager.
I checked USB interfaces.
I experimented with the cdc_acm driver.
I re-entered Download Mode.
Same result.
The USB connection was clearly working — Odin4 could see the phone and complete its initial handshake — but it couldn’t establish the actual Odin flashing session.
At some point you have to stop blaming the cable.
Enter Thor
The next candidate was Thor, another open-source implementation of Samsung’s Odin protocol.
Thor is written in .NET.
I launched it:
sudo ./Thor-Linux
and got:
Welcome to Thor Shell v1.1.0!
Then:
connect
Response:
Successfully connected to the device!
And finally:
begin odin
Response:
Successfully began an Odin session!
That was interesting.
Thor had just succeeded at precisely the point Odin4 repeatedly failed.
So suddenly a lot of possible causes disappeared:
Phone USB hardware ✓
Cable ✓
Linux USB stack ✓
Samsung Download Mode ✓
Odin protocol possible ✓
Odin4 on this device ¯\_(ツ)_/¯
I thought we were ready to flash.
Thor had other ideas.
Bug number one: apparently my storage technology is too modern
Samsung devices contain a PIT, or Partition Information Table.
Thor can retrieve it:
printPit
So I did.
Thor crashed.
System.IndexOutOfRangeException
Index was outside the bounds of the array.
Excellent.
Fortunately:
dumpPit /home/omar/a71.pit
worked.
The resulting PIT was valid and Heimdall could parse it.
One entry revealed an important clue:
Device Type: 8
The A71 uses UFS storage.
I went into Thor’s source.
The PrintPIT code contained lookups roughly like this:
mapper.DeviceType[entry.DeviceType + 1]
The mapping array simply didn’t contain every value a modern Samsung PIT might return.
So rather than handling an unknown type gracefully, Thor walked off the end of its array.
The project already had the beginnings of a safe lookup helper.
I changed it to:
public static string GetMapping(this string[] array, int index)
=> index < 0 || index >= array.Length
? "Unknown"
: array[index];
and used that for the PIT mappings.
Rebuild.
Run Thor.
printPit
And this time:
it worked.
That was bug number one.
Bug number two: .lz4
Now for the actual firmware.
Thor’s:
flashTar
command is supposed to inspect Samsung’s TAR files, match each contained image with the corresponding PIT partition, and present a list of partitions to flash.
Instead I got:
Cannot show an empty selection prompt.
Please call the AddChoice() method to configure the prompt.
An empty list.
Back to the source code.
Thor was doing essentially this:
x.FileName == entry.Name
That looks reasonable until you compare the files.
Samsung’s firmware archive:
boot.img.lz4
recovery.img.lz4
super.img.lz4
Samsung’s PIT:
boot.img
recovery.img
super.img
So:
super.img.lz4 != super.img
Thor consequently concluded there was nothing to flash.
The fix was tiny:
var flashName = entry.Name.EndsWith(
".lz4",
StringComparison.OrdinalIgnoreCase)
? entry.Name[..^4]
: entry.Name;
var pitEntry = data.Entries.FirstOrDefault(
x => string.Equals(
x.FileName,
flashName,
StringComparison.OrdinalIgnoreCase));
Rebuild again.
Run again.
And suddenly Thor understood the firmware.
It correctly mapped things like:
recovery.img.lz4 -> RECOVERY
dtbo.img.lz4 -> DTBO
super.img.lz4 -> SUPER
metadata.img.lz4 -> METADATA
vbmeta.img -> VBMETA
vbmeta_samsung.img.lz4 -> VBMETA_SAMSUNG
misc.bin.lz4 -> MISC
boot.img -> BOOT
Most importantly:
Magisk boot.img -> BOOT
We now had an open-source Samsung flashing tool, running on Linux, correctly understanding the firmware.
So naturally this was when I decided to press the button.
22 partitions and one very large problem
The final flash consisted of the original:
BL
CP
CSC
and the Magisk-patched:
AP
Thor selected 22 partitions in total.
The flashing process began beautifully.
Partition after partition completed.
Then it reached:
SUPER
SUPER was approximately 6.8 GiB and Thor divided it into 233 transfer sequences.
I watched:
1 / 233
10 / 233
30 / 233
50 / 233
...
64 / 233
And then:
Failed to bulk write:
Cannot allocate memory (12)
Thor died.
Approximately 1.9 GiB of a 6.8 GiB Android system partition had been written.
That is not an ideal place to stop.
At this point Android was unquestionably broken.
“Have I bricked it?”
The phone eventually displayed Samsung’s cheerful:
An error has occurred while updating the device software.
But this distinction matters:
Download Mode still worked.
The low-level bootloader was alive.
The phone wasn’t hard-bricked. Its Android installation was simply mangled.
A soft brick is mostly a scary-looking invitation to flash it again.
Still, seeing Samsung’s giant software-error screen immediately after partially overwriting SUPER does concentrate the mind wonderfully.
The laptop had plenty of RAM
The error said:
Cannot allocate memory
My laptop had roughly:
19 GiB RAM
~12 GiB available
So this clearly wasn’t ordinary process memory exhaustion.
The errno was coming from USB:
ENOMEM (12)
That led me to Linux’s usbfs configuration:
cat /sys/module/usbcore/parameters/usbfs_memory_mb
Output:
16
16 MB.
Linux limits how much memory usbfs may allocate for USB transfer buffers.
Thor was performing large sustained USB transfers while flashing a 6.8 GiB image.
So I raised the limit:
echo 256 | sudo tee \
/sys/module/usbcore/parameters/usbfs_memory_mb
Verification:
cat /sys/module/usbcore/parameters/usbfs_memory_mb
256
I briefly considered modifying Thor’s transfer packet size too.
Then decided against it.
One variable at a time.
That decision turned out to be useful, because it let me know exactly what fixed the problem.
Here we go again
I forced the phone back into Download Mode.
Started Thor.
Connected.
Started another Odin session.
Selected the entire firmware again.
And flashed it from the beginning.
Eventually:
SUPER
appeared.
The previous failure point approached.
50 / 233
60 / 233
64 / 233
Then:
65
66
67
...
Beautiful.
100
150
200
...
233 / 233
All 6.8 GiB transferred.
Thor then completed:
METADATA
VBMETA
VBMETA_SAMSUNG
MISC
BOOT
RADIO
CP_DEBUG
Everything reached 100%.
The second flash had worked.
The only change:
usbfs_memory_mb: 16 -> 256
That was probably my favourite discovery of the entire exercise.
Of course, Thor had one last surprise
After the successful flash I ran:
end
Thor timed out.
Failed to bulk read:
Connection timed out (110)
At this stage I’d spent enough time inside Thor’s source code that looking at it again seemed perfectly normal.
The generic end path attempted a shutdown operation.
Another command existed:
reboot normal
and its implementation performed:
odin.EndSession();
odin.Reboot();
That looked much closer to what I wanted.
I established a clean Odin session again and ran:
reboot normal
The phone rebooted.
Lesson number three:
On this phone, with this Thor version,
reboot normalwas the clean way out.
Android Recovery
Samsung Recovery appeared.
/data needed to be recreated, so I performed:
Factory data reset
Reboot system now
Then came the long Samsung boot logo.
And finally:
Android.
Alive.
At this point I had transformed a perfectly functional phone into a broken phone, repaired the software I was using to repair it, broken the phone harder, repaired Linux, repaired the phone again, and ended up approximately where I started.
Except now I had root.
Progress.
uid=0(root)
Magisk completed its additional setup and rebooted once.
Then:
adb shell
su
id
returned:
uid=0(root)
Finally.
And remember why we did all of this?
I wanted to know whether the storage and battery were healthy.
So now I could simply ask them.
Asking the UFS controller how close it is to death
The A71’s internal flash identifies as:
SAMSUNG
KM8V8001JM-B813
Revision 0003
The kernel exposes UFS health descriptors directly.
On this device:
cat /sys/devices/platform/13600000.ufs/health_descriptor/life_time_estimation_a
cat /sys/devices/platform/13600000.ufs/health_descriptor/life_time_estimation_b
cat /sys/devices/platform/13600000.ufs/health_descriptor/eol_info
returned:
0x04
0x04
0x01
The lifetime estimates are intentionally coarse.
0x04 corresponds to roughly 30–40% of the rated endurance having been consumed.
The pre-EOL value:
0x01
means:
NORMAL
So the storage was nowhere near the end of its rated life.
Other UFS status values were equally boring:
exception_event_status = 0
bkops_status = 0
ffu_status = 0
purge_status = 0
Kernel logs contained no suspicious UFS resets, timeouts or I/O failures.
Excellent.
Let’s write a gigabyte to it anyway
For good measure:
cd /data/local/tmp
dd if=/dev/zero \
of=ufs_test.bin \
bs=4M count=256 \
conv=fsync
Result:
~354 MB/s write
Then:
dd if=ufs_test.bin \
of=/dev/null \
bs=4M
gave around:
~718 MB/s read
The read result can be influenced by caching, so I wouldn’t publish it as a storage benchmark.
The more meaningful result was:
1 GiB written
1 GiB read
0 I/O errors
The UFS looked healthy.
And the supposedly dying battery?
Now root exposed Samsung’s internal battery information.
Two different places agreed:
mSavedBatteryAsoc = 90
fg_asoc = 90
Samsung’s CISD telemetry also said:
ASOC = 90
So the fuel gauge itself considered the battery to be around 90% state of health.
Other interesting historical values included:
FULL_CNT: 510
HIGH_SWELLING_CNT: 15
BATT_THM_MAX: 505
CHG_THM_MAX: 566
Those temperature values correspond approximately to:
Battery maximum: 50.5°C
Charging circuitry max: 56.6°C
The phone had lived a life.
But it wasn’t dying.
Current Android health status was:
Good
So after everything we’d just done, the answer to my original question was:
Yes. The battery and storage are actually fine.
I could probably have stopped here.
I did not stop here.
Now that I have root, what’s all this rubbish?
A fresh Samsung installation contains a frankly impressive amount of software that I have absolutely no intention of using.
Bixby.
Samsung Free.
AR Emoji.
AR Zone.
Samsung Pay.
Samsung Pass.
Samsung Cloud.
Game Launcher.
Game Optimizing Service.
Facebook services.
Netflix activation.
Microsoft integration.
OneDrive.
Google Photos.
Chrome.
Google TV.
Carrier recommendation services.
And innumerable packages whose purposes range from “legitimate platform component” to “why does this exist?”
Rather than deleting system APKs, I used Android’s package manager:
pm disable-user --user 0 PACKAGE
This is important.
Disabled is reversible.
For example:
pm enable --user 0 PACKAGE
brings it back.
I worked in batches and rebooted between them.
After each round I tested:
Wi-Fi
Bluetooth
camera
fingerprint
Settings
Play Store
telephony-related components
Eventually I’d disabled close to a hundred packages while leaving Android’s actual core functionality intact.
Samsung Dressroom: a cautionary tale
One package was called:
com.samsung.android.app.dressroom
“Dressroom.”
Obviously essential operating-system infrastructure.
I disabled it.
A little later I noticed:
Settings
-> Wallpaper and style
no longer worked.
The button was there.
It received the tap animation.
And then:
Nothing.
So I started restoring the obvious suspects:
com.samsung.android.dynamiclock
com.samsung.android.themecenter
com.samsung.android.themestore
Nope.
Wallpaper settings were still dead.
Eventually:
pm enable --user 0 \
com.samsung.android.app.dressroom
And instantly:
Wallpaper & Style came back.
Of course it did.
We subsequently disabled:
dynamiclock
themecenter
themestore
again.
Wallpaper continued working.
The essential component was:
com.samsung.android.app.dressroom
There is probably a Samsung engineer somewhere who can explain this package name.
I don’t want to know.
Rule of Android debloating: package names are hints, not documentation.
Samsung had one final suggestion
After several rounds of debloating, a notification appeared:
Get recommended apps
Tap here to install apps from your service provider.
No.
Using:
dumpsys notification --noredact
revealed the responsible application:
com.samsung.android.app.omcagent
So:
pm disable-user --user 0 \
com.samsung.android.app.omcagent
The notification disappeared immediately.
The phone and I had reached an understanding.
Making the debloat reproducible
I eventually consolidated everything into:
/data/local/tmp/debloat-packages.txt
/data/local/tmp/debloat.sh
/data/local/tmp/restore.sh
The package list is the canonical definition of what I don’t want.
The basic debloat script is deliberately boring:
#!/system/bin/sh
LIST="/data/local/tmp/debloat-packages.txt"
while IFS= read -r pkg; do
[ -z "$pkg" ] && continue
if pm path "$pkg" >/dev/null 2>&1; then
echo "DISABLE: $pkg"
pm disable-user --user 0 "$pkg"
fi
done < "$LIST"
restore.sh lets me selectively turn packages back on if I discover another “Dressroom.”
I keep copies on the laptop too.
That means after a future firmware update I can reproduce the configuration rather than spending another evening rediscovering every Samsung package.
No Google account required
I also wanted to see how pleasant Android could be without signing the phone into Google.
Quite pleasant, as it turns out.
I installed applications directly from their official distributions, including:
WhatsApp
Telegram X
DuckDuckGo
Termux
FUTO Keyboard
ADB installation is simply:
adb install application.apk
and updates can generally be installed with:
adb install -r application.apk
as long as the package is signed by the same developer key.
Termux became particularly useful because, with Magisk installed:
su
gives me a root shell directly on the phone.
No laptop required.
What actually fixed what
If I had to repeat this entire process tomorrow, these are the details I would care about.
Thor could establish an Odin session when Odin4 couldn’t
On this particular:
SM-A716B
A716BXXS9FXA1
Odin4 completed its initial USB handshake but repeatedly timed out beginning the flashing session.
Thor worked.
That doesn’t mean Odin4 is universally broken or Thor universally superior. It means this specific combination worked for me.
Thor 1.1.0 needed two source fixes
First, PIT output needed bounds-safe mapping for newer values such as the A71’s UFS device type.
Second, flashTar needed to normalize:
foo.img.lz4
to:
foo.img
before matching TAR members against PIT filenames.
Linux usbfs memory mattered
This:
cat /sys/module/usbcore/parameters/usbfs_memory_mb
initially returned:
16
and a 6.8 GiB SUPER flash repeatedly died with USB ENOMEM.
After:
echo 256 | sudo tee \
/sys/module/usbcore/parameters/usbfs_memory_mb
the complete transfer succeeded.
Don’t panic just because Android is dead
If Samsung Download Mode remains accessible, a failed Android flash is not necessarily a brick.
Don’t delete system APKs while debloating
Use:
pm disable-user --user 0 PACKAGE
first.
com.samsung.android.app.dressroom is Exhibit A.
The final machine
What started as an old Galaxy A71 that Samsung had once complained was deteriorating ended up as:
Samsung Galaxy A71 5G
Android 13
Bootloader unlocked
Magisk 30.7
Root access
Storage:
Samsung KM8V8001JM-B813 UFS
30–40% rated endurance consumed
Pre-EOL: Normal
No observed UFS errors
~354 MB/s sequential test write
Battery:
Samsung ASOC: ~90%
Android health: Good
Software:
Aggressively debloated
No Samsung account
No Google account required
Termux root shell
Reproducible debloat scripts
The phone is now cleaner, more inspectable and arguably more useful to me than it was when Samsung shipped it.
The absurd summary
I wanted to answer:
Is the battery in this phone still okay?
The procedure I eventually used was:
- Unlock the bootloader.
- Factory-reset the phone.
- Download 8 GB of Samsung firmware.
- Patch Android with Magisk.
- Build an open-source Odin client.
- Discover that it doesn’t work with the phone.
- Switch to another Odin client.
- Discover two bugs in that one.
- Patch its source code.
- Recompile it.
- Start flashing the phone.
- Partially overwrite Android.
- Crash the flashing tool.
- Soft-brick the phone.
- Debug Linux’s USB subsystem.
- Increase the kernel’s usbfs memory allocation.
- Flash the entire phone again.
- Find another Odin-session bug.
- Read more source code.
- Recover Android.
- Factory-reset it again.
- Install Magisk.
- Obtain root.
- Read the battery health value.
Answer:
90%
Completely sensible use of an evening.
And this is why I like Linux.