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

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

[复制链接]
STMCU小助手 发布时间:2022-9-21 15:49
5.1 STM32MP1启动模式
1 U* A1 o, F  _6 `: OSTM32MP1支持从多种设备启动,比如EMMC、SD、NAND、NOR、USB、UART等。STM32MP1内部有一段ROM来存放ST自己编写的程序,这段ROM空间是不开放给用户使用的,仅供ST存放自己的ROM代码,ROM空间如图5.1.1所示:- {  \& u' W4 z) \1 X$ k# n4 c: r

7 f) o/ v8 L. f) F: e1 Q 1776aec2429d4a75ab3cdbc2588b4023.png
. v: X4 `; _1 C5 g2 b6 s
7 F  d, z: t3 \" |, x图5.1.1 内部ROM空间
" |: y( q7 q  j: }; q1 F( B图5.1.1中CA7是Cortex-A7的缩写,可以看出A7内核有128KB的ROM空间,起始地址为0X00000000,STM32MP1上电以后会先运行这段ROM代码。STM32MP157有三个BOOT引脚:BOOT0~BOOT2,这三个BOOT引脚通过拉高/拉低来设置从哪种设备启动,正点原子STM32MP157开发板上的拨码开关就是控制这三个BOOT引脚的,大家可以打开底板原理图,找到BOOT相关原理图,如图5.1.2所示:
7 b; T  |- @* R* B7 f1 n7 R) {1 r# ?/ b8 v" t6 A7 H& `
0d87acc45c4244fba0b868adf62b8f0f.png
2 C' G7 O3 b' L. K$ I) z+ n; a5 k" \" L1 C9 }
图5.1.2 BOOT原理图6 l$ J+ J" W+ j/ P& W
从图5.1.1可以看出,当BOOT0~BOOT1拨到“ON”的时候就会接到3.3V上,此时就是 逻辑1,拨到OFF的时候BOOT0~BOOT1就悬空(也可以外接下拉电阻),此时就是逻辑0(悬空或接地都为逻辑0),三个引脚不同电平对应的启动模式,如表5.1.1所示:; o9 O. o: M+ X8 @* G( w5 P
BOOT2 BOOT1 BOOT0 启动模式 描述0 ^' V) ?/ {0 x8 c2 y# W% C
0 0 0 UART/USB - USART2/3/6、UART4/5/6/7/8。) O! X7 U0 s' {3 u8 ?' @

