Sunday, December 18, 2022

The Air32F103


Its no surprise that Bluepills, such as they were, a smoking sub $2 deal would dry up eventually, taken over by clones, some subtle, some overt. With supply issues still plaguing ST parts for hobbyists, what about these cheap Chinese clone parts? Lets dive into the Air32F103.


I bought 5 of these $1.90 boards. They look like this:

Some notables vs a stock bluepill:

1) Castellated pins with flat underside for use as a module

2) 3 LEDs R/G/B (vs just the G for a bluepill)

3) Clone of ST's peripherals

4) over clockable to 256Mhz (spec is 216Mhz) (original STM32F103CB is 72Mhz)

5) 32K of ram. But, via secret regs, 97K(!)

6) top and bottom debug pads, top for JLINK, bottom, legacy STLINK SWD

7) USB C vs USB mini

8) BOOT as a button vs jumper

9) 2 12bit DACs (STM32F103CB's don't have that)

10) QSPI (hidden support)

11) Undocumented crypto block from MegaHunt (includes: AES/DES/3DES/SHA/SM[1,3,4,7])


As mentioned 256Mhz is possible. One Q is how do they do that? Well, PLLMUL is a 4bit field so 8Mhz * 16 gets a max of 128Mhz. A new bit, bit28 of RCC.CFGR adds 16 to the MUL. So, 31(+1) gets 256Mhz.

So, if you do set that, the code will crash with a usage fault. There is secret code called in the ROM that flips a hidden bit to allow high speed operation (the SysFreq_Set code below)

The RCC init code from:

Here we see the 97K unlock:

Observe they flip the lock at the end and revert back to 32K sram

In my Ada code, I flip the lock but seem to fail to revert it back.. leaving 97K available.

The magic reg to allow for high speed op is as follows:

     Magic_Reg : UInt32 with Volatile, Address => System'To_Address (16#40022214#);
         --  Hidden system reg block --
         --  w/o this.. 216Mhz progs don't run (usage fault) --
         Magic_Reg := Magic_Reg or 1;

I really don't think you can misstep here, $1.90 (aliexpress price) for a board with all those features.
There are some differences that need exploration:

Have fun! To quote Hunz on value for money wrt hacking:

Disassembly backup material for the rom study
On entry
r0             0x10280000          271056896
r1             0x1                 1
r2             0x0                 0
r3             0x1
0x1fffe97c: push {r4, r5, r6, lr}
   0x1fffe97e: mov r6, r1
   0x1fffe980: mov r1, r0
   0x1fffe982: sub sp, #24
   0x1fffe984: mov r4, r3
   0x1fffe986: mov r5, r2
   0x1fffe988: movs r0, #0
   0x1fffe98a: bl 0x1fffe12c
; read 0x40021004 (reads 0)
        0x1fffe12c: ldr r3, [pc, #12] ; (0x1fffe13c)
        0x1fffe12e: ldr r2, [r3, #4]
; or in the PLLMUL
        0x1fffe130: orrs r0, r1
; clear PLLMUL field
        0x1fffe132: bic.w r2, r2, #4128768 ; 0x3f0000
; or in prior contents(why?)
        0x1fffe136: orrs r0, r2
; final PLLMUL (0x10280000) (thats +16 and 10 mult (27 * 8 = 216)
        0x1fffe138: str r0, [r3, #4]
        0x1fffe13a: bx lr
   0x1fffe98e: movs r0, #0
   0x1fffe990: bl 0x1fffe0d8
   ; read 0x40021004
        0x1fffe0d8: ldr r2, [pc, #12] ; (0x1fffe0e8)
; reads old val 0x10280000
        0x1fffe0da: ldr r1, [r2, #4]
; wipe HPRE
        0x1fffe0dc: bic.w r1, r1, #240 ; 0xf0
; or in wanted val (in this case 0)
        0x1fffe0e0: orrs r1, r0
; no change to HPRE
        0x1fffe0e2: str r1, [r2, #4]
        0x1fffe0e4: bx lr
   0x1fffe994: mov.w r0, #1024 ; 0x400
   0x1fffe998: bl 0x1fffe0f8
        0x1fffe0f8: ldr r2, [pc, #12] ; (0x1fffe108)
        0x1fffe0fa: ldr r1, [r2, #4]
; Now PPRE1 (set to the 0x4 of r0's arg of 0x400)
        0x1fffe0fc: bic.w r1, r1, #1792 ; 0x700
        0x1fffe100: orrs r1, r0
        0x1fffe102: str r1, [r2, #4]
;(gdb) x/x 0x40021004
;0x40021004: 0x10280400
        0x1fffe104: bx lr
   0x1fffe99c: movs r0, #0
   0x1fffe99e: bl 0x1fffe10c
; we arrive with r0=0
        0x1fffe10c: ldr r2, [pc, #12] ; (0x1fffe11c)
        0x1fffe10e: ldr r1, [r2, #4]
; once again 0x40021004
        0x1fffe110: bic.w r1, r1, #14336 ; 0x3800
        0x1fffe114: orr.w r0, r1, r0, lsl #3
        0x1fffe118: str r0, [r2, #4]
; 0x10280400
        0x1fffe11a: bx lr
   0x1fffe9a2: mov r0, sp
;(gdb) x/16x $r0
;0x20000fd8: 0x00000000 0x00000000 0x00000000 0x00000000
;0x20000fe8: 0x00000000 0x00000000 0x00010000 0x10280000
;0x20000ff8: 0x00000001 0x0800136d 0x200183ff 0x08001464
;0x20001008: 0x08001464 0x00000000 0x00000000 0x080002dd
   0x1fffe9a4: bl 0x1fffe004
        0x1fffe004: push {r4, r5, lr}
        0x1fffe006: ldr r2, [pc, #144] ; (0x1fffe098)
        0x1fffe008: ldr r1, [r2, #4]
; once again 0x40021004 = 0x40021004
        0x1fffe00a: ldr r3, [pc, #144] ; (0x1fffe09c)
; r3=0x7a1200
; SWS masked off (0xC)
        0x1fffe00c: ands.w r1, r1, #12
; vvv taken since after masking, r1 == 0
        0x1fffe010: beq.n 0x1fffe01a
        0x1fffe012: cmp r1, #4
        0x1fffe014: beq.n 0x1fffe01a
        0x1fffe016: cmp r1, #8
        0x1fffe018: beq.n 0x1fffe01e
; Save r3 on the stack
        0x1fffe01a: str r3, [r0, #0]
;(gdb) x/x $r0
;0x20000fd8: 0x007a1200
        0x1fffe01c: b.n 0x1fffe054
        0x1fffe01e: ldr r1, [r2, #4]
        0x1fffe020: ldr r3, [r2, #4]
        0x1fffe022: and.w r1, r1, #3932160 ; 0x3c0000
        0x1fffe026: ldr r4, [r2, #4]
        0x1fffe028: and.w r3, r3, #65536 ; 0x10000
        0x1fffe02c: lsrs r4, r4, #27
        0x1fffe02e: lsls r4, r4, #31
        0x1fffe030: beq.n 0x1fffe03c
        0x1fffe032: movs r4, #16
        0x1fffe034: orr.w r1, r4, r1, lsr #18
        0x1fffe038: adds r1, r1, #1
        0x1fffe03a: b.n 0x1fffe042
        0x1fffe03c: movs r4, #2
        0x1fffe03e: add.w r1, r4, r1, lsr #18
        0x1fffe042: cbz r3, 0x1fffe04a
        0x1fffe044: ldr r3, [r2, #4]
        0x1fffe046: lsls r3, r3, #14
        0x1fffe048: bpl.n 0x1fffe04e
        0x1fffe04a: ldr r3, [pc, #84] ; (0x1fffe0a0)
        0x1fffe04c: b.n 0x1fffe050
        0x1fffe04e: ldr r3, [pc, #76] ; (0x1fffe09c)
        0x1fffe050: muls r1, r3
        0x1fffe052: str r1, [r0, #0]
; ^^^ from 0x1c above
; ld 0x40021004 again...
        0x1fffe054: ldr r1, [r2, #4]
        0x1fffe056: movs r3, #15
;??? why
        0x1fffe058: and.w r1, r3, r1, lsr #4
; HPRE now in r1 (r1 >> 4) & 0xF
        0x1fffe05c: ldr r3, [pc, #68] ; (0x1fffe0a4)
; r3=0x20018050 (the secret memory)
; some sort of byte table indexed by r1
        0x1fffe05e: ldrb r4, [r3, r1]
; reads 0
        0x1fffe060: ldr r1, [r0, #0]
; read back tos (0x7a1200)
; this is a shift by 0 (r4 == 0)
        0x1fffe062: lsrs r1, r4
; write back tos+4 same 0 shifted# 0x7a1200
        0x1fffe064: str r1, [r0, #4]
; read 0x40021004 again...
        0x1fffe066: ldr r4, [r2, #4]
        0x1fffe068: movs r5, #7
; get 10:8 into r4 (was 0x400 -> PPRE1=4)
        0x1fffe06a: and.w r4, r5, r4, lsr #8
; this reads 0x20018054[7:0] = 1
        0x1fffe06e: ldrb r4, [r3, r4]
; now shift 7a1200 by... r4(==1)
        0x1fffe070: lsr.w r4, r1, r4
; r4 0x3d0900
; 0x20000fd8: 0x007a1200 0x007a1200 0x003d0900 0x00000000
        0x1fffe074: str r4, [r0, #8]
; still 0x40021004 == 0x10280400
        0x1fffe076: ldr r4, [r2, #4]
; read PPRE2
        0x1fffe078: and.w r4, r5, r4, lsr #11
; r4=0, read is 0
        0x1fffe07c: ldrb r3, [r3, r4]
; r1=0x7a1200 << 0, unchanged
        0x1fffe07e: lsrs r1, r3
; after this str
; (gdb) x/8 $r0
; 0x20000fd8: 0x007a1200 0x007a1200 0x003d0900 0x007a1200
        0x1fffe080: str r1, [r0, #12]
; CFGR again...
        0x1fffe082: ldr r2, [r2, #4]
        0x1fffe084: movs r3, #3
; ADCPRE was 0
        0x1fffe086: and.w r2, r3, r2, lsr #14
        0x1fffe08a: ldr r3, [pc, #24] ; (0x1fffe0a4)
; backup magic ram to 0x2001804c...btw 0x4c = 0x08060402
        0x1fffe08c: subs r3, r3, #4
; r2=0 this should read 2
        0x1fffe08e: ldrb r2, [r3, r2]
; r2=2 div 8mhz by 2
        0x1fffe090: udiv r1, r1, r2
; r1 == 4mhz (0x3d0900)
        0x1fffe094: str r1, [r0, #16]
0x20000fd8: 0x007a1200 0x007a1200 0x003d0900 0x007a1200
0x20000fe8: 0x003d0900 0x00000000 0x00010000 0x10280000
        0x1fffe096: pop {r4, r5, pc}
; 0x40022000 secret peripheral
   0x1fffe9a8: ldr r0, [pc, #40] ; (0x1fffe9d4)
; read 0x40022214 (== 0x00034069)
   0x1fffe9aa: ldr.w r1, [r0, #532] ; 0x214
   0x1fffe9ae: movw r2, #49159 ; 0xc007
; r1 now becomes 0x30068
   0x1fffe9b2: bics r1, r2
; r6 (==1) arg save from entry
   0x1fffe9b4: and.w r2, r6, #7
; r2 is now 1
; which makes r1=0x30069
   0x1fffe9b8: orrs r1, r2
; r4 was 1 so this is 1
   0x1fffe9ba: and.w r2, r4, #1
; r1=0x30069
   0x1fffe9be: orr.w r1, r1, r2, lsl #14
; now 0x34069  r2=0 after the and
   0x1fffe9c2: and.w r2, r5, #1
; making r1 unchanged (0x34069)
   0x1fffe9c6: orr.w r1, r1, r2, lsl #15
   0x1fffe9ca: str.w r1, [r0, #532] ; 0x214
   0x1fffe9ce: add sp, #24
   0x1fffe9d0: pop {r4, r5, r6, pc}