你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

STM32MP1嵌入式Linux驱动开发指南V1.7

[复制链接]
STMCU小助手 发布时间:2022-9-21 15:49
5.1 STM32MP1启动模式
: r2 p0 ]9 l3 KSTM32MP1支持从多种设备启动,比如EMMC、SD、NAND、NOR、USB、UART等。STM32MP1内部有一段ROM来存放ST自己编写的程序,这段ROM空间是不开放给用户使用的,仅供ST存放自己的ROM代码,ROM空间如图5.1.1所示:
( {" H5 H& a, ~) k0 ^: C
  Y- J' V1 T. y; C8 p" ^( l: B" c 1776aec2429d4a75ab3cdbc2588b4023.png
0 @0 Q3 l5 O% v( F# x: p& M! K# ^+ ^$ t
图5.1.1 内部ROM空间
9 [3 g. g( ]# e& C+ n图5.1.1中CA7是Cortex-A7的缩写,可以看出A7内核有128KB的ROM空间,起始地址为0X00000000,STM32MP1上电以后会先运行这段ROM代码。STM32MP157有三个BOOT引脚:BOOT0~BOOT2,这三个BOOT引脚通过拉高/拉低来设置从哪种设备启动,正点原子STM32MP157开发板上的拨码开关就是控制这三个BOOT引脚的,大家可以打开底板原理图,找到BOOT相关原理图,如图5.1.2所示:* F5 U' X+ q; i) c5 m$ E
- [( y, X3 H8 c2 Y! M
0d87acc45c4244fba0b868adf62b8f0f.png % D) ^$ R. c5 `+ ~, ^, @
, l- M! w5 z! ]# F9 _+ z4 d
图5.1.2 BOOT原理图
/ m# Q. E; n+ Q* }$ F: M从图5.1.1可以看出,当BOOT0~BOOT1拨到“ON”的时候就会接到3.3V上,此时就是 逻辑1,拨到OFF的时候BOOT0~BOOT1就悬空(也可以外接下拉电阻),此时就是逻辑0(悬空或接地都为逻辑0),三个引脚不同电平对应的启动模式,如表5.1.1所示:9 t# s& C3 m1 J8 A1 \' J
BOOT2 BOOT1 BOOT0 启动模式 描述- \% w4 P9 w6 j! D
0 0 0 UART/USB - USART2/3/6、UART4/5/6/7/8。
2 Q" W1 c. P7 D6 E% ?7 x$ O7 G0 [  R4 @
USB接口。0 T, v8 \  q3 s) S9 o, `! g7 i
0 0 1 串行NOR 串行NOR或者QUADSPI5 h- m7 y: V0 o3 v- I
0 1 0 EMMC 连接到SDMMC2上的EMMC设备。$ s3 N7 G7 ~! ^+ M% }0 J* W/ B3 ]
0 1 1 并行NAND 连接到FMC上的并行SLC NAND
( t# p) n3 J: _" ~1 W1 0 0 MCU 启动内部的M4内核。
5 c. Y. c9 ~: B+ U# p  u1 0 1 SD 连接到SDMMC1上的SD卡。2 [) F& B; ^* `/ k7 K. z* ]3 h! E
1 1 0 UART/USB 和000效果一样,从UART/USB启动。! F) V$ j1 Z3 _
1 1 1 串行NAND 连接到QUADSPI上的串行NAND  e: I  Z. _! x9 z
表5.1.1 启动模式2 `* {8 s- Q5 _) j) i0 E% s
注:STM32MP1的引脚具有复用功能,因此一个外设有很多不同引脚可以使用,比如SDMMC2的D0引脚就可以使用PB14或PE6。STM32MP1内部ROM代码肯定会有个默认的引脚,比如内部默认使用PB14,如果你自己绘制的板子用了PE6,那么就会出问题。当然了,可以通过修改OTP来设置启动设备所使用的引脚,但是笔者强烈不建议用户修改OTP,因为OTP只能改一次,一旦修改错误芯片就废了。所以大家在绘制自己板子的时候启动设备所占用的引脚一定要和ST官方的开发板一致!
6 k  v) j1 [+ ?; B3 k正点原子STM32MP157开发板上通过拨码开关来选择启动模式,开发板上有丝印提示如何选择不同的启动方式,如图5.1.3所示:
. \2 z2 l# U* b: l$ C 73d5dc2ae2b44eedb98d7a4e7938b4c2.png / a+ i0 x3 @+ c, h. z  `

) ^1 U2 S9 a* c: g) m+ c9 N* Q$ |7 ^图5.1.3 开发板拨码开关设置
  R5 U& ?0 ~9 a2 k/ V大家可以根据开发板上的丝印来选择启动模式,拨上去为ON,也就是‘1’,拨下来就是OFF,也就是‘0’。
$ I& S/ q2 h% F/ z1 }- j但是要注意一点,开发板拨码开关3个开关从左到右依次对应:BOOT0,BOOT1和BOOT2,因此图5.1.2中丝印从左到右依次是BOOT0、BOOT1和BOOT2,顺序刚好和表5.1.1是相反的,表5.1.1中从左到右是BOOT2、BOOT1和BOOT0,所以大家看到开发板上丝印和ST数据手册里面不同的时候不要以为是丝印标错了。
7 k; a: |! o9 B" r; H4 L; Q# }  g) m, }
5.2 STM32MP1启动流程详解
' `; U) g) Y: B, h3 W! v5 |1 n上一小节我们说了,STM32MP1内部有一段ST自己编写的ROM代码,这段ROM代码上电以后就会自动运行,ROM代码会读取BOOT0~BOOT2这三个引脚电平,获取启动模式信息,比如读取到是从EMMC启动的,那么ROM代码就会从EMMC中读取相关程序。说起来很简单,但是实际操作比较复杂,本节我们就来详细学习一下这个启动过程。# z  |0 F' ]) d3 u/ `' y
# w7 S% a" k* d2 k- m4 d
5.2.1 内部ROM代码
( ?1 z7 f) J1 E3 ~内部ROM代码支持如下功能:5 c$ r) _) i! `1 U
①、Secure boot(安全启动),不管是串行启动还是从Flash设备启动。
: e" w+ G! l) Q8 k: l- t: U②、Engineering boot(工程启动?),当BOOT2~BOOT0设置为100的时候,我们就可以通过STLINK访问A7或者M4内核。一般是通过此方法来调试M4内核代码。
/ u/ H% N( h: v# D7 A0 P! w3 c③、Secondary core boot(第二个内核启动),复位以后,STM32MP157的每个A7内核都会启动,并且运行相同的指令。内部ROM代码会分离执行流,只有Core0才会运行ROM代码,另外一个内核会处于一个死循环状态,等待应用程序发送信号来进行下一步操作。这个信号是由SGI(软中断)和另外两个BACKUP寄存器:MAGIC_NUMBER、BRANCH_ADDRESS组成的。如果要启动Core1,运行在Core0的应用程序需要:
; h3 l. G% P) f8 s: s- X" j· 将跳转地址写入BRANCH_ADDRESS寄存器。7 X+ i0 N4 N' X
· 将0xCA7FACE1这个值写入到MAGIC_NUMBER寄存器。
0 z5 d8 V1 }6 Q· 向Core1和发送SGI中断。
0 g2 J8 g/ v: M6 K总结一下,只要你不自己开启第二个核,那么由于内部ROM代码的作用,此时STM32MP157就相当于单核A7。这样有利于我们编写的STM32MP157的A7裸机例程,因为无需考虑多核情况。& n0 R. l2 I. Z' L8 o+ {9 @
④、RMA boot,RMA是Return Material Authorization的缩写,笔者没研究过此种启动方式。* \5 b& g9 _  d7 ^/ q+ c, b; h
⑤、低功耗唤醒。) d7 X) V" Q$ k, h& i
⑥、提供安全相关服务。+ w, G' w# d5 x* M
内部ROM启动流程如图5.2.1.1所示:
4 T% m6 i) Y7 w# N
7 A* a3 A$ ?. I/ V1 r e491bd7c0d1f4a86b14d4a52a1d71607.png ! d, U& G  H2 j& B
( ^* M. _; r2 a2 A( D* o* W
图5.2.1.1 ROM流程图
( G1 V0 T0 T2 j4 y/ V' S- B! i4 Y* K我们只关心图5.2.1.1中红色部分,因为这个是最常用的启动流程,也就是上电或复位以后运行流程:* |; f# J( n1 q
①、首先检查当前是不是CPU0运行,如果不是的话就启动CPU1,正常上电肯定是CPU0在运行。( ?+ B) d) m' z" x, V! w
②、如果是CPU0在运行,检查复位原因。
: m% d; w$ |4 z+ Q: d+ w5 ]③、检查是否为退出Standby而导致的复位,如果不是的话就进入RMA检查。) A( i$ v4 ~8 X; Y3 i& \+ X
③、检查是否为RMA启动,不是的话是否为ENGI启动。/ U( b1 ~, I2 \# J
④、如果不是ENGI启动的话直接进去冷启动。8 ?: C5 Q" o* }9 `! |, `6 U  ?
⑤、进入冷启动以后就从flash中加载系统,并且进行鉴权,如果鉴权成功的话就运行系统。9 o0 g6 a3 m. @' w/ ~. F4 g
3 G/ L- U: N( T
5.2.2 安全启动' |  L; n- O, R' j, T$ |
首先了解两个概念:6 B& C2 @8 l9 e$ D* O
FSBL:全称为First stage boot loader,也就是第一阶段启动文件。
) g( ^! v4 m  A; i) B. tSSBL:全称为Second stage boot loader,也就是第二阶段启动文件。( M* z- M% w  U. e' `4 T
当我们设置好BOOT2~BOOT0,选择从外部Flash,比如EMMC、NAND或NOR等启动的时候就会进入安全启动流程。STM32MP157的安全启动流程比较复杂,这里我们就简单了解一下安全启动的基本流程:6 _6 G% ^; u, ?
①、首先ROM代码从选定的Flash设备中加载FSBL镜像文件,FSBL镜像就是ROM加载的第一个用户编写的可执行程序,一般是TF-A镜像,但是我们可以换成自己编写的程序,比如A7裸机代码。当然了,这个FSBL镜像是有要求的,不是简单的把bin文件丢过来就可以,而是需要在bin文件前面添加一个头部信息,否则内部ROM代码不知道如何处理这个bin文件,这个头部信息以后稍后讲解。8 y* F- _+ ~7 R3 S4 h) _
②、FSBL镜像加载以后需要对其进行鉴权。; d8 ^9 |/ t0 c7 T. h
③、如果鉴权成功,那么就会跳转到FSBL镜像入口地址,开始运行FSBL固件。
: G5 K5 F9 K& J6 K, ^内部ROM首先从选定的Flash设备中读取FSBL镜像文件并运行,但是此时DDR还没有初始化,那么FSBL镜像在哪里运行呢?肯定是内部RAM啊,ST32MP1内部有256KB的SYSRAM,如图5.2.2.1所示:8 C. }7 i* ]; N, |7 N6 A. ]
# @( J+ B2 p- a, t, l
593caf145cdd4bcfb0c56ff390884673.png 8 M3 l9 R+ T. M, h- q* F