; Q8 n  b! F0 L5 G/ v  @USB接口。
3 E( O+ C8 s# [) P) z0 0 1 串行NOR 串行NOR或者QUADSPI( s( T" e( S3 e7 c
0 1 0 EMMC 连接到SDMMC2上的EMMC设备。$ B3 {9 s3 J% i7 n& [6 x7 x1 y
0 1 1 并行NAND 连接到FMC上的并行SLC NAND$ x/ C9 n% H3 r+ z
1 0 0 MCU 启动内部的M4内核。8 r( r! K) z% n; v" x9 W) t7 Z% F/ K
1 0 1 SD 连接到SDMMC1上的SD卡。7 w# N' [; z* q5 L& J
1 1 0 UART/USB 和000效果一样,从UART/USB启动。
7 y) E/ P/ d- p1 1 1 串行NAND 连接到QUADSPI上的串行NAND1 A; L' r* c- i2 S: P+ ^+ e
表5.1.1 启动模式
+ i$ D4 U7 M5 H% [注:STM32MP1的引脚具有复用功能,因此一个外设有很多不同引脚可以使用,比如SDMMC2的D0引脚就可以使用PB14或PE6。STM32MP1内部ROM代码肯定会有个默认的引脚,比如内部默认使用PB14,如果你自己绘制的板子用了PE6,那么就会出问题。当然了,可以通过修改OTP来设置启动设备所使用的引脚,但是笔者强烈不建议用户修改OTP,因为OTP只能改一次,一旦修改错误芯片就废了。所以大家在绘制自己板子的时候启动设备所占用的引脚一定要和ST官方的开发板一致!0 _' i* O2 s( J2 U; |
正点原子STM32MP157开发板上通过拨码开关来选择启动模式,开发板上有丝印提示如何选择不同的启动方式,如图5.1.3所示:
+ L: K3 |/ _' m. i, W; A 73d5dc2ae2b44eedb98d7a4e7938b4c2.png ! `6 W9 _: n+ R7 n; {7 Q* j/ X9 h

* k" i( a1 q' T' h( C( b图5.1.3 开发板拨码开关设置5 |' ]3 Y$ K/ t1 {% {5 u: H
大家可以根据开发板上的丝印来选择启动模式,拨上去为ON,也就是‘1’,拨下来就是OFF,也就是‘0’。
( B* A' _" T' o7 n但是要注意一点,开发板拨码开关3个开关从左到右依次对应:BOOT0,BOOT1和BOOT2,因此图5.1.2中丝印从左到右依次是BOOT0、BOOT1和BOOT2,顺序刚好和表5.1.1是相反的,表5.1.1中从左到右是BOOT2、BOOT1和BOOT0,所以大家看到开发板上丝印和ST数据手册里面不同的时候不要以为是丝印标错了。$ H$ c- u: A5 j% W; O9 o9 a
. z3 W& W8 ?% [" L
5.2 STM32MP1启动流程详解1 m% @9 O6 [9 c3 o4 g" ?* Q9 p
上一小节我们说了,STM32MP1内部有一段ST自己编写的ROM代码,这段ROM代码上电以后就会自动运行,ROM代码会读取BOOT0~BOOT2这三个引脚电平,获取启动模式信息,比如读取到是从EMMC启动的,那么ROM代码就会从EMMC中读取相关程序。说起来很简单,但是实际操作比较复杂,本节我们就来详细学习一下这个启动过程。
- f" {9 i, C* K, @& W
  e+ r- w! y2 L3 s+ }5.2.1 内部ROM代码
; D. a% a* c2 h. D$ u8 `) t9 _0 J内部ROM代码支持如下功能:2 L: l' x: [# I. }" ^: r  d! o
①、Secure boot(安全启动),不管是串行启动还是从Flash设备启动。
" E' N( a; D! _4 {+ X②、Engineering boot(工程启动?),当BOOT2~BOOT0设置为100的时候,我们就可以通过STLINK访问A7或者M4内核。一般是通过此方法来调试M4内核代码。
# h" e8 J) {# }) q0 v3 ^0 y3 F③、Secondary core boot(第二个内核启动),复位以后,STM32MP157的每个A7内核都会启动,并且运行相同的指令。内部ROM代码会分离执行流,只有Core0才会运行ROM代码,另外一个内核会处于一个死循环状态,等待应用程序发送信号来进行下一步操作。这个信号是由SGI(软中断)和另外两个BACKUP寄存器:MAGIC_NUMBER、BRANCH_ADDRESS组成的。如果要启动Core1,运行在Core0的应用程序需要:& \: V# y5 y& Q& Y  v% c
· 将跳转地址写入BRANCH_ADDRESS寄存器。
; z7 o! I/ H- `5 z( S3 Z$ m2 {· 将0xCA7FACE1这个值写入到MAGIC_NUMBER寄存器。9 {! I8 C. v% `* r; b& |2 G& s( e$ J
· 向Core1和发送SGI中断。4 Y/ {4 f; C4 V4 u  I- \- r
总结一下,只要你不自己开启第二个核,那么由于内部ROM代码的作用,此时STM32MP157就相当于单核A7。这样有利于我们编写的STM32MP157的A7裸机例程,因为无需考虑多核情况。
( ]1 |$ c, x+ e9 G- ]+ |1 R④、RMA boot,RMA是Return Material Authorization的缩写,笔者没研究过此种启动方式。: K% T$ v  R: A% j+ `" Q* p
⑤、低功耗唤醒。
4 J+ O9 F' F* d" d$ S8 E⑥、提供安全相关服务。" c7 o9 j+ H/ `3 J( j
内部ROM启动流程如图5.2.1.1所示:
0 G6 N' x  A% F& ?( [6 H) l3 U
. P: L3 q6 L  R7 l+ x e491bd7c0d1f4a86b14d4a52a1d71607.png
) ], T3 j$ \; R  S, }
$ @/ D/ m: q- Y5 @2 W/ r- B; M图5.2.1.1 ROM流程图
, U; n+ [, B5 o5 x$ [; _/ o, E: _我们只关心图5.2.1.1中红色部分,因为这个是最常用的启动流程,也就是上电或复位以后运行流程:* O) l# N! r4 g  J
①、首先检查当前是不是CPU0运行,如果不是的话就启动CPU1,正常上电肯定是CPU0在运行。
! ~& D* S1 S& _& d" I- {②、如果是CPU0在运行,检查复位原因。
- S( i* }& @0 k7 e2 o③、检查是否为退出Standby而导致的复位,如果不是的话就进入RMA检查。
  c) ]7 T# X5 v5 q4 Q+ S+ i7 N③、检查是否为RMA启动,不是的话是否为ENGI启动。
% W8 W4 F$ W4 e% K8 E④、如果不是ENGI启动的话直接进去冷启动。
7 b/ `$ e- v- @- ]) o( ?$ r/ N⑤、进入冷启动以后就从flash中加载系统,并且进行鉴权,如果鉴权成功的话就运行系统。
1 n* V2 H  `5 H' R7 w, D1 L/ S8 ?- b' A+ Y! e
5.2.2 安全启动
) G9 _& l& n$ `" N首先了解两个概念:
- r$ r$ v5 z: {( t7 a4 V3 OFSBL:全称为First stage boot loader,也就是第一阶段启动文件。$ _- N3 {( i, S
SSBL:全称为Second stage boot loader,也就是第二阶段启动文件。
6 r; _0 G# i' J7 W当我们设置好BOOT2~BOOT0,选择从外部Flash,比如EMMC、NAND或NOR等启动的时候就会进入安全启动流程。STM32MP157的安全启动流程比较复杂,这里我们就简单了解一下安全启动的基本流程:  M6 T- R5 U" k6 b5 ]! r& U7 B
①、首先ROM代码从选定的Flash设备中加载FSBL镜像文件,FSBL镜像就是ROM加载的第一个用户编写的可执行程序,一般是TF-A镜像,但是我们可以换成自己编写的程序,比如A7裸机代码。当然了,这个FSBL镜像是有要求的,不是简单的把bin文件丢过来就可以,而是需要在bin文件前面添加一个头部信息,否则内部ROM代码不知道如何处理这个bin文件,这个头部信息以后稍后讲解。
8 c1 T  C+ Q1 B% H* E9 Q/ @②、FSBL镜像加载以后需要对其进行鉴权。
/ S. O2 W' X5 _! D③、如果鉴权成功,那么就会跳转到FSBL镜像入口地址,开始运行FSBL固件。
, t+ K$ _) e6 _: O内部ROM首先从选定的Flash设备中读取FSBL镜像文件并运行,但是此时DDR还没有初始化,那么FSBL镜像在哪里运行呢?肯定是内部RAM啊,ST32MP1内部有256KB的SYSRAM,如图5.2.2.1所示:
8 a, V2 B7 n8 ^  }1 x% I. c) p. R8 n4 k' d! g' _2 c+ T& |& {
593caf145cdd4bcfb0c56ff390884673.png
$ {( G0 H1 k, g' R4 K# z, x
- {' l" P  s" m$ ~: O5 @图5.2.2.1 STM32MP1内部SYSRAM
$ v2 U8 B/ B6 Y" P( ]5 S% u0 Z+ H& S, N+ j; l# p! {
从图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足够我们编写裸机例程了。
2 d3 \% w9 Y( W  A# T; w9 `, B- @- jFSBL镜像鉴权成功以后,ROM代码会boot上下文的起始地址保存到R0寄存器,然后跳转到FSBL镜像的入口地址,这个入口地址会定义到头部里面,其实就是上面讲的0X2FFC2500。
$ m; k' B2 k9 z; r1 K$ Vboot上下文我们不需要管,但是还要简单讲一下,boot上下文会保存到SYSRAM的前512字节里面,boot上下文包含了boot信息,比如选定的boot设备,还有一些和安全启动鉴权有关的服务。结构体boot_api_context_t定义了boot上下文结构,boot_api_context_t结构体是定义在TF-A源码里面的(plat/st/stm32mp1/include/boot_api.h),内容如下所示:
6 ~: |, C9 c6 z' v+ H- B! X
: B* S7 Q9 Z+ v! T4 z9 _4 z$ F4 Y
  1. 示例代码5.2.2.1 boot上下文结构+ p' X% j- h# Z
  2. 1  typedef struct {
    7 {: A' d( X% h' T/ m$ L) H1 l
  3. 2           /*$ A8 f$ C* G( _% T7 [7 v1 j1 e" }
  4. 3            * Boot interface used to boot : take values from defines1 q9 J6 k+ @4 q) L, D6 ~" Y' ~2 X! H
  5. 4            * BOOT_API_CTX_BOOT_INTERFACE_SEL_XXX above
    : ?- c2 D; d2 t  M" W$ ^, W
  6. 5            */* i5 M; v, ?' R2 e# x. h
  7. 6           uint16_t boot_interface_selected;, ?* Q+ X1 `; e+ M. a3 V( W4 d
  8. 7           uint16_t boot_interface_instance;
    . X  k' r& k( n, [. `2 ^; g( G
  9. 8           uint32_t reserved1[12];8 t" ]/ V# I0 W  {8 f
  10. 9           uint32_t usb_context;" Q  `# V7 L  F% ?
  11. 10          uint32_t otp_afmux_values[3];' @% k+ L1 }9 }  ^- S
  12. 11          uint32_t reserved[2];
    9 Z( f3 G! n  Q. t
  13. 12          /*$ n# g, r8 }$ E& a+ j
  14. 13           * Log to boot context, what was the kind of boot action7 T- |$ N+ Y% e7 z1 {
  15. 14           * takes values from defines BOOT_API_BOOT_ACTION_XXX above
    9 Y7 z5 \! P$ A$ _% f( C" S
  16. 15           */
    1 [0 Q% V# L$ p
  17. 16          uint32_t boot_action;
    5 E7 D# D) G/ j
  18. 17          /*3 I) A; c7 v$ p1 c. ?
  19. 18            * STANDBY Exit status to be checked by FSBL in case
    : r3 f& r( v- E8 J& l8 @7 U
  20. 19           * field 'boot_action' == BOOT_API_CTX_BOOT_ACTION_WAKEUP_STANDBY
    ; C. @7 A$ z. E$ k' g! N' k
  21. 20           * take values from defines above 'BOOT_API_CTX_STBY_EXIT_STATUS_XXX'
    ! h0 |$ C, k3 }/ D! D, K- C' d9 P, {
  22. 21           * depending on encountered situation
    % u% ~2 o& l3 S& j: n% ^$ S0 l
  23. 22           */
    # V/ C" H* D( E: n8 r
  24. 23          uint32_t stby_exit_status;- I  a1 B* m, x5 {8 c
  25. 24          /*
    . W. i2 M0 r! Z. w8 n
  26. 25           * CSTANDBY Exit status to be checked by FSBL in case6 q. j4 t0 B: J( |. }0 r% Q
  27. 26           * boot_action == BOOT_API_CTX_BOOT_ACTION_WAKEUP_CSTANDBY7 Y4 s% G. U! D6 _8 v& |0 r
  28. 27      * take values from defines above 'BOOT_API_CTX_CSTBY_EXIT_STATUS_XXX'5 I2 [( t6 y5 H8 h7 i9 T
  29. 28           * depending on encountered situation
    # V3 I: P3 A& q$ ~) z$ K3 @
  30. 29           */2 K: U$ d- s$ i3 ?: D) n' q! o
  31. 30          uint32_t cstby_exit_status;
    & E$ W+ p* O/ R1 r0 \* m
  32. 31          uint32_t auth_status;
    ' ~8 s% d) t# V& U  l
  33. 32 $ x; k5 A3 w3 C2 R. p# U$ [
  34. 33          /*
    6 X& [5 Y* U9 S( ~
  35. 34           * Pointers to bootROM External Secure Services. D# ^  W! P1 {, ]1 T4 v
  36. 35           * - ECDSA check key2 Z' \5 m7 Y* ?0 Y7 F
  37. 36           * - ECDSA verify signature
    " q( K; |9 k' b  y3 U+ E9 A
  38. 37           * - ECDSA verify signature and go
    % B% l6 l9 N$ }( ]& k
  39. 38           */
    + o/ _' U/ l5 I1 B
  40. 39          uint32_t (*bootrom_ecdsa_check_key)(uint8_t *pubkey_in,
    - c- T7 p+ C8 J4 E/ }% f9 l; W7 m
  41. 40                      uint8_t *pubkey_out);) k" Q! j  F* C/ k3 J2 |
  42. 41          uint32_t (*bootrom_ecdsa_verify_signature)(uint8_t *hash_in," f4 E9 h  \  ?3 c8 d! A
  43. 42                         uint8_t *pubkey_in,' Z1 G6 J1 v9 `8 b, P
  44. 43                         uint8_t *signature,' K, F& [9 ]+ g2 B4 s6 Q8 C
  45. 44                         uint32_t ecc_algo);
    * e# g, I- o9 e# S+ Y6 `, C' S
  46. 45          uint32_t (*bootrom_ecdsa_verify_and_go)(uint8_t *hash_in,) h" J- ^: Z: O
  47. 46                      uint8_t *pub_key_in,% G4 d0 v% ?' \1 j, X: C' R! E5 Z
  48. 47                      uint8_t *signature,. B: R( @9 _2 U! m. f+ w1 p! G6 |
  49. 48                      uint32_t ecc_algo,
    : d% l/ P* k! K7 _) B
  50. 49                      uint32_t *entry_in);
    + p! S. I" z. F1 N& ?
  51. 50
    - ], |! e! c- u% X. d
  52. 51          /*
    % o: `8 `1 H2 y" \9 r
  53. 52           * Information specific to an SD boot
    . F  E) [( X2 T3 u9 ~% e& j: W- Y5 {
  54. 53           * Updated each time an SD boot is at least attempted,
    4 I6 Q& j% C) K8 I
  55. 54           * even if not successful
    7 f; K$ L! K! n
  56. 55           * Note : This is useful to understand why an SD boot failed
    ( Z9 T/ Q: `( |
  57. 56           * in particular
    # R- ?! V1 n2 D2 f2 B2 N. p
  58. 57           */
    . f% l* y$ Q8 ]! @
  59. 58          uint32_t sd_err_internal_timeout_cnt;
    " ~8 ~$ ?: m- N6 g9 C
  60. 59          uint32_t sd_err_dcrc_fail_cnt;
    % x& l3 B' M5 U4 c% H+ W5 j3 K
  61. 60          uint32_t sd_err_dtimeout_cnt;2 c% I8 K8 p( L& l) }
  62. 61          uint32_t sd_err_ctimeout_cnt;
    & }& P( r5 J, ^! `( `1 |
  63. 62          uint32_t sd_err_ccrc_fail_cnt;' O  v1 m4 Y. o( ^* G  p  ^5 _# Z5 D9 z
  64. 63          uint32_t sd_overall_retry_cnt;; x) F. |, |3 ^
  65. 64                  /*" t( E% q1 ^+ n3 t: p" s
  66. 65           * Information specific to an eMMC boot3 Y9 f$ \$ g" N+ l! J
  67. 66           * Updated each time an eMMC boot is at least attempted,
    6 h: R. ?) e, v* R! H: t5 ]
  68. 67           * even if not successful
      `; y1 |  ]+ o' d' G/ F
  69. 68           * Note : This is useful to understand why an eMMC boot failed. N" D  N% A; Z% o3 K: t
  70. 69          * in particular6 @  K! }# U5 E
  71. 70           */
    ' b2 {: p( e) C4 G
  72. 71          uint32_t emmc_xfer_status;; P9 X! {7 c8 B. W7 o
  73. 72          uint32_t emmc_error_status;. I0 ~, {2 u& l( f
  74. 73          uint32_t emmc_nbbytes_rxcopied_tosysram_download_area;
    ; _" z" T* Y+ v3 h: ~
  75. 74          uint32_t hse_clock_value_in_hz;9 C% t: {" F+ g! W% S- V6 X
  76. 75          /** }, c/ K- R" R. W" ~& }
  77. 76           * Boot partition :5 `1 [8 j: ~2 b+ ?4 R2 x$ w
  78. 77           * ie FSBL partition on which the boot was successful
    . {2 g' {+ z& P3 g
  79. 78           */' M/ _6 @* O; O4 M3 U( p* W% h$ b
  80. 79          uint32_t boot_partition_used_toboot;' \9 b9 h) J1 E7 D  W6 A8 Y
  81. 80          /*! E) G$ H: t2 y$ Y: R) Q( Q
  82. 81           * Address of SSP configuration structure :
    & i4 M6 B7 O. z* f4 M
  83. 82           * given and defined by bootROM
    * @/ S3 `" b1 U1 L3 z  ~
  84. 83           * and used by FSBL. The structure is of type# ^, v! Q+ Z3 }5 T
  85. 84           * 'boot_api_ssp_config_t'
    5 G: U5 S: S1 X3 _/ E; [
  86. 85           */
    , Z' v5 q$ c5 \* Z7 c( B
  87. 86          boot_api_ssp_config_t *p_ssp_config;
    ' m' P) R; c- r" O: k6 W& b( a' q
  88. 87          /*1 E0 l( Z' n  ^
  89. 88           * boot context field containing bootROM updated SSP Status4 f5 P4 s% P/ ^: z. x
  90. 89           * Values can be of type BOOT_API_CTX_SSP_STATUS_XXX
    & j0 y- Y7 L% y4 `
  91. 90           */
    3 K  b; D; L. F
  92. 91          uint32_t    ssp_status;
    # k% [4 x0 k* [0 D
  93. 92
    3 d% ]; G+ V* U7 \4 m7 S8 O- W
  94. 93          /* Pointer on ROM constant containing ROM information */
    % {& V/ C& t# q' C0 `2 `% b
  95. 94          const boot_api_rom_version_info_t *p_rom_version_info;" D) Z4 X1 c$ C9 H
  96. 95 : V1 K8 O# Q" B( O# F* W
  97. 96 }         __packed boot_api_context_t;
复制代码

1 n& n# e+ ~9 `; w+ `2 W2 Pboot_api_context_t结构体目前不需要去研究,后面学习TF-A的时候根据实际情况在看是否有必要学习。
, V! E! d5 z! _3 Y; ^5.2.3 串行启动6 m2 g  T; X1 w# h- |. |0 }: d
当我们设置BOOT2~BOOT0为串行启动,也就是从USB或UART启动的时候就会进入此模式。当选择串行启动以后ROM代码就会并行扫描所有可以启动的UART以及USB OTG接口。当扫描到某个活动的串行接口以后,ROM代码就会使用此串行接口,并且忽略掉其他的串行接口。
1 K2 S7 l7 Y  O* v1、USB启动
$ S' Z1 H8 ]2 p) q4 ?# p: f, v内部ROM代码支持USB OTG启动,我们一般使用STM32CubeProgrammer软件通过USB OTG接口来向STM32MP1烧写系统。USB OTG需要一个48M和60M的时钟,这两个时钟由HSE生成。ROM代码支持的HSE时钟值如下:3 M3 v1 [  A/ H: U6 L
8, 10, 12, 14, 16, 20, 24, 25, 26, 28, 32, 36, 40, 48 MHz0 M7 s  o7 c7 ?0 o$ i) \1 \! ]
正点原子STM32MP157开发板使用24M有源晶振作为HSE时钟源。我们可以通过设置OTP来更改ROM代码的HSE晶振大小,设置如表5.2.3.1所示:
. G7 l3 H- B, T8 v: Y4 F: dOTP WORD 3值
' ~. X+ W! G5 G( I3 i; w(2bit) 描述
) b8 f. P  ~9 C  U2 r00 默认模式,ROM代码自动检测HSE频率,HSE频率必须为8, 10, 12, 14, 16, 20, 24, 25, 26, 28, 32, 36, 40或 48MHz,如果没有检测到的话就直接认为是24MHz8 f, v- @. I/ X
01 HSE=24MHz
- B, v/ E) {# o10 HSE=25MHz
% R# B9 c9 X! w) d9 I& t5 \11 HSE=26MHz" H) x) p* R/ {! u  I* {& Q6 R0 d
表5.2.3.1 HSE时钟OTP设置表; ]8 `/ ^+ x6 t
从表可以看出,默认情况下HSE选择24MHz,虽然可以通过修改OTP来更改HSE,但是强烈不建议!因为OTP只能修改一次,一旦修改错误芯片就废了!所以大家在自己做核心板的时候外部HSE时钟最好选择24MHz,不要特立独行!
) v7 D  l+ c5 |8 k5 n1 d: o2、UART启动
( B  i; f2 \8 Z1 i) [如果要送UART启动,也就是通过UART烧写系统,那么只能使用USART2、USART3 UART4、UART5、USART6、UART7或UART8,此时串口工作模式为:1位起始位、8位数据位、偶校验、1位停止位、波特率115200。
: C$ O% x# Y# G1 a2 e: }由于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所示:: U9 h) O! c8 M7 s# i' b+ D8 L% C" l( W
串口 串口引脚 所使用的IO 复用编号(AF)$ U; D& R2 i6 L$ e
USART2 USART2_RX PA3 AF07! p1 q. Z- H( m/ T
USART2_TX PA2 AF07
& r/ e; W5 d, c* s0 C7 ]; N& CUSART3 USART3_RX PB12 AF08: d* L! m: R/ D0 I+ |  Z
USART3_TX PB10 AF07
; E7 N6 ^) [  CUART4 UART4_RX PB2 AF084 b  \: {8 s8 `- X" g% T% X
UART4_TX PG11 AF06
3 J9 T* W% T3 o  fUART5 UART5_RX PB5 AF12# f1 B7 s4 |/ j. v" R; R8 V$ L( O
UART5_TX PB13 AF14
+ K1 B" R1 F3 hUSART6 USART6_RX PC7 AF07
' u3 u+ a2 j/ k3 w; V" CUSART6_TX PC6 AF074 ]5 d/ K; B& Y& A/ D/ b
UART7 UART7_RX PF6 AF076 k+ A# K; j5 g$ r' S; g% R! a# H9 f3 h
UART7_TX PF7 AF07
1 T& }5 Z% C8 C5 B6 m2 P; o, aUART8 UART8_RX PE0 AF08
/ q, \4 k8 B: C( X; QUART8_TX PE1 AF08
+ T4 c$ r, F4 S+ c8 e! ]1 L表5.2.3.2 ROM代码串口默认IO$ I: Q' ^7 [  @4 V
大家在做板子的时候,如果要使用串口启动,那么相关串口IO一定要参考表5.2.3.2中定义的IO引脚,比如正点原子开发板UART4的RX引脚使用PB2,TX引脚使用PG11。
- ^* d9 ^" L- [. s1 Y3 S5.3 Flash设备启动要求
7 D% `/ P" c5 vSTM32MP1支持从SD、EMMC、NAND或NOR等Flash设备启动,但是不同的Flash设备在启动的时候有不同的要求。linux系统他不像单片机那样,就一个bin文件,烧写进去就可以启动并运行,linux系统自身编译出来就是一个镜像文件,但是这个镜像文件要运行是需要一大堆的“小弟”来辅助。比如需要uboot来启动,启动以后还需要根文件系统(rootfs),传统的嵌入式linux有三巨头:uboot、kernel和rootfs,但是对于STM32MP1而言,由多了几个“小弟”,比如TF-A、TEE、vendorfs等,所有这一大堆构成了最终的系统镜像。系统镜像是要烧写到Flash设备中的,这些不同的文件肯定要按照一定的要求,分门别类的烧写,一个萝卜一个坑,TF-A应该放到哪里、uboot应该放到哪里等等。
; W3 T7 F- z$ b7 X; t9 Z& ~针对Flash设备,可以通过创建不同的分区来存放不同的文件,ST针对STM32MP1系列给出了官方分区建议,这些建议包含了Flash分区数量、分区最小空间、分区存放的内容等,如表5.3.1所示:3 w6 W* B: X8 S3 X
尺寸 分区 描述
0 e) ]0 W( `( e3 C. k. }" M3 |256KB~512KB fsbl 第一阶段启动代码,此分区存放TF-A或者uboot的SPL部分,如果写A7裸机例程的话此分区也用来存放裸机代码。% y3 \+ z* W& {) ?5 K
2MB ssbl 第二阶段启动代码,一般是uboot,如果uboot使用设备树的话,设备树添加到后面。+ Z) d+ z  c$ S* }4 R. J
64MB bootfs boot文件分区,可以存放如下内容:
8 L2 k, d# d" N" w· init ram文件系统,可以将此文件系统拷贝到RAM中,在linux内核挂载正式根文件系统之前可以使用init ram文件系统。# ~! ~8 B# V$ M( `$ E4 H
·linux内核设备树
3 o. a- I, Z: H0 t6 J·linux内核7 v7 p* s5 r+ A* p
·uboot显示的启动界面
# ^: S) T3 D: [: ]& P- d·uboot发行配置文件extlinux.conf& ]4 k5 b7 d8 k/ }
16MB vendorfs 此分区存放第三方的版权信息,确保它们不会受到任何开源许可的污染,比如GPL V3。
3 T6 k/ u7 V0 o9 ~/ A( I' X768MB rootfs linux根文件系统。7 j  w- I; q8 y- p
剩余空间 userfs 用户自行使用的剩余空间' p3 |, d4 w# J" _9 e" @6 L; K* q( L
表5.3.1 ST官方的Flash分区建议
6 a' ~* k1 P7 |# a4 [  R5.3.1 从NAND启动& [& m  f( S- g! _( h8 z& e# k" _
NAND前几个块(block)里面包含了多份FSBL,ROM代码会从第一个块开始扫描,并且加载第一个有效块里面的FSBL。ROM代码支持并行NAND和串行NAND,并行NAND连接到FMC总线上,串行NAND连接到QSPI上。
2 K7 R. u: n  f+ _& CROM代码支持的并行NAND要求如表5.3.1.1所示:2 ?9 w! k6 Q9 C& t/ T0 G/ `
块大小(KB) 页大小(KB) 数据宽度 ECC(bit数和编码)1 ~9 d1 l) J; Z/ w- A2 ^  _! [
128 2 8,16 4(bch),8(bch),1(hamming)
+ ~; g" Z4 r# J# Z256 4 8,16 4(bch),8(bch),1(hamming)
+ I4 g' G" d/ u: J! \0 c512 4 8,16 4(bch),8(bch),1(hamming)  k* R1 j  v8 L5 l0 m/ A
512 8 8,16 4(bch),8(bch),1(hamming)
- W' }0 Y5 h7 S& F& ^8 D9 k9 j. a' ]表5.3.1.1 并行NAND参数要求
- D* t+ E; {/ O% b4 C# @ROM代码支持串行NAND要去如表5.3.1.2所示:
& e9 o5 I9 N3 g/ O) C/ I- Y4 @% w4 ], ~块大小(KB) 页大小(KB)
0 \, l$ H! K( }$ q128 2
0 H" T# f* ?8 L: t/ i256 47 i- O8 w. w7 q, o- @; U- X
512 48 }1 J: w- C& N
512 83 D9 Z0 `# m' g) T% [6 _
表5.3.1.2 串行NAND参数要求
6 _/ e2 Y( d2 }# K# }所以大家在制作STM32MP1硬件的时候,NAND Flash选型一定要符合表5.3.1.1中的参数。
% P; ~5 ~: k7 A! S3 {  A8 v9 |" s9 x5.3.2 从EMMC启动
0 Y+ o/ P! R/ t# @: REMMC在物理结构上有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所示:
" L! J4 s) |2 i( M2 v1 s4 S3 G6 u1 e: s+ \6 k2 _
0413c8cadfdc459e82052f346843b543.png
; ~  h7 Q+ J8 q
5 {% {6 J: d; c* U" I  Q+ l图5.3.2.1 三星KLM系列EMMC分区结构
) G2 K7 j8 N2 a! P8 Z我们一般知道和常用的就是UDA分区,也就是用户数据区域,很少会关心boot1、boot2这样的分区。boot1、boot2、RPMB这三个分区代销是固定的,用户不能修改,boot1、boot2分区存在的意义就是用于引导系统。正点原子STM32MP157开发板所使用的EMMC型号为KLM8G1GETF,这是三星的一颗8GB EMMC 5.1芯片,boot1、boot2和RPMB分区大小如图5.3.2.2所示:
8 w9 K' M  Q2 e7 M) K7 g0 y/ V0 b0 d) r' O! l+ k0 S" P: m% W
10a7f98b0be34cdb89befd54b34829ee.png " U& c8 h3 }! v2 d/ x& ~* S

$ E# ~' H" A6 L5 d( a2 U" f, a/ c图5.3.2.2 KLM系列EMMC分区
  w* [# C8 |9 t  D- v从图5.3.2.2中可以看出,对于三星的8GB的EMMC而言,boot1和boot2分区默认大小为4096KB,RPMB为512KB。
8 g- [- w6 G' u% ^9 p1 u1 OST会使用EMMC的boot1和boot2这两个分区作为FSBL,但是同一时间只有一个有效,ROM代码会加载有效的哪个FSBL。ROM代码使用单bit模式来操作EMMC,默认情况下ROM代码使用连接到SDMMC2上的EMMC,可以通过OTP来修改EMMC所使用的SDMMC接口,但是这里不建议!7 P; a3 e2 h- k# a5 H' |
5.3.3 从SD卡启动
5 C5 m$ D( H+ c5 L6 J( E0 I( u- KSD卡也包含两个FSBL,但是SD卡没有boot1和boot2这样的物理分区。ROM代码默认尝试加载第一个FSBL,如果第一个FSBL加载失败,那么ROM代码就会加载第二个FSBL。' A7 _3 R# T' x  r7 k9 |% h0 N
ROM代码首先在SD卡上查找GPT分区,如果找到的话就查找名字以“fsbl”开始的两个FSBL分区。如果没有找到GPT分区的话就直接根据物理地址查找两个FSBL,第一个FSBL的起始偏移地址为LBA34,地址位34512=17408=0X4400,所以第一个FSBL的起始地址为0X4400。第二个FSBL的起始偏移地址为LBA546,地址为 546512=279552=0X44400,所以第二个FSBL的起始地址为0X44400。2 D& e, N. `% w5 P( z9 n1 [
ROM代码默认也是使用单bit模式操作SD卡,并且默认使用连接到SDMMC1接口上的SD卡。
4 r$ z" w/ ]9 K4 N) d7 O5.4 STM32MP1二进制头部信息
6 u" N# J/ A/ p* i  C7 F5 c0 Q前面讲了STM32MP1内部的ROM代码会先读取FSBL代码,一般是TF-A或者Uboot的SPL,也可以是A7裸机代码。比如TF-A我们之间编译生成二进制bin文件,但是这个bin文件不能直接拿来用,需要在前面添加一段头部信息,这段头部信息也包含了鉴权内容。加入头部信息以后的FSBL代码结构如图5.4.1所示:( B: C0 \0 `: W' X( z

7 A5 L5 [% A1 E: l5 f4 { cff5b4eb064c42cab0563ed3dc821b07.png 6 {$ c1 S( _, t( }. T

$ V/ j# u$ k: H5 L8 H图5.4.1 FSBL镜像组织结构0 I+ L+ \* g+ R3 L& ]  [6 a1 H
头部信息一共是256字节,这256个字节的头部信息具体含义如表5.4.1所示:# w) M1 ?$ d/ L4 ]: ]# B! ^
名字 长度 偏移(B) 描述; @/ C2 J7 f. u$ S1 C
Magic number 32bits 0 魔术数,4字节,为‘S’、‘T’、‘M’和0X32的组合,固定为0X53544D32。注意,这4个字节是大端模式,也就是高字节存储在低地址处,低字节数据在高地址处。+ v" h" q- h& |) q+ j: T+ W/ k
Image signature 512bits 4 ECDSA签名,用于镜像鉴权。! N* ~; K1 j% V" {' @# Z
Image checksum 32bits 68 镜像校验和。
2 H5 z5 D4 }# x" ]5 GHeader version 32bits 72 头部版本信息,V1.0的话为0X00010000,含义:
$ \/ C, l, u) R! `Byte0:保留( O5 W6 K% {* g* V( ^( c
Byte1:主版本号为0X01。
6 E5 t+ W7 p! ^2 HByte2:次版本号为0X00。8 v, c; \, U3 X" `: ?
Byte3:保留
, t2 B" Y3 ]( G2 O* b" KImage length 32bits 76 镜像长度,不包含头部,单位为字节。
7 y" C. R; E7 v) d! @4 e7 ~Image entry Point 32bits 80 镜像入口地址。3 r& j: X! T& n$ G4 G- v1 k
Reserved1 32bits 84 保留。8 R  |" d0 N  ~
Load address 32bits 88 镜像加载地址,ROM代码不使用此地址。
2 q6 [6 h9 m! y# n. dReserved2 32bits 92 保留。
: R4 @* l+ U' W3 T# K/ w. [Version number 32bits 96 镜像版本信息。
! Y; P9 T2 x5 x5 q% T: f: F9 uOption flags 32bits 100 可选字段,b0=1的话表示不需要验证签名。6 t+ f$ q" J4 [0 n' {
ECDSA algorithm 32bits 104 ECDSA算法,1:P-256 NIST;2:brainpool 2562 S. l' M! d, H" B. q
ECDSA pubilc key 512bits 108 ECDSA公共秘钥,签名的时候使用。
3 O2 U" x3 U9 o9 [: F$ K2 j$ PPadding 83Bytes 172 保留的填充区域,必须全部为0
9 F" j& D( v5 K3 |0 wBinary type 1Byte 255 二进制文件类型:' a+ D" b1 U) b# r! a
0X00:U-Boot4 q1 T, m; t2 g/ S: x. D$ L( {
0X10-0X1F:TF-A
, {( q3 K% [' q: `" w# Q0X20-0X2F:OPTEE
) p9 z2 r% U0 h' q  B. S6 r0X30:Copro
" R' j" c; U6 c! Z6 |$ c2 i- H" [表5.4.1 头部信息含义0 V, Y0 y% n- O0 G0 N+ `
头部信息不需要我们自己手动添加,我们在编译ST官方提供的TF-A或者Uboot的时候会自动添加,因为ST提供了个名为“stm32image”的工具专门用于在bin文件前面添加头部信息。我们已经从TF-A源码中提取出来了stm32image并放到了开发板光盘中,路径为:开发板光盘5、开发工具2、ST官方开发工具stm32imagestm32image.c。在编写A7裸机的时候需要自己使用stm32image工具在bin文件前面添加头部信息,stm32image是在Ubuntu下运行的,所以需要先编译,将stm32image.c发送到Ubuntu下,然后输入如下命令编译:. G2 |3 J4 q. v8 Y
gcc stm32image.c -o stm32image
7 v6 K* |( |7 I9 i4 s编译成功以后就会生成一个名为stm32image的可执行文件,如不5.4.2所示:- T, K% m  m) A$ g% D6 s
" x% G# y8 Y  v% E7 u
6241fd2c43e2486faed955acab75dd62.png
" ]! ?) L, D3 G8 T$ [+ b0 G& A. k
% M& w1 q& W( g) m2 R6 m! d; B图5.4.2 stm32image工具; a5 T% V2 d# c8 v' r! l7 |# Z# A
运行图5.4.2编译出来的stm32image工具,输入“-s”选项可以查看使用方法,如图5.4.3所示:! _; ]  t, H* K  Z) ?3 w4 O4 G

% u( w5 P3 v& ^ af748a43c7de4babbbe1541cebe0cd87.png 1 l3 w' B$ k6 F

2 ^# j2 n- c1 m4 V/ k3 A. U图5.4.3 stm32image使用方法5 N! r- p# E" p* `
从图5.4.3可以看出,stm32image在使用的时候需要搭配一系列的参数:' S* k# p( U# j: k/ j: U, H
-s:指定源文件。/ T! g& j4 e! ~0 M$ F" r
-d:生成的目标文件。3 H: c' I- B7 r- R7 ^/ s: j  [
-l:加载地址。& h% ^! U! g3 b) ?
-e:入口地址。
- I( C- e; K, J6 ~-m:出版本号。) w" [) B7 @. o1 w
-n:次版本号。
+ S  M# O( B4 l* [) f) `大家在开发板光盘里面找到正点原子出厂的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所示:
; `2 ^/ A" ], c4 H+ y6 S. G+ u6 P! `$ ~. O4 B$ [
be0523a1e7e44ffe93e61e0b0b28efe5.png   p1 Z8 z+ x% M" }8 O
9 Y  ]; u' W0 k. Y9 o
图5.4.4 tf-a-stm32mp157d-atk-trusted.stm32文件内容
' w- u2 p" F. ~图5.4.4就是tf-a-stm32mp157d-atk-trusted.stm32文件原始数据,其中前256个字节就是头部信息。这里我们根据图5.4.4中的内容,分析一下tf-a头部信息中几个比较重要的参数:
$ O) n+ ]& t4 cMagic number:起始偏移地址为0,长度为4个字节,值依次为:0X53、0X54、0X4D、0X32,合起来就是0X53544D32,这个就是表5.4.1中的魔术数,注意这四个字节的顺序是大端模式。  O+ m% b4 D) Z: N4 ]3 |0 o" b
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。
" m* m6 i% Z# W. h5 aImage length:起始偏移地址为76,长度为4个字节,也就是图76~79这4个字节的数据,为:0X40、0XB0、0X03和0X00,按照小端模式拼起来就是0X0003B040=241728≈236.1KB,说明此TF-A的bin镜像大小为236.1KB。
1 M  v; v% f+ c8 h( P6 qImage entry Point:起始偏移地址为80,长度为4个字节,也就是图80~83这4个字节的数据,为:0X00、0X60、0XFD和0X2F,按照小端模式拼起来就是0X2FFD6000,说明入口地址为0X2FFD6000。
) [  r3 \1 X9 e9 E. _Load address:起始偏移地址为88,长度为4个字节,也就是图88~91这4个字节的数据,为:0X00、0X25、0XFC和0X2F,按照小端模式拼起来就是0X2FFC2500,说明加载地址为0X2FFC2500,这个不正是我们前面在5.2.2小节中分析的FSBL镜像起始地址。4 N9 W! b. T9 }# c2 L: o
Binary type:起始偏移地址为255,也就是最后一个字节,为0X10,表示当前二进制文件是TF-A。: e) x/ U4 }5 S6 `  h
5.5 STM23MP1 Linux系统启动过程
" t" A: L0 O; Q* b' T, x' K9 ]4 H前面已经对STM32MP1的启动流程做了详细的讲解,STM32MP1是面向Linux领域的,因此所以的这些启动过程都是为了启动Linux内核。STM32MP1xil启动Linux内核的流程如图5.5.1所示:# m' W  b% Y: {, v) y

, [- m  L3 @! j( N/ Z5 ~ 3f193ad3432d445e90152a1bdfe3e5a8.png
  I  ^. d8 C  j. j. W+ G7 H
* s1 I, j* a7 X4 L2 e图5.5.1 MP1 Linux启动流程
  ]. b; _/ S5 E5 W4 h/ N从图5.5.1可以看出,STM32MP1启动linux内核一共分为5个步骤,我们依次来看一下这五个步骤的内容:0 G& X- E: b% }/ w. a' a
①、ROM代码
: B& x) G) _7 k8 J6 m前面说了很多次了,这是ST自己编写的代码,在STM32MP1出厂的时候就已经烧写进去的,不能被修改的。ROM代码因为保存在STM32内部ROM里面,因此也就直接简单明了的叫做“ROM代码”了。它是处理器上电以后首先执行的程序,ROM代码的主要工作就是读取STM32MP1的BOOT引脚电平,然后根据电平判断当前启动设备,最后从选定的启动设备里面读取FSBL代码,并将FSBL代码放到对应的RAM空间。8 y, v1 O, Z! Q, X; w
现在很多产品对设备上运行的应用都提出了安全要求,从图5.5.1中可以看出,STM32MP1启动Linux内核的过程是一个链式结构:ROM CodeFSBLSSBLLinux kernelrootfs,系统启动的过程中要保证整个链式结构都是安全的。ROM代码作为第一链,首先要对FSBL代码进行鉴权,同样的,FSBL以及后面的每一链都要对下一个阶段的镜像进行鉴权,直到设备系统正确启动。' S) a* i2 W2 g
②、FSBL
4 u5 M% Q4 m. q; w* }FSBL代码初始化时钟树、初始化外部RAM控制器,也就是DDR。最终FSBL将SSBL加载到DDR里面并运行SSBL代码。
, g/ ^  [5 m7 c$ B2 D一般FSBL代码是TF-A或者Uboot的SPL代码,前面我们说了,也可以将FSBL换成我们自己编写的STM32MP1 A7内核裸机代码。
. j2 ?1 e# y/ A. b, w③、SSBL
1 n8 ~$ p/ ?, R由于SSBL代码运行在DDR里面,无需担心空间不够,因此SSBL代码的功能就可以做的很全面,比如使能USB、网络、显示等等。这样我们就可以在SSBL中灵活的加载linux内核,比如从Flash设备上读取,或者通过网络下载下载等,用户使用起来也非常的友好。SSBL一般是Uboot,用来启动Linux内核。2 l+ D; E% l4 i4 p; s5 \4 f
③、Linux内核
1 M4 i) Z, S5 WSSBL部分的Uboot就一个使命,启动Linux内核,Uboot会将Linux内核加载到DDR上并运行。Linux内核启动过程中会初始化板子上的各种外设。2 j+ F1 }+ [0 h1 Z2 W9 A2 u
④、Linux用户空间% o$ a+ ?/ Y+ @4 u& F( T7 n, l
系统启动的时候会通过init进程切换到用户空间,在这个过程中会初始化根文件系统里面的各种框架以及服务。
3 O* g' _8 A& ]# ^4 @5 \  l# u5 F————————————————7 f' y# h$ `( C
版权声明:正点原子
  s4 Z$ E4 B6 R; |( A+ @8 h2 G1 }8 d" m% t
7 v2 f# z9 h, J
收藏 评论0 发布时间:2022-9-21 15:49

举报

0个回答

所属标签

相似分享

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版