Back in November 2019, Xiaomi sent some of us the Redmi Note 8 Pro through XDA, to play with and mod. The Redmi Note 8 Pro is powered by the MediaTek G90T (mt6785), a platform most of us in the custom ROM world aren’t used to.
I personally enjoy tinkering with anything and everything that can boot Linux and Android, and this device was no exception. After all, we have the OEM’s proprietary firmware, and given the recent developments of Project Treble, it shouldn’t be too hard to reverse and stitch things together right? Let’s find out.
Before anything, let’s take a moment to appreciate how pretty the device is. Sure, there are better looking devices, but I haven’t seen a white phone look this good in different lighting conditions.
MediaTek and the Custom ROM world
Over the last decade, MediaTek devices have earned an awful reputation where many believe that the devices run hot, waste battery, are slow and are not “developer-friendly”. You see, since 2010, many manufacturers have been creating “budget” smartphones using MediaTek platforms that are often rebrands of generic cheap Chinese phones. In addition to this, MediaTek does not opensource it’s BSP (Board Support Package) at all, unlike Qualcomm (in the form of CodeAurora Forums, or CAF for short). This often compels people buying cheap devices today, to opt for a Qualcomm equivalent. In the last few years however, commercial MTK devices have been getting better and the Redmi Note 8 Pro is no exception. Xiaomi was nice enough to opensource the Kernel (and Kernel modules), which makes a crucial part of development in the later stages. Let’s find out how such a strange machine can be so unique, yet so familiar.
So, our first step would be to unlock the bootloader (of course), identify the product and figure out what code and firmware we already have. Xiaomi has a fancy unlock process in place, which involves binding the device to a Mi Account and applying for an unlock. It normally takes a week to unlock the bootloader. During this week, I started looking into the kernel sources and MIUI dumps to see how we could get the device to run cool stuff. The Redmi Note 8 Pro has 2 variants, namely “begonia” and “begoniain”. “begoniain” is simply the Indian variant and lacks NFC hardware. This wasn’t a big deal, given they used the same kernel and their software was mostly interchangeable.
Now, as per tradition, I decided to work on getting TWRP up and running first. Team Win Recovery Project (or TWRP for short) is a Custom Android Recovery that provides for a friendly touch interface and allows users to install OTA packages, make and restore full device backups (famously known as “nandroid” backups). These OTA packages give us the ability to run scripts before and after writing partitions as necessary, making the user’s life a lot easier. An Android Recovery contains it’s own kernel, thus killing two birds with one stone. Though the kernel didn’t compile out of the box, it was by no means “incomplete”. It was missing a few things, but it was pretty simple to figure it out and patch up. Within 2–3 days of unlocking my device’s bootloader, I had the kernel compiling and TWRP booting!
Fast-forward a few days, I start working on getting POSP (an AOSP derivative) to boot. Why POSP? Why not Lineage or something more conventional? I happen to be the Founder and Lead of POSP and let’s be real, there is pretty much no difference when it comes to device porting; Everything is AOSP and proprietary vendor services take care of most issues thanks to Treble. Within no time, I managed to reach the boot animation.
Buuuut, that’s where the fun ends. For now.
A few days later, I encounter multiple hard-bricks while experimenting. A “hard-brick” is slang used to indicate a catastrophic software failure that, more often than not, cannot be fixed with conventional tools. While the reason for the bricks were probably stupid, it helps us understand how the device’s download mode (aka EDL) work and will be of great help at a later point.
Stuck in BootROM download
To understand what’s happening here, let us understand a conventional MediaTek platform boot sequence and partition layout on the Redmi Note 8 Pro.
Checking /dev/block/platform/bootdevice/by-name, we can see what we’re working with:
We can see, sda and sdb contain the preloader(s) and sdc contains pretty much everything else. The boot sequence is rather straightforward. While some specifics may not be completely correct (remember, all this is proprietary and the following were determined by observing behavior, logs and from other blogs):
- BootROM (BROM) — At the beginning of SoC init, once the CPU has initialized itself, the internal SRAM controller pushes a jump instruction to the address of BootROM, which is a small program that will initialize the flash storage and do some basic UART setup. The UART interface is still not ready yet. BootROM will check storage and try to load a working Preloader from the flash storage. BootROM may also implement security to prevent unauthorized alteration to the storage.
- Preloader (PL) — This program resides at the start of the emmc/ufs flash device. It relies on various things including but not limited to timers, clocks, watchdogs, UART, GPIO, USB, etc and will setup these in it’s init instructions. Preloader executes after BootROM has initialized the storage and validated security, meaning it does not need to perform security checks and has full access to the flash (Note: this information will come handy at a later point). PL also initializes SecureLib which is responsible for Secure Boot setup. After it is done bringing up the platform, it will select a boot mode (usually NORMAL_BOOT) and will load the Android BootLoader.
If during PL init, the Volume Up (+) button is held, Preloader will abort loading and hand control back to BROM, signaling it to load a low-level “Emergency Download” (EDL) mode. This interrupt is only listened for before SecureLib is setup.
- Android BootLoader, the LittleKernel (LK) — Now, a minimized kernel (LK) and an OEM customized version of ABOOT (Android BootLoader) will spin up to start the traditional Android boot process. This stage also loads the logo data and displays the boot-logo, before a full boot-image starts booting Linux and takes over control. The LK is responsible for verifying several partitions to make sure they are signed with a particular key. If it fails to verify a partition, it will not continue. ABOOT will check Android Verified Boot (AVB) as necessary and conditionally continue bootup. LK also listens to the device keys to load recovery or fastboot download. On MIUI LK images, Volume Up (+) loads recovery, while Volume Down (-) loads fastboot.
- Linux boot — Now Android can continue booting like any other device! The selected boot image (boot/recovery/equivalent) is loaded, DTOs are merged and Linux begins to boot :D
Okay, so how did you get stuck in BROM Download? What next?
Glad you asked!
You see, if LK fails to find a working boot-image, it will not continue and reboot the device. Effectively giving you no display output and a blinking LED. Often, draining the battery (equivalent of unplugging the battery) and booting the device with Volume Up (+) helped boot into recovery (given the kernel image itself was at fault).
There was one more problem. A bigger problem. Before moving forward, you may wanna check out this article about DTOs (Device Tree Overlays):
If DTOs failed to merge, LK could not even init. For whatever reason on this device, LK tries to use boot’s DTB (Device Tree Blob) and DTBO image (Device Tree Blob Overlay) for itself*. Meaning, if you were to break DTB and DTBO merging or their compatibility with this proprietary LK, you would be left with a broken device. A device in such a non-functional state is commonly referred to as a “hard-brick” (The device is no better than a brick! Cannot be fixed with conventional software). Oh and, tripping AVB would hard-brick you too!
Over time, a hard-bricked Redmi Note 8 Pro would run out it’s battery pretty quickly, since the device would attempt to boot, fail and reboot. You would also notice the device being a little warm while it does this. There is no other indication of life other than a bright LED that blinks every few seconds when plugged into a charger or USB, and a UART connection when Volume Up (+) is held long enough.
* From observations, this should have been fixed in MIUI A10 (Android 10). We however diverged from official firmware, as you will see below. LK images on MIUI A10 seem to contain the kernel dtb too.
Whats the big deal? Just use SP Flash Tools in BROM download, right?
Wrong. For those unaware, SP Flash Tools, short for SmartPhone Flash Tool is a tool that MediaTek distributes that allows flashing the OEM firmware back onto a MediaTek device, in case something goes wrong. Now, in this “hard-brick” condition, the device is able to enter the BROM “emergency-download” mode (EDL, for short). If you remember, BROM may implement security to prevent unauthorized modification to the device.
Most manufacturers implement very basic security; there are 2 main BROM security implementations:
- SLA (Serial Link Authorization)
- DAA (Download Agent Authorization)
A MediaTek device can have none, either or both. Usually a slightly modified version of the flash tool which contains a few secrets is enough to let anyone re-flash the device. Let’s quickly understand what these implementations are like and how they differ.
BROM exposes UART to communicate. In both cases, the device will generate a few random bytes which must be “decrypted” or simply processed to create a new string. If BROM validates the string, it’ll allow the host to issue many more instructions without errors, such as jumping to addresses or writing partitions. The difference between the two is, in SLA, BootROM performs the checks and in DAA, Download Agent (DA) performs the check. Download Agent is loaded by SP Flash Tool. On devices that implement SLA, you cannot load a DA file without completing the SLA challenge. On devices that implement DAA, the challenge is done by DA and a modified DA file is enough to bypass security (That is, assuming you manage to reverse things or have the BSP).
This device had SLA. What’s worse? Server side SLA.
Xiaomi has special accounts (called Mi Authorized Accounts) that are given to service centers for repairing devices. These accounts are capable of requesting Authorization tokens to unlock BROM download on MediaTek devices (and other EDL equivalents for Qualcomm devices). Something that can be very easily fixed by a consumer and/or developer, is locked to service centers.
It gets worse. Many service centers do not know how to properly EDL and end up doing full motherboard replacements. While I understand this is for security of their devices, it would be nice to allow a user to fix his own device. After all, they tie your device to your Mi Account, which should help making sure that you are flashing things on your own device.
I did manage to flash the device once on my machine by paying some shady person on the Internet to TeamViewer into my system and authorize the flash process. It was a well controlled and Wireshark monitored session, but it yielded no usable data unfortunately.
The device generates 16 bytes of data (red) and sends it to the server. The server checks if your account has authorization and returns 256 bytes of data. If the data is correct, BROM continues. Else it traps itself in an infinite loop, until it times-out due to no-command and reboots.
For completeness sake, I tried to make a Python Script that basically simulates the exact same serial communication between the host and device as SP Flash Tools, and tried to ignore the entire SLA (qualify_host) part, but BROM simply rejected any further commands. You can find the script here:
After going through multiple shady services with variable level of success and countless service center visits, teaching them how to enter BROM download so they can fix it using their special accounts, I was able to get my device running again.
Back to development!
I would never let these petty roadblocks keep me from running my own code on this shiny device. Coming back to development, remember how I mentioned LK verifies signed images? Well, turns out DTBO was signed too. My compiled DTBO images yielded a brick-like situation. This was luckily not a hard-brick, since the DTO was still valid and LK was able to load. After close inspection, we can see that the the DTBO contains 2 certificates:
My earlier builds reached the animation since I was using MIUI dtbo, which was identical to the opensource kernel DTO sources. DTO however changed on later MIUI A9 (Android 9) images, which could potentially be catastrophic. So first, we had to compile our own working DTBO images.
After some magic with dd, I extracted the 2 certs that were appended.
A simple Python Script and a few hex-diffs later, we had a dtbo image that looked more like the one on MIUI. Let’s see if it runs.
(What’s the worse that could happen right? Another service center visit, perhaps! Those guys must be sick of me, by now :P)
This point on, getting the device to boot was fairly straightforward. It was a matter of reading logs and figuring out why something doesn’t work. However, it was still pretty dangerous to develop and modify the device, given the risk that one little mistake could cost a lot of time and money to fix.
A Ray Of Hope
Back in (late) November 2019, a firmware surfaced that was labelled to be begonia’s “factory firmware”. You see, every OEM receives a BSP for their platform of choice from the SoC manufacturer. Usually, the OEM will boot a clean version of this BSP on their hardware to get everything working, before the product team can start porting out the “skin” of Android that they advertise and ship their products with. This clean-version of the BSP build is often referred to as the “final factory firmware”. This happened to be just that. It was a userdebug build (you can read more about build types here), with lots of fun options enabled, that you would never see on a production device. You know, the kind of stuff that lets you run pretty much anything, dump memory and registers, have a debug console connection, and the kind of stuff that makes your device invincible. On closer inspection of strings and unix-paths in preloader, bootloader and kernel, it was clear that this build was compiled on the very same system as the newest MIUI A9 builds.
Remember the “Note” in the Preloader part of the boot sequence? No? Well, here it is again:
Preloader executes after BootROM has initialized the storage and validated security, meaning it does not need to perform security checks and has full access to the flash storage.
Well, this Preloader was compiled with download.c and was setting up UART on bootup, without any key-presses/interrupts.
To verify, I used a slightly modified version of my BROM script to see if it really was a download connection, by trying to perform a handshake.
Credit where due, a man by the name of “Nikolay Molev” from 4PDA was brave enough to try out the entire firmware, and observed that he was able to use SP Flash Tool without any fancy Mi Account. I simply tried the Preloader and LK from that build as suggested by him to confirm this was in fact real.
Current State of Things
With a Preloader that has a working UART download connection, we have to make sure that we’re able to run newer software from Xiaomi on this old leaked Preloader. The thing is, the newer Android 10 BootLoaders (LK images) do not run on this PL. We are stuck with Factory and MIUI Android 9 bootloaders. And, the new kernel sources do not run on the old BootLoader for some odd reason (possibly due to DTO differences). For newer software to run, we need the new kernel running, or at least it’s new drivers.
So, I sat down and started tinkering with the existing working kernel to boot the new Android 10 vendor. You can see the result of my first few attempts here :D
Sitting and debugging through porting of drivers, one at a time, took about a week but was well worth it! I needed a mixed combination of Android 9 and Android 10 firmware images, which is now commonly referred to as the Begonia CFW Project. This allows us to ship unified builds for begonia and begoniain while still being completely functional!
The kernel source and device configurations for the same can be found here:
Fix, fix and fix!
Camera caused me the most trouble, since it failed to auto-focus no matter what. Turns out, that many services on the device were failing to properly read and write NV, and this trickled down to the camera services failing to use the AF driver. This however made me read through countless leaked technical documents that were in Chinese to understand how to properly enable logging, MediaTek’s camera-twin architecture and it’s dependencies.
The Audio stack also was pretty interesting, which was the main motivation of the CFW. On first few boots, audio was broken and the device would reboot itself due to a WDT in the audio drivers. For audio to work and the device to stop rebooting, I had to port the new audio drivers, and for the new audio drivers to work, I had to use the MIUI Android 10 audio_dsp.img. Further, I had to use scp.img from the newer firmwares too, to fix a few sensors. (scp.img is supposedly the data struct for the MediaTek sensorHub driver)
The CFW also contains the Factory Preloader and LK to make sure the user is safe when installing such Frankenstein firmwares.
Other fun fixing experiences include fixing of IMS (Known popularly for VoLTE). What was deemed impossible by many actually turned out to be very very easy. Properly porting the prebuilt MediaTek ImsService and it’s dependencies managed to fix the IMS stack too! I did need to reverse out a few changes so WiFi StaState callbacks could be registered by the service. This would fix random crashes of the ImsService app. You can find the patches here.
We finally have a happy device which we have (almost) full control over, running an AOSP/AOSP-derivative build. It’s safe to play with software and everything works! Without Xiaomi’s kernel source release, this would never have been possible. This device has been quite a roller-coaster for me and has taught me a lot about MediaTek internals, security and architecture. But, the development doesn’t stop there. Who knows what else this device can run? 😉
This device is now my daily-driver and I absolutely love using it! All the code is linked above.
Credits and Thanks
I’d like to thank everyone who contributed in the development, testing and debugging of this project. Here’s them in no particular order:
- Zinadin Zidan (ZIDAN44)
- Elias (TheMalachite)
- Aayush Gupta (theimpulsion1)
- Harshit Jain (dev_harsh1998)
- Sagar Sorathiya
- Sahil Sonar
Special thanks to the GSI community, who had been working on innovative fixes on their project, way before we even had usable builds :D
Most importantly, thanks to Xiaomi India and XDA for sending us the devices!
- Glossary -
ABOOT = AndroidBoot (or, Android BootLoader)
ARB = Anti-RollBack Protection
AVB = Android Verified Boot
BROM = BootROM
BSP = Board Support Package (usually proprietary)
DT = Device Tree
DTB = Device Tree Blob
DTBO = Device Tree Blob Overlay
DTO = Device Tree Overlay
DTS = Device Tree Source
EDL = Emergency Download
LK = LittleKernel
PL = Preloader
UART = Universal Asynchronous Receiver-Transmitter
WDT = WatchDog Timer