Forcing dead 4C+ to boot (Part I)
Forcing “dead” Radxa 4C+ to boot (Part I)
Several days ago I got my hands on Radxa 4C+ which is an alternative for Raspberry PI. I tried booting it, but it didn’t work. At first I thought it was happening because of corrupted firmware or a damaged SD card, but when I checked the SD card, it was fine. I also used many different images that were provided officially by Radxa but it still failed to boot.
Getting UART output from the board
The first thing that came to my mind was to connect the board to my computer with UART (serial communication protocol). I bought TTL->USB converter from the local electronics store.
When it comes to UART communication, there are three mandatory connections that have to be created in order to be able to communicate with other devices.
Here is a diagram that shows how jumper cables should be connected:
1 | |
GND means ground, which in electronics is the flow of electrons (negatively charged little particles that run around the nucleus of the atom). RX is the endpoint that receives the data from the device. TX is the endpoint that sends the data to the device. So, if Radxa 4C+ wants to talk to my computer, it has to put the data on the databus (from the CPU) part, which will lead to the TX endpoint on the board. The same thing happens when the RX endpoint receives data from the jumper cable. It puts data on the databus, which takes received bytes to the CPU.
After that I turned on my Kali VM and used the screen command to capture UART output:
1 | |
This command opens up /dev/ttyUSB0 device and receives data from it. The UART communication rate is 1500000. It depends on the device. Mostly, 9600, 19200, 38400, 57600 and 115200 rates are used BTW, but it doesn’t mean that those work every time. If you choose a different rate, you’ll still receive data, but it’s going to be gibberish. In order to get clear information, both devices should be using the same rate.
1 | |
This was the output of the UART comm. After doing some research on Google, asking ChatGPT random stuff, and having expensive consultations with Gemini, I found out that one of the RAM modules is burned on the board. RAM modules have heatsinks mounted, and I was unable to see any visual damage that device might have.
What can be the solution?
Actually, there are two solutions. One is crazy, and another is even more crazy. Of course, I would do the craziest one, BUT in this situation I don’t have time for that.
The first solution is to make changes to the bootloader’s code in order to fix the memory issue. This is crazy because I have to rewrite a memory driver for the bootloader.
This is even crazier. I can find out the version of the chip used on the board, and I can order it from China for like $5 USD. Then I can go to the workshop and swap the chip. This will fix the problem, but I don’t want to remove the heatsink.
Alright, time to write some C code (drivers, bootloaders, and even OS are always written in C). I found out that Radxa is using U-boot bootloader for its images. Also, I can’t just pull any version of the U-boot from the repository. I need a very, VERY specific version. As I found out, I needed this:
1 | |
There is a file that needs some changes to be applied. The file is drivers/ram/rockship/sdram_rk3399.c C code file. I can’t paste the whole code here because it’s more than 3.5k lines. There is a function used for initialization of SDRAM. This is done because the bootloader has to know how much RAM is on the system and which channels are being used. By default (in code), there are two channels. This is because on the PCB itself there are two RAM modules, and each one of them is connected to the CPU via different channels.
BTW, 0 and 1 channels are being used. This is the function that is the most interesting here:
1 | |
Look at the for loop here. It iterates over the channel variable, setting its value to 0 and increasing it by one if it’s still less than 2. This means only two values are going to be used: 0 and 1. From UART output I know that the channel 0 fails initialization. So, I just need to set the channel variable’s value to 1 instead of 0 to completely skip the initialization process. This will force U-boot to skip the channel 0 and only work with the channel 1. Of course, there is a downside. I’m going to only have 2GB RAM instead of 4GB, but it’s ok. It’s still better than not being able to boot the device at all.
I tried compiling, but I couldn’t find the configuration… (To be continued)