General notes ============= The A9 is based on a system-on-a-chip namely RDA8955, made by RDA Technologies. It is a very powerful and versatile GSM/GPRS-capable microcontroller that was definitely conceived for mobile use, and probably used by various mobile phone manufacturers during the 2000's (personal opinion). Ai-Thinker sells a variant with GPS called the A9G. According to https://blog.zakkemble.net/remote-mail-notifier-and-gps-tracker , the A9G uses a GK9501 GPS receiver. Ai-Thinker distributes two packages for the A9: - GPRS_C_SDK (https://github.com/ai-thinker-open/gprs_c_sdk): contains .sh/.bat scripts to build user projects and demos. It also includes source code for some libraries (cjson, vlgl and aliyun, among others) and header files for interfacing their closed-source firmware (more on this later). - CSDTK (official link dead, mirror https://github.com/pulkin/csdtk42-linux): contains the binaries of modified versions of the GNU toolchain (gcc and binutils), a bunch of tools used to communicate with the microcontrollers namely "cooltools" and lots of configuration files, some of without clear purpose. Ai-Thinker seems to have stopped development of their repositories since 2018, where the latest version was released, except from some minor commits. So this project aims to keep maintaining the A9/A9G by creating a free (as in freedom) version of these repositories developed by the community. On the other hand, since Ai-Thinker are only distributing binaries of the GNU toolchain, this means they are violating the GNU General Public License v3 by not distribuing the source code. This has already been reported to Ai-Thinker on Github (https://github.com/Ai-Thinker-Open/GPRS_C_SDK/issues/431) and e-mail both to Ai-Thinker themselves and the Free Software Foundation, with no response so far. Two versions of the toolchain are distributed: mips-rda-elf and mips-elf. The former relies on versions of gcc and binutils released in 2017 and the latter from 2014. It is currently unclear the differences between both toolchains, although GPRS_C_SDK uses mips-elf to build a project instead of mips-rda-elf. Despite Ai-Thinker distributing so many resources, the real deal on the A9 is a debug/release pair of closed-source firmware blobs that contain the GSM/GPRS and GPS stacks, the bootloader, the C standard library implementation and the implementation of all the libraries that are provided to the user. The binaries are located at GPRS_C_SDK/platform/csdk/[debug/release]. According to mips-elf-size, the .text section of the debug version of SW_V2129_csdk.elf is 1969176 bytes (around 1.88 MiB), so there is a lot going on in there. Fortunately for us, the GPRS_C_SDK repository and also the blobs are under the MIT license, which gives us freedom to reverse-engineer them. What's more, the original authors compiled these .elf files with full debugging information, so they can be de-compiled using a reverse-engineering tool such as NSA's Ghidra while keeping the original symbol names. Using this approach, we see open-source libraries such as mbedtls and lwip have been used. A major drawback of the closed-source binary is the fact all unused code cannot be optimized away by the linker, so if the A9 contains 4 MiB of flash memory that leaves the user with 2.12 MiB. It could be worse, but it could also be better. Ghidra does a decent job at de-compiling the binaries, but sometimes it does not figure out the names of local variables and shows many "Could not recover jumptable at 0x88010a48. Too many branches" warning messages at the end of a function. Also, while Ghidra allows exporting to a C source file, it apparently only allows exporting as a *single* C source file, which might be cumbersome for such a big project (> 14 MiB, > 530,000 lines). So it is a good idea to compare the de-compiled code against the available documentation (more on this later). Memory mapping ============================== build/app/cust.ld (modified by build.sh from gprs_c_sdk/platform/compilation.cust.ld) - Flash origin at 0x88000000 - User application at 0x88000000 + 0x00240000 - RAM starts at 0x82000000 + 0x00300000 void boot_Sector(UINT32 param1) is apparently the entry point, but bcpu_main does not have any functions calling it, either. Name Addr Off Size ES Flg Lk Inf Al .spi_reg_debug a2000000 22d000 000980 00 WA 0 0 4 .boot_code 88010000 01f000 0010b0 00 AX 0 0 4 .bcpu_rotext 88011100 020100 000510 00 AX 0 0 4 .bcpu_rodata 88011610 020610 000010 00 A 0 0 4 .bcpu_rom_text a1e80000 007000 0137b0 00 AX 0 0 16 .bcpu_rom_rodata 81e937b0 01a7b0 0046f0 00 A 0 0 4 .bbsram 81980000 001000 001e00 00 WA 0 0 4 .bbsramu a1981e00 22ce00 001bf0 00 WA 0 0 4 .bbsram_globals 81986640 000640 0001b9 00 WA 0 0 4 .mailbox a1b00580 22c580 000280 00 WA 0 0 4 .bcpu_rom_version 81e97ff8 01eff8 000004 00 A 0 0 4 .bcpu_rom.crc 81e97ffc 01effc 000004 00 WA 0 0 1 .bbsram_patch_tex 81986800 020800 001070 00 AX 0 0 4 .bbsram_patch_dat 81987870 021870 000030 00 WA 0 0 4 .bbsram_patch_ucd a19878a0 0218a0 000320 00 WA 0 0 4 .bbsram_patch_ucb a1987bc0 22cbc0 000100 00 WA 0 0 4 .bcpu_sramtext 81c00680 022680 001450 00 AX 0 0 4 .bcpu_sramucdata a1c01ad0 023ad0 000040 00 WA 0 0 4 .bcpu_bss 82000a00 01fa00 000010 00 WA 0 0 4 .bcpu_flash_end 88013e70 023e70 000010 00 WA 0 0 1 .main_entry_secti 88013e80 023e80 000460 00 AX 0 0 4 .internal_rom 81e00000 001000 0051f0 00 WAX 0 0 128 .rom_entries_unca a1c000a0 22d0a0 0000f4 00 WA 0 0 4 .rom_entries_cach 81c00194 001194 000054 00 WA 0 0 4 .boot_rom_version 81e05ff8 006ff8 000004 00 A 0 0 4 .internal_rom.crc 81e05ffc 006ffc 000004 00 WA 0 0 1 .boot_sector_stru 81c00220 001194 00001c 00 WA 0 0 4 .boot_sector_relo 81c00274 001194 000004 00 WA 0 0 4 .boot_sector_stru 81c00278 001194 000004 00 WA 0 0 4 .fixptr 81c0027c 001194 000004 00 WA 0 0 4 .irqsram 81c00280 025280 0002e0 00 WAX 0 0 16 0xa1xx_xxxx and 0x81xx_xxxx could be mirrors. The MIPS R3000 allows using KUSEG (0x0000_0000) and KSEG0/1 (0x8000_0000 and 0xA000_0000, respectively) with different access permissions (user or kernel modes). So there are apparently flash memory sections on 0xa1, 0x81 and 0x88. If KUSEG/KSEG were used, then 0xa1 and 0x81 are essentially kernel-mode mirrors of each other. However, I can't tell where 0x88 sections are actually pointing. According to the documentation, the A9 features 4 MiB flash memory, and 0x8800_0000 - 0x8100_0000 > 4 MiB, so could they be two different flash chips mapped at different addresses? "rsoft: kuseg is the user segment, while the kernel segments are only accessible in kernel mode and differ in caching and translation" Compiling and linking a simple application returns undefined references to: __stack _fini _init hardware_init_hook get_mem_info hardware_hazard_hook hardware_exit_hook software_init_hook RAM executable bootloader? ========================= coolwatcher seems to be loading a RAM-based bootloader before flashing user firmware. This bootloader depends on the SoC model and they are located at csdtk42-linux/cooltools/chipgen/Modem2G/toolpool/plugins/fastpf/flash_programmers . -> This sounds like a very sensible idea considering the whole flash might be erased when downloading new software. That might be reason why a full firmare (namely _B.lod) flashing is required but the .lod only containing user firmware can be flashed afterwards to save time. coolhost UART configuration =========================== coolhost can only communicate successfully with the A9 using the following UART configuration: - Baud rate: 921600 - Data bits: 8 - Stop bits: 1 - Parity: None - Flow control: None Any other configuration simply fails, despite the A9 AT firmware being known for baud rate auto detection. Unusual compilation flags ========================= By inspecting the steps performed by gprs_c_sdk's Makefiles (remember to remove the -s from them), it definitely uses some peculiar compilation settings: - First, mips-rda-elf-gcc is not used, but mips-elf-gcc. - mips-rda-elf-gcc is based on gcc 7.1.0, whereas mips-elf-gcc is based on gcc 4.4.2. - -ffixed-t4 -ffixed-t5 -ffixed-t6 -ffixed-t7 -ffixed-s2 -ffixed-s3 -ffixed-s4 -ffixed-s5 -ffixed-s6 -ffixed-s7 -ffixed-fp. According to https://stackoverflow.com/questions/35809832/is-the-flag-ffixed-reg-always-bugged-in-gcc , the '-ffixed-reg' flag is used to "treat the register named reg as a fixed register; generated code should never refer to it (except perhaps as a stack pointer)". This means CPU registers t4-t7, s2-27 and fp are reserved for some reason. According to https://cgi.cse.unsw.edu.au/~cs3231/doc/mips.php , registers t0-t7 are used as general-purpose temporary registers, s0-s7 as general-purpose saved registers and fp is the frame pointer. - Other funky compilation flags are: -minterlink-mips16 -march=xcpu -mtune=xcpu <- Not so rare tbh, even x86 uses them -mmemcpy -mmips-tfile -nostartfiles -EL -DEL <- probably used to indicate little-endian? -mexplicit-relocs -fweb -frename-registers -mmemcpy -mmips-tfile -pipe -fwide-exec-charset=UTF-16LE -fshort-wchar <- probably used for Chinese encoding - As already shown on gprs_c_sdk/libs/utils/src, the engineers working on this might hated or simply ignored any reasonable implementation of the C standard library e.g.: newlib, instead writing their own wrappers around the unknown, proprietary real-time operating running on the A9, hence the -nostdlib -nostdinc -nodefaultlibs compilation flags. Link process =============== The link process has been split into two steps: - (mips-elf-cpp) Generate a user-specific linker script namely cust.ld by reading gprs_c_sdk/build/gpio/cust.ld and applying the following definitions on it: -DUSER_ROM_BASE=0xFFFFFFFF88000000+0x00240000 -DUSER_ROM_SIZE=0x00100000 -DUSER_RAM_BASE=0xFFFFFFFF82000000+0x00300000 -DUSER_RAM_SIZE=0x00100000 -DDUSE_BINUTILS_2_19=1 This is accomplished by using the -C and -P flags. -undef is also indicated, but I do not know what it is used for. And, IMHO, I would have used a set of predefined linker scripts with defined memory boundaries rather than throwing in a bunch of macros. - (mips-elf-ld) Perform the usual link operation. As expected when writing build scripts with bash+GNU Make, --start-group/--end-group are required since it is very hard to solve circular dependencies between libraries by hand. - An interesting note is the use of the -just-symbols flag, which allows mapping unresolved symbols by using another ELF as reference. In this case, gprs_c_sdk/platform/chip/rom/8955/lib/mem_bridge_rom_CHIP.elf is used. As opposed to the SW_V2129_csdk.elf file pair, mem_bridge_rom_CHIP.elf has been stripped from all debugging symbols, so Ghidra has a tougher time trying to guess what's going on there. It's considerably smaller than the latter (20980 bytes according to mips-elf-size), but I do not think this is actually running on the chip since it is not burned by coolwatcher or referenced by any other that I am aware of. - Ghidra shows a bunch (< 100) of functions, mainly related to USB, Ispi (regardless what that means) and UART. LOD combination =============== csdtk and gprs_c_sdk were designed around a set of proprietary tools, file formats and communication protocols. - coolwatcher and coolhost would be roughly equivalent to OpenOCD or avrdude, but GUI-based. - .lod files are very similar to .hex files, although IMHO poorly designed. More on this later. - HST (a seemingly plain old UART despite its fancy name) is used instead of JTAG, SWD or any other kind of debug interface. However, coolwatcher features mips-elf-insight, so it might be using the HST for source-level debugging. The .lod file format includes the following information: - Optional fields prefixed by '#' (maybe comments?). This is an example of the output generated by the gpio demo: #$mode=flsh_spi32m #$sectormap=(16 x 4k, 57 x 64k, 96 x 4k) #$base=0x08000000 #$spacesize=0x08000000 #$FLSH_MODEL=flsh_spi32m #$FLASH_SIZE=0x400000 #$RAM_SIZE=0x00265000 #$RAM_PHY_SIZE=0x00400000 #$CALIB_BASE=0x3FA000 #$FACT_SETTINGS_BASE=0x3FE000 #$CODE_BASE=0x00000000 #$USER_DATA_BASE=0x00361000 #$USER_DATA_SIZE=0x00099000 #$USER_BASE=0x00240000 #$USER_SIZE=0x00100000 - Starting address, prefixed by '@' e.g.: @01c000a0. Must be indicated before program information, although several starting addresses can be indicated throughout the file. See 'host_8955_flsh_spi32m_ramrun.lod' as an example. - Program data, with one 32-bit hex per line e.g.: '3c04d9ef'. This is the main reason why I disliked this file format, as it tends towards *very* long files that are difficult to open with some text editors, as opposed to .hex or .mot, where multiple words are packed into one line. Frame structure =============== CID 27 readi8 at 0x00000003 Write host: AD 00 07 FF 04 03 00 00 00 8A 72 ^ [ ] ^ ^ [ ] ^ ^ | [ ] | | [ ] | unknown | [ ] | | [ ] unknown | [ ] | | memory address? little-endian | [ ] | unknown | [ ] unknown (frame type?) | frame size without header or next byte (little-endian) header (always 0xAD) Another example: Offset 00|01|02|03|04|05|06|07|08|09|0a|0b|0c|0d|0e|0f | 00 |ad 00 35 80 14 80 3c 2d 2d 20 3c 25 73 3e 20 6f ..5...<-- <%s> o 10 |6e 20 41 72 66 63 6e 20 30 78 25 78 0a 00 4c 31 n Arfcn 0x%x..L1 20 |5f 53 59 4e 43 48 5f 4e 4f 54 5f 46 4f 55 4e 44 _SYNCH_NOT_FOUND 30 |00 6a 00 00 00 00 0a 80 00 Coolwatcher reads the following decodified syntax: "<-- on Arcfn 0x6a" Header: 0xAD Size: 0x0035 Frame type?: 0x80 Timestamp?: 0x8014 Plain ASCII data from offset 0x06 to 0x1d So it means parameters are specified in a printf-like syntax. In this case, two parameters are specified: - Null-terminated string "L1_SYNCH_NOT_FOUND" - 32-bit integer (0x%x) with value 0x0000006a - Unknown 32-bit integer with value 00 0a 80 00 Hardware registers ================== The csdtk package includes a great deal of information about low-level registers in XML format (sometimes .xmd extension is used) which could be easily parsed and laid out in a more readable format e.g.: HTML. A WIP application is being developed for that very same purpose under the hw_html directory. These files are located on csdtk/cooltools/chipgen/Modem2G/toolpool/map/8809, where 8955_hard.xml describes low-level registers and 8955_soft_pkg.xmd describes software structures and enums. Even C code is embedded into these XML files, usually under the tag, and containing macro definitions and function declarations. Moreover, this documentation even also includes comments on how these low-level registers work. coolwatcher apparently uses this information for its "Register Viewer" (which can be accessed from the "Tools" menu), showing the layout of all low-level registers, as well as their addresses and real-time values. According to this, there is a rather lengthy list of peripherals featured on the A9/RDA8955: - ABB - AIF - BB - BCPU - CALENDAR - CAMERA - CHOLK - CIPHER - CORDIC - CS0/CS1 - DEBUG_HOST/DEBUG_UART - DMA - EXCOR - FMD - GOUDA - GPIO - I2C - IOMUX - ITLV - KEYPAD - PAGE_SPY - PMU - PWM - RF - SCI - SDMMC - SEG_SCAN - SPI1/2/3 - SPI_FLASH - TCU - TIMER - UART - USBC - VITAC It is currently unknown what some of these peripherals are used for, and some of them are not even mentioned on the official specifications e.g.: CAMERA. On the other hand, the same website states 2 SPI interfaces are available, although as shown above the register viewer listed SPI1/2/3. Dual CPU? ========= BCPU and XCPU are mentioned in various places, which suggests a dual-CPU design. However, https://ai-thinker-open.github.io/GPRS_C_SDK_DOC/en/hardware/a9.html only mentions "RDA 32 bit RISC core, frequency up to 312MHz, with 4k instruction cache, 4k data cache", so it is unknown whether two CPUs are actually present.