, o' n# L8 _' R+ K图5.2.2.1 STM32MP1内部SYSRAM
; I( S8 R5 B* i8 i# i/ c) u# e7 ~3 T& |- |
从图5.2.2.1中可以看出,SYSRAM地址范围为:0X2FFC0000~0X2FFFFFFF,一共是256KB。ROM代码会将FSBL镜像拷贝到0X2FFC2400地址,但是要注意,FSBL镜像的起始地址不是0X2FFC2400,因为FSBL镜像前面还有一个256(0X100)字节的头部信息,因此FSBL镜像的真正起始地址为0X2FFC2400+0X100=0X2FFC2500。为什么要讲明这一点呢?因为我们可以将FSBL镜像换成A7裸机例程,我们在编译A7裸机例程的时候要指定链接起始地址,这个链接起始地址就是0X2FFC2500。由此可以计算出整个FSBL镜像大小不能超过0X30000000-0X2FFC2500=252672B=246.75KB,246.75KB足够我们编写裸机例程了。
8 B& }  R. z- z0 v2 T* P1 c8 XFSBL镜像鉴权成功以后,ROM代码会boot上下文的起始地址保存到R0寄存器,然后跳转到FSBL镜像的入口地址,这个入口地址会定义到头部里面,其实就是上面讲的0X2FFC2500。
4 ?" R$ P8 z% m" H$ gboot上下文我们不需要管,但是还要简单讲一下,boot上下文会保存到SYSRAM的前512字节里面,boot上下文包含了boot信息,比如选定的boot设备,还有一些和安全启动鉴权有关的服务。结构体boot_api_context_t定义了boot上下文结构,boot_api_context_t结构体是定义在TF-A源码里面的(plat/st/stm32mp1/include/boot_api.h),内容如下所示:
* O0 ], \$ D, A' }& P0 Z6 @' d5 S5 W5 C! p9 `
  1. 示例代码5.2.2.1 boot上下文结构
    * s- f: ?/ Y: W& N' a+ i. Z6 @
  2. 1  typedef struct {0 a: N3 |8 r" B6 X3 L
  3. 2           /*" g# Q2 W: a- r9 s) ~& e
  4. 3            * Boot interface used to boot : take values from defines
    ) M7 u6 D0 n, R4 \
  5. 4            * BOOT_API_CTX_BOOT_INTERFACE_SEL_XXX above
    . X! D( S# Y4 K- ]! ^  T( k& L
  6. 5            */! \" k1 b1 \* E0 |  q# U# b- ^
  7. 6           uint16_t boot_interface_selected;
    + `* \* z8 P5 t: j' w+ v
  8. 7           uint16_t boot_interface_instance;
    0 b3 K' o- c2 o' w+ }/ U
  9. 8           uint32_t reserved1[12];8 h8 v9 u) s/ Z* r* D
  10. 9           uint32_t usb_context;
    , s& X9 m2 E: C( b2 E7 X; i, O
  11. 10          uint32_t otp_afmux_values[3];, U. m$ C! z' `7 G6 Q
  12. 11          uint32_t reserved[2];6 ^7 i, J& ]  U3 ]$ W5 \6 b4 D/ S5 C
  13. 12          /*' Q7 @4 {  ]; v) Y, o" I" o8 a8 v
  14. 13           * Log to boot context, what was the kind of boot action
    $ j0 o4 E2 }) R1 K: [) l
  15. 14           * takes values from defines BOOT_API_BOOT_ACTION_XXX above
    + g" y! P+ C* N% p+ N7 r
  16. 15           */
    8 R6 B1 N2 R9 S
  17. 16          uint32_t boot_action;
    ) _6 ^! V% `" Q% k
  18. 17          /*+ |* k7 G  P/ u' X# A
  19. 18            * STANDBY Exit status to be checked by FSBL in case
    8 C4 b& v/ }* x% t% ]# v0 W! G
  20. 19           * field 'boot_action' == BOOT_API_CTX_BOOT_ACTION_WAKEUP_STANDBY! n  B% a6 P& i  L
  21. 20           * take values from defines above 'BOOT_API_CTX_STBY_EXIT_STATUS_XXX'# H) Y9 X7 u/ [" B% ?+ `6 c: b5 Y1 a
  22. 21           * depending on encountered situation
    $ K. ~* N9 _; B+ o
  23. 22           */
      g' ]/ O) m/ |! u( L! Y
  24. 23          uint32_t stby_exit_status;
    $ Q$ N  O# ~3 }: d! Y6 Z- @
  25. 24          /*
    % W$ ?8 v5 G! J# f$ J
  26. 25           * CSTANDBY Exit status to be checked by FSBL in case
    ! x0 }5 i' G* G9 V2 S- b
  27. 26           * boot_action == BOOT_API_CTX_BOOT_ACTION_WAKEUP_CSTANDBY+ `) d6 G* M# o+ E3 ]
  28. 27      * take values from defines above 'BOOT_API_CTX_CSTBY_EXIT_STATUS_XXX'+ ^* e- T0 t, M
  29. 28           * depending on encountered situation
    + Y  h6 h0 w, R0 S( H7 b
  30. 29           */$ J( e, a& [& U3 E
  31. 30          uint32_t cstby_exit_status;
    $ `* q/ [& w1 y( d2 n' V8 n
  32. 31          uint32_t auth_status;
    5 \9 z8 L! Q3 d/ p8 ~$ h# v1 x
  33. 32 ) I" g' E% G6 Z6 O
  34. 33          /*
    6 B8 D* t3 a" m3 {
  35. 34           * Pointers to bootROM External Secure Services0 ]" S+ q( y8 y: f. B7 E# h0 I
  36. 35           * - ECDSA check key
    & U3 s) I5 t  W  o. V
  37. 36           * - ECDSA verify signature3 p0 L  h- ~% J7 D
  38. 37           * - ECDSA verify signature and go
    . ~+ [2 X, b3 B8 r# C( g1 t' _2 z
  39. 38           */2 ?" E$ M/ ]; Q
  40. 39          uint32_t (*bootrom_ecdsa_check_key)(uint8_t *pubkey_in,0 \2 p& w0 c: L
  41. 40                      uint8_t *pubkey_out);
    ! z% ?6 S8 c- R5 R5 ?' A! |, d" |
  42. 41          uint32_t (*bootrom_ecdsa_verify_signature)(uint8_t *hash_in,
    # ~3 A  r' b, I2 X
  43. 42                         uint8_t *pubkey_in,
    2 d: ?4 W. C+ {" m
  44. 43                         uint8_t *signature,7 Q- R' }% t: t4 h
  45. 44                         uint32_t ecc_algo);8 k5 T  C. z% x& w4 H* H
  46. 45          uint32_t (*bootrom_ecdsa_verify_and_go)(uint8_t *hash_in,
    % S$ R" i7 q5 H9 M/ }
  47. 46                      uint8_t *pub_key_in,, V* ^* ~& o0 l7 i
  48. 47                      uint8_t *signature,+ w9 }  c/ g; A' q- `. H% v+ _
  49. 48                      uint32_t ecc_algo,
    ' P( Z! r' v" P
  50. 49                      uint32_t *entry_in);
    : K5 J! y- `* l( T
  51. 50
    & q! L( k4 f# O- R- e  ^
  52. 51          /*
    2 z: B6 Y) k6 U; K( z4 z4 e6 l
  53. 52           * Information specific to an SD boot
    ( y$ s+ {: A, C* q9 j
  54. 53           * Updated each time an SD boot is at least attempted,1 h* h- p6 |( ~8 A! k
  55. 54           * even if not successful: Z$ m5 {9 Z" j) R. ~* ^% d
  56. 55           * Note : This is useful to understand why an SD boot failed
    # e& V3 N7 }. N. k2 c8 ?4 _# y/ G
  57. 56           * in particular) D' u9 p6 X  W' {" s
  58. 57           */7 W( ^% k7 y6 R: U
  59. 58          uint32_t sd_err_internal_timeout_cnt;
    9 e/ w2 f. |6 ]+ q' q* C
  60. 59          uint32_t sd_err_dcrc_fail_cnt;
    & `3 z( x" {/ ^3 v% K9 e( s
  61. 60          uint32_t sd_err_dtimeout_cnt;" ]( Y% g$ a4 W! s- F
  62. 61          uint32_t sd_err_ctimeout_cnt;$ `  L7 O" j: d7 a
  63. 62          uint32_t sd_err_ccrc_fail_cnt;
    * K7 a( f8 u$ g$ G$ ~( y
  64. 63          uint32_t sd_overall_retry_cnt;
      l8 U5 \9 c$ X, Z: b
  65. 64                  /*
    1 N) K  L: I, S
  66. 65           * Information specific to an eMMC boot
    7 k2 ]& z4 P6 Q& L- [; Q1 F' B' m3 A
  67. 66           * Updated each time an eMMC boot is at least attempted,
    & E& w) o6 ?1 O
  68. 67           * even if not successful( A" z2 A4 }& J3 |5 Y4 P
  69. 68           * Note : This is useful to understand why an eMMC boot failed
    1 [" G5 k6 j: a$ K, ?- x
  70. 69          * in particular
    ! b- B5 q0 N; V+ i4 i
  71. 70           */
    * k) J4 K& M5 R$ S$ F/ H
  72. 71          uint32_t emmc_xfer_status;
    2 x% A2 k8 F8 N; L6 ^" k
  73. 72          uint32_t emmc_error_status;2 r6 D1 k4 o1 p2 W6 u' @
  74. 73          uint32_t emmc_nbbytes_rxcopied_tosysram_download_area;
    ( o/ W3 {5 s! Q2 p
  75. 74          uint32_t hse_clock_value_in_hz;
    & A6 M( t6 K" R) }7 w
  76. 75          /*& M1 |: v! M" Z1 w; C
  77. 76           * Boot partition :
    * s: Z. H9 f* R# V' e; \! O9 C
  78. 77           * ie FSBL partition on which the boot was successful
    0 x& ^' u/ Z+ z9 Q! u1 F
  79. 78           */
    ; C1 J0 K. F" t7 A
  80. 79          uint32_t boot_partition_used_toboot;2 A3 w( p2 P( ^0 i' B
  81. 80          /*4 o" m7 n0 U5 V: ^2 I* }4 t7 b6 J
  82. 81           * Address of SSP configuration structure :
    4 R0 j3 t# q1 Q9 s
  83. 82           * given and defined by bootROM& l+ @8 L7 ~* c" D  w7 @$ D
  84. 83           * and used by FSBL. The structure is of type
    / O8 {* J7 @" a0 I! f) o: P2 n
  85. 84           * 'boot_api_ssp_config_t') b) d. `) z1 @  U9 Q0 S
  86. 85           */
    . w* A% z. o8 [
  87. 86          boot_api_ssp_config_t *p_ssp_config;
    * |; f+ h" Y' ]$ F* k( j
  88. 87          /*
    - }+ A, R  t: }! b4 z. ~9 H0 A
  89. 88           * boot context field containing bootROM updated SSP Status. g1 t6 p  L- u/ |  ^$ v  p0 M
  90. 89           * Values can be of type BOOT_API_CTX_SSP_STATUS_XXX3 g# ?7 M" i/ V7 I! R! C2 F  ]
  91. 90           */
    ( @5 M+ m% s+ Y& J8 q
  92. 91          uint32_t    ssp_status;
    ; F  |6 [5 @+ |+ o9 F! ^: k
  93. 92 ' R5 ~+ ]" {& y+ j: O4 c. e3 Y: C
  94. 93          /* Pointer on ROM constant containing ROM information */
    ! ~' ?( F  |6 i( o2 d* u0 T
  95. 94          const boot_api_rom_version_info_t *p_rom_version_info;
    5 k  X3 v( x) Y6 V, W, Z
  96. 95
    / Q6 Q* r$ y: d$ X  \- ~
  97. 96 }         __packed boot_api_context_t;
复制代码

( f0 m3 c0 O  `* C) d( g7 ~. Yboot_api_context_t结构体目前不需要去研究,后面学习TF-A的时候根据实际情况在看是否有必要学习。
3 Q7 s9 y# d2 M0 f+ Y5.2.3 串行启动
6 K% o/ {5 a6 U当我们设置BOOT2~BOOT0为串行启动,也就是从USB或UART启动的时候就会进入此模式。当选择串行启动以后ROM代码就会并行扫描所有可以启动的UART以及USB OTG接口。当扫描到某个活动的串行接口以后,ROM代码就会使用此串行接口,并且忽略掉其他的串行接口。
4 U+ w# e6 a  P8 B+ s! y7 G1、USB启动
4 C) A. C1 U& f4 l9 Y1 @. y内部ROM代码支持USB OTG启动,我们一般使用STM32CubeProgrammer软件通过USB OTG接口来向STM32MP1烧写系统。USB OTG需要一个48M和60M的时钟,这两个时钟由HSE生成。ROM代码支持的HSE时钟值如下:4 T% I6 R  x+ ^% T$ ^
8, 10, 12, 14, 16, 20, 24, 25, 26, 28, 32, 36, 40, 48 MHz
4 @- n7 B& y4 @% u: G: x7 W* u2 W正点原子STM32MP157开发板使用24M有源晶振作为HSE时钟源。我们可以通过设置OTP来更改ROM代码的HSE晶振大小,设置如表5.2.3.1所示:* j0 `. [5 m8 m/ w$ P7 p
OTP WORD 3值
/ ?8 o: q# O4 T2 c" k7 j(2bit) 描述
- a2 |( O2 ~, I; f$ }4 K00 默认模式,ROM代码自动检测HSE频率,HSE频率必须为8, 10, 12, 14, 16, 20, 24, 25, 26, 28, 32, 36, 40或 48MHz,如果没有检测到的话就直接认为是24MHz
! X% T' o. ^1 Y% J0 G2 h1 U' C5 O01 HSE=24MHz* \$ P; Z' ~7 H$ m9 s+ C
10 HSE=25MHz* e; L$ [! w9 P, ^8 l6 C
11 HSE=26MHz- g! T) G+ Q8 S: a
表5.2.3.1 HSE时钟OTP设置表
1 \% N9 d- G: I$ B. O3 G从表可以看出,默认情况下HSE选择24MHz,虽然可以通过修改OTP来更改HSE,但是强烈不建议!因为OTP只能修改一次,一旦修改错误芯片就废了!所以大家在自己做核心板的时候外部HSE时钟最好选择24MHz,不要特立独行!! t* G  I% C; m" Q5 T
2、UART启动% j7 _  j3 {+ V  w+ ]* D
如果要送UART启动,也就是通过UART烧写系统,那么只能使用USART2、USART3 UART4、UART5、USART6、UART7或UART8,此时串口工作模式为:1位起始位、8位数据位、偶校验、1位停止位、波特率115200。1 A  y) H% h3 F$ a! t
由于STM32的IO复用功能,1个串口可能有多个IO可以使用,比如UART4的RX(接收)可以使用PI10、PH14、PA1、PA11、PB2、PB8、PC11、PD0或PD2,一共9个IO可以用作UART4_RX引脚,但是ROM代码里面的UART4_RX引脚肯定只会使用这个9个里面的其中一个,所以我们板子的串口引脚要和ROM代码里面的一致,否则就无法使用串口启动。ROM代码里面串口使用的引脚如表5.2.3.2所示:
9 Q; T: ~; Q/ d( I0 o0 y串口 串口引脚 所使用的IO 复用编号(AF)
# y8 q/ ~* \' ^8 n0 i3 m$ _USART2 USART2_RX PA3 AF07# T  o! Y$ N8 i/ r! P; Q; h. X/ Z
USART2_TX PA2 AF07
- H% ^+ {+ O6 j5 F" y+ C; vUSART3 USART3_RX PB12 AF08
/ ]3 M, n# V/ G3 ~" PUSART3_TX PB10 AF07
+ T& H/ w5 l+ |  X) `7 S9 }8 hUART4 UART4_RX PB2 AF08
% \1 C- f. S. rUART4_TX PG11 AF06$ J7 q, j) \6 D; D6 D# \. c6 t5 ~2 _
UART5 UART5_RX PB5 AF12
5 P% K( T& B: v$ `1 zUART5_TX PB13 AF143 }3 i% c1 y: N9 O
USART6 USART6_RX PC7 AF07
2 `1 X1 \1 N& c! ?" F# `USART6_TX PC6 AF07+ H2 u0 V4 d! U4 X  m
UART7 UART7_RX PF6 AF07
3 L" a! ~+ c3 z* s0 zUART7_TX PF7 AF07+ a+ ?% A  U8 u5 P: y% k) L( q
UART8 UART8_RX PE0 AF08
0 \3 G, T5 i9 J1 y3 K: J* xUART8_TX PE1 AF08
$ _% @- b+ C$ p表5.2.3.2 ROM代码串口默认IO$ G- q( ]7 B6 k3 g3 C& l
大家在做板子的时候,如果要使用串口启动,那么相关串口IO一定要参考表5.2.3.2中定义的IO引脚,比如正点原子开发板UART4的RX引脚使用PB2,TX引脚使用PG11。
0 K" H( w- h2 L5.3 Flash设备启动要求( ^: k+ m( S- U$ b7 b8 H6 y
STM32MP1支持从SD、EMMC、NAND或NOR等Flash设备启动,但是不同的Flash设备在启动的时候有不同的要求。linux系统他不像单片机那样,就一个bin文件,烧写进去就可以启动并运行,linux系统自身编译出来就是一个镜像文件,但是这个镜像文件要运行是需要一大堆的“小弟”来辅助。比如需要uboot来启动,启动以后还需要根文件系统(rootfs),传统的嵌入式linux有三巨头:uboot、kernel和rootfs,但是对于STM32MP1而言,由多了几个“小弟”,比如TF-A、TEE、vendorfs等,所有这一大堆构成了最终的系统镜像。系统镜像是要烧写到Flash设备中的,这些不同的文件肯定要按照一定的要求,分门别类的烧写,一个萝卜一个坑,TF-A应该放到哪里、uboot应该放到哪里等等。
7 G' G. y7 W% l* b% p: R针对Flash设备,可以通过创建不同的分区来存放不同的文件,ST针对STM32MP1系列给出了官方分区建议,这些建议包含了Flash分区数量、分区最小空间、分区存放的内容等,如表5.3.1所示:! |8 k0 ?3 q2 Z! ^, g
尺寸 分区 描述0 J! g) s; W, t5 @4 {- B
256KB~512KB fsbl 第一阶段启动代码,此分区存放TF-A或者uboot的SPL部分,如果写A7裸机例程的话此分区也用来存放裸机代码。1 [1 b  |3 W- d3 b7 `( T
2MB ssbl 第二阶段启动代码,一般是uboot,如果uboot使用设备树的话,设备树添加到后面。
  ^- a( ?7 z! v0 k$ u64MB bootfs boot文件分区,可以存放如下内容:
2 G: V% j2 _, v1 W· init ram文件系统,可以将此文件系统拷贝到RAM中,在linux内核挂载正式根文件系统之前可以使用init ram文件系统。
- x. W4 _7 M- H2 S( t& \$ J. x·linux内核设备树
9 Y9 F. N' ^) w* I·linux内核
% H8 o) k3 C; }& P3 m·uboot显示的启动界面6 K5 T1 a' @3 w0 O3 j6 p# ^
·uboot发行配置文件extlinux.conf
3 b& D0 X; ^" }7 N3 T6 d16MB vendorfs 此分区存放第三方的版权信息,确保它们不会受到任何开源许可的污染,比如GPL V3。* S- U; Z+ m  r
768MB rootfs linux根文件系统。
  |8 z5 v$ u9 Z+ G剩余空间 userfs 用户自行使用的剩余空间! O7 K( G  y- I2 i
表5.3.1 ST官方的Flash分区建议
7 r  d0 Z  a0 i/ ]5.3.1 从NAND启动; u1 t5 S% a/ h0 [% ~2 {. s
NAND前几个块(block)里面包含了多份FSBL,ROM代码会从第一个块开始扫描,并且加载第一个有效块里面的FSBL。ROM代码支持并行NAND和串行NAND,并行NAND连接到FMC总线上,串行NAND连接到QSPI上。
7 V0 X5 s8 U- C& vROM代码支持的并行NAND要求如表5.3.1.1所示:9 K/ a( N! n+ E9 S5 D8 N
块大小(KB) 页大小(KB) 数据宽度 ECC(bit数和编码)
) X# i1 q; M! x% r  ~128 2 8,16 4(bch),8(bch),1(hamming)& C9 {3 |! |, v5 M1 G
256 4 8,16 4(bch),8(bch),1(hamming)
8 Y% `% E5 F5 C) m5 Z" \% J8 [512 4 8,16 4(bch),8(bch),1(hamming)
/ s0 o+ X- J% ~2 v3 @512 8 8,16 4(bch),8(bch),1(hamming)! H" H, G0 u+ {9 [
表5.3.1.1 并行NAND参数要求
0 L# o) p( \* R* i. yROM代码支持串行NAND要去如表5.3.1.2所示:
+ n! j) i  T" R& B: x3 _8 f块大小(KB) 页大小(KB)8 ]! t. R9 R" e3 w  T& w. Y
128 2' d: ?6 @3 m0 K/ Z2 \& D
256 4
; |! S' K0 ?& x: T, O1 i512 4& }( y+ K& Y) T, c
512 8
: l4 i' S/ Q' J3 v表5.3.1.2 串行NAND参数要求
* v  U4 J; s4 y  o7 D$ V; r所以大家在制作STM32MP1硬件的时候,NAND Flash选型一定要符合表5.3.1.1中的参数。
( o; z$ _3 _, `- t* d! o* c  P! T# k5.3.2 从EMMC启动
; G" u$ }8 j  {, c2 g9 A) o9 eEMMC在物理结构上有boot1、boot2、RPMB(Replay Protected Memory Block)、GPP(General Purpose Partitions,GPP最多4个分区)以及UDA(User Data Area)这5种分区,比如三星的KLM系列EMMC 5.1的分区结构如图5.3.2.1所示:
/ \: T2 w6 ^# }8 P$ ?' n/ P1 a( ]( n8 h2 x4 S' O6 q% Q( x8 q
0413c8cadfdc459e82052f346843b543.png * I+ L' \7 y. F' M$ H% _/ ?

  J4 y: Q: `+ L* z图5.3.2.1 三星KLM系列EMMC分区结构' n' E) t; n2 ~  v' T
我们一般知道和常用的就是UDA分区,也就是用户数据区域,很少会关心boot1、boot2这样的分区。boot1、boot2、RPMB这三个分区代销是固定的,用户不能修改,boot1、boot2分区存在的意义就是用于引导系统。正点原子STM32MP157开发板所使用的EMMC型号为KLM8G1GETF,这是三星的一颗8GB EMMC 5.1芯片,boot1、boot2和RPMB分区大小如图5.3.2.2所示:" t! ~" M: Y8 e2 Q4 d3 v" J
/ }2 s  p( `. E
10a7f98b0be34cdb89befd54b34829ee.png
' s% E9 j" P6 d( u0 [6 l+ |9 h/ f; F$ Z1 m& C) R
图5.3.2.2 KLM系列EMMC分区
2 s: H3 _& k% i+ U+ O从图5.3.2.2中可以看出,对于三星的8GB的EMMC而言,boot1和boot2分区默认大小为4096KB,RPMB为512KB。
6 X4 X5 p* N1 W( J! H2 A4 @ST会使用EMMC的boot1和boot2这两个分区作为FSBL,但是同一时间只有一个有效,ROM代码会加载有效的哪个FSBL。ROM代码使用单bit模式来操作EMMC,默认情况下ROM代码使用连接到SDMMC2上的EMMC,可以通过OTP来修改EMMC所使用的SDMMC接口,但是这里不建议!
  q  ^* x- V* m$ x/ q. L5.3.3 从SD卡启动9 D4 U5 Z: i$ ]
SD卡也包含两个FSBL,但是SD卡没有boot1和boot2这样的物理分区。ROM代码默认尝试加载第一个FSBL,如果第一个FSBL加载失败,那么ROM代码就会加载第二个FSBL。
$ a% A! F! n! }8 Z! jROM代码首先在SD卡上查找GPT分区,如果找到的话就查找名字以“fsbl”开始的两个FSBL分区。如果没有找到GPT分区的话就直接根据物理地址查找两个FSBL,第一个FSBL的起始偏移地址为LBA34,地址位34512=17408=0X4400,所以第一个FSBL的起始地址为0X4400。第二个FSBL的起始偏移地址为LBA546,地址为 546512=279552=0X44400,所以第二个FSBL的起始地址为0X44400。; M  s( B# i8 N* s% o" c
ROM代码默认也是使用单bit模式操作SD卡,并且默认使用连接到SDMMC1接口上的SD卡。
% H- E1 z* |8 l2 C8 D2 g- S. `5.4 STM32MP1二进制头部信息
( Z. R5 J; u  N前面讲了STM32MP1内部的ROM代码会先读取FSBL代码,一般是TF-A或者Uboot的SPL,也可以是A7裸机代码。比如TF-A我们之间编译生成二进制bin文件,但是这个bin文件不能直接拿来用,需要在前面添加一段头部信息,这段头部信息也包含了鉴权内容。加入头部信息以后的FSBL代码结构如图5.4.1所示:
3 ^7 I( n) ^. R* J& M' ^& A- O9 {! x3 G$ m; \$ M9 z
cff5b4eb064c42cab0563ed3dc821b07.png
7 x5 v! d9 e* U/ f& x" g
, {- e: E1 E$ S+ g% X. j6 U" G图5.4.1 FSBL镜像组织结构
3 Q. f% s) [, P2 k4 ^# j; D$ ~头部信息一共是256字节,这256个字节的头部信息具体含义如表5.4.1所示:
6 y' R  ?$ z1 @; {0 O- Z) y8 L+ c名字 长度 偏移(B) 描述
7 B& G: s1 f6 Q, D. BMagic number 32bits 0 魔术数,4字节,为‘S’、‘T’、‘M’和0X32的组合,固定为0X53544D32。注意,这4个字节是大端模式,也就是高字节存储在低地址处,低字节数据在高地址处。
' o" v2 z% S7 ?8 h& yImage signature 512bits 4 ECDSA签名,用于镜像鉴权。
6 \5 _: `8 k: X: K/ H4 m0 [Image checksum 32bits 68 镜像校验和。
; f  ]# m8 ~+ _1 p, X3 h- q# wHeader version 32bits 72 头部版本信息,V1.0的话为0X00010000,含义:
, }3 @* o8 G! q/ Z. bByte0:保留. K. M% I* o' k; F+ @
Byte1:主版本号为0X01。7 U- N5 H6 ]0 R$ N! i& b
Byte2:次版本号为0X00。" m& u# d, N3 o* D  E
Byte3:保留
7 F7 r4 @/ u3 B0 _% O! ]Image length 32bits 76 镜像长度,不包含头部,单位为字节。: r8 ^, z2 _3 H, R1 P, a
Image entry Point 32bits 80 镜像入口地址。
/ W: l2 K- q6 D( I8 yReserved1 32bits 84 保留。1 F3 e) A, ]! K4 ?( F0 N; I" }4 S
Load address 32bits 88 镜像加载地址,ROM代码不使用此地址。
9 P6 [& K" ]' {# X+ O% f" }5 wReserved2 32bits 92 保留。% F5 Z3 _2 \# {  B+ e' ?
Version number 32bits 96 镜像版本信息。& c. b+ y/ Q& t# a# N* g9 d
Option flags 32bits 100 可选字段,b0=1的话表示不需要验证签名。
9 r1 V  I- x( `9 IECDSA algorithm 32bits 104 ECDSA算法,1:P-256 NIST;2:brainpool 256
& I1 I8 |" s: @7 eECDSA pubilc key 512bits 108 ECDSA公共秘钥,签名的时候使用。
0 b, s- ^: ]( P/ T2 NPadding 83Bytes 172 保留的填充区域,必须全部为0
: S$ O8 N2 U8 s. T4 {/ pBinary type 1Byte 255 二进制文件类型:
7 o  r* k3 v% ?- R0X00:U-Boot
! N" A& A$ m. M$ ], j- m0X10-0X1F:TF-A
- K* H% U; l- r, {& S% I7 x0X20-0X2F:OPTEE
# g$ F' K! K" N3 H  F3 X% o0X30:Copro8 x( _) t$ ^2 C6 y' i) J+ s
表5.4.1 头部信息含义
8 i# |# A+ y3 g1 T: g7 {头部信息不需要我们自己手动添加,我们在编译ST官方提供的TF-A或者Uboot的时候会自动添加,因为ST提供了个名为“stm32image”的工具专门用于在bin文件前面添加头部信息。我们已经从TF-A源码中提取出来了stm32image并放到了开发板光盘中,路径为:开发板光盘5、开发工具2、ST官方开发工具stm32imagestm32image.c。在编写A7裸机的时候需要自己使用stm32image工具在bin文件前面添加头部信息,stm32image是在Ubuntu下运行的,所以需要先编译,将stm32image.c发送到Ubuntu下,然后输入如下命令编译:
6 M1 P+ e$ h% a1 U; C9 x+ Y9 mgcc stm32image.c -o stm32image9 M  S% [- d9 z# M! |
编译成功以后就会生成一个名为stm32image的可执行文件,如不5.4.2所示:9 t2 Z$ a2 V( X' k" c+ c+ T

8 U/ ^/ v6 h8 y* o2 _/ B6 E 6241fd2c43e2486faed955acab75dd62.png 9 H- f% h0 s- y3 c7 D/ _0 u
# O+ w9 E3 t- ]
图5.4.2 stm32image工具' I3 u9 Z5 [( K4 Y6 }
运行图5.4.2编译出来的stm32image工具,输入“-s”选项可以查看使用方法,如图5.4.3所示:6 k/ I, @; I$ d) d; m; s# v
! ^! {7 s# i- l5 r
af748a43c7de4babbbe1541cebe0cd87.png $ d& Y; J! T" R, C- G

- h8 {; z$ w3 L! X2 i( i$ u图5.4.3 stm32image使用方法6 g# K& h1 U- K, n! P
从图5.4.3可以看出,stm32image在使用的时候需要搭配一系列的参数:# D1 S+ b8 [0 V4 {2 P5 |/ f! ]
-s:指定源文件。/ I8 V( X7 ~! Q5 b
-d:生成的目标文件。' u% Q- V1 O' `0 X
-l:加载地址。0 f+ E. ?' J& l9 A, i: ^
-e:入口地址。
. f: u( n& t# `5 f( F+ P0 P% J-m:出版本号。+ {( ~0 b% t; c
-n:次版本号。
2 {3 @$ x* s) ?0 E% |# @) E: R* S大家在开发板光盘里面找到正点原子出厂的tf-a固件,路径为:开发板光盘8、系统镜像2、出厂系统镜像1、STM32CubeProg烧录固件包tf-atf-a-stm32mp157d-atk-trusted.stm32,这个就是加入了头部信息的TF-A可执行文件。使用winhex软件打开tf-a-stm32mp157d-atk-trusted.stm32,winhex软件已经放到了开发板光盘中,路径为:开发板光盘 3、软件 winhexv19.7.zip,大家自行安装即可。安装完成以后打开winhex,然后点击:文件->打开,找到tf-a-stm32mp157d-atk-trusted.stm32并打开,如图5.4.4所示:
+ B+ s- V+ b3 y+ ~5 {
; {) A8 A. F* z4 J, G5 Z be0523a1e7e44ffe93e61e0b0b28efe5.png
/ ?- |( Y3 b. g
" ^: \3 N$ z4 _8 F& w8 ^图5.4.4 tf-a-stm32mp157d-atk-trusted.stm32文件内容
0 \: E6 b. }0 u" Y图5.4.4就是tf-a-stm32mp157d-atk-trusted.stm32文件原始数据,其中前256个字节就是头部信息。这里我们根据图5.4.4中的内容,分析一下tf-a头部信息中几个比较重要的参数:
; {" i% l, f$ z0 g& F! @  xMagic number:起始偏移地址为0,长度为4个字节,值依次为:0X53、0X54、0X4D、0X32,合起来就是0X53544D32,这个就是表5.4.1中的魔术数,注意这四个字节的顺序是大端模式。1 T" O+ B# Q0 y% C+ \1 t% ~4 _1 f0 A
Header Version:起始偏移地址为72,长度为4个字节,也就是图5.4.4中第72~75这4个字节的数据,分别为:0X00、0X00、0X01和0X00,此时有的朋友将这四个字节拼起来发现是0X00000100,发现并不是表5.1.1中的0X00010000!这不是弄错了?肯定不是的,整个头部信息中,除了Magic number采用大端模式存储以外,其他都是小端模式存储,也就是低字节数据存放在底地址处,高字节数据存放在高字节处。因此0X00、0X00、0X01和0X00这四个字节的数据正确的拼出结果为0X00010000。: ^0 x* D7 H$ R5 h2 n0 \
Image length:起始偏移地址为76,长度为4个字节,也就是图76~79这4个字节的数据,为:0X40、0XB0、0X03和0X00,按照小端模式拼起来就是0X0003B040=241728≈236.1KB,说明此TF-A的bin镜像大小为236.1KB。3 C  z% K$ M. l
Image entry Point:起始偏移地址为80,长度为4个字节,也就是图80~83这4个字节的数据,为:0X00、0X60、0XFD和0X2F,按照小端模式拼起来就是0X2FFD6000,说明入口地址为0X2FFD6000。; E. _' i4 U4 u  D& b
Load address:起始偏移地址为88,长度为4个字节,也就是图88~91这4个字节的数据,为:0X00、0X25、0XFC和0X2F,按照小端模式拼起来就是0X2FFC2500,说明加载地址为0X2FFC2500,这个不正是我们前面在5.2.2小节中分析的FSBL镜像起始地址。
2 R2 v5 m" E- F& yBinary type:起始偏移地址为255,也就是最后一个字节,为0X10,表示当前二进制文件是TF-A。
6 L7 L( Y' x5 ~6 d; g5.5 STM23MP1 Linux系统启动过程" W" F' \8 g# _' @  E
前面已经对STM32MP1的启动流程做了详细的讲解,STM32MP1是面向Linux领域的,因此所以的这些启动过程都是为了启动Linux内核。STM32MP1xil启动Linux内核的流程如图5.5.1所示:2 R6 X' X$ b5 N$ ]

+ S, i& S8 T) H8 e5 A 3f193ad3432d445e90152a1bdfe3e5a8.png
% \+ l7 D3 B1 S8 G7 c
( F6 S0 J. z7 p+ y( k: \图5.5.1 MP1 Linux启动流程1 g8 U3 U' W6 Y8 {
从图5.5.1可以看出,STM32MP1启动linux内核一共分为5个步骤,我们依次来看一下这五个步骤的内容:& q/ p! F* z  o/ K7 z
①、ROM代码
! D( z4 Y: t+ [0 N/ P: Y7 R( g% R前面说了很多次了,这是ST自己编写的代码,在STM32MP1出厂的时候就已经烧写进去的,不能被修改的。ROM代码因为保存在STM32内部ROM里面,因此也就直接简单明了的叫做“ROM代码”了。它是处理器上电以后首先执行的程序,ROM代码的主要工作就是读取STM32MP1的BOOT引脚电平,然后根据电平判断当前启动设备,最后从选定的启动设备里面读取FSBL代码,并将FSBL代码放到对应的RAM空间。0 c, s9 |; B# j: N
现在很多产品对设备上运行的应用都提出了安全要求,从图5.5.1中可以看出,STM32MP1启动Linux内核的过程是一个链式结构:ROM CodeFSBLSSBLLinux kernelrootfs,系统启动的过程中要保证整个链式结构都是安全的。ROM代码作为第一链,首先要对FSBL代码进行鉴权,同样的,FSBL以及后面的每一链都要对下一个阶段的镜像进行鉴权,直到设备系统正确启动。
: G; B" _' J- P6 b3 z$ |+ C6 h②、FSBL$ @4 o8 L% `# H: [4 C1 h$ M$ H
FSBL代码初始化时钟树、初始化外部RAM控制器,也就是DDR。最终FSBL将SSBL加载到DDR里面并运行SSBL代码。
8 B  |. Z! U/ V& m一般FSBL代码是TF-A或者Uboot的SPL代码,前面我们说了,也可以将FSBL换成我们自己编写的STM32MP1 A7内核裸机代码。# E' N( p9 \  R! `: S. `
③、SSBL
8 w, S7 {; ^- O3 P由于SSBL代码运行在DDR里面,无需担心空间不够,因此SSBL代码的功能就可以做的很全面,比如使能USB、网络、显示等等。这样我们就可以在SSBL中灵活的加载linux内核,比如从Flash设备上读取,或者通过网络下载下载等,用户使用起来也非常的友好。SSBL一般是Uboot,用来启动Linux内核。& {( o; q& O( h3 m  B* Z
③、Linux内核
6 K/ r- g7 L& X: J! ~( aSSBL部分的Uboot就一个使命,启动Linux内核,Uboot会将Linux内核加载到DDR上并运行。Linux内核启动过程中会初始化板子上的各种外设。
2 ]% T) v4 A& x& ^6 ]# K. I/ e④、Linux用户空间
) e$ i2 y# W: t4 Z* `7 e4 [1 T系统启动的时候会通过init进程切换到用户空间,在这个过程中会初始化根文件系统里面的各种框架以及服务。
1 ]9 l# T/ t- `- y# E! @% ~6 a2 X————————————————
$ p, C+ ]2 z0 u: S+ y( N0 @9 {版权声明:正点原子
1 u$ Z6 Q8 o7 j5 U+ Z9 _+ ]& |! T
3 h9 n7 Q3 M$ A7 o  V
5 M. j, H& a& F! W  b
赞 收藏 评论0 发布时间:2022-9-21 15:49

举报

0个回答

所属标签

相似技术帖

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版