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

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

[复制链接]
STMCU小助手 发布时间:2022-9-21 15:49
5.1 STM32MP1启动模式% l5 x  ?. W0 @" M' q
STM32MP1支持从多种设备启动,比如EMMC、SD、NAND、NOR、USB、UART等。STM32MP1内部有一段ROM来存放ST自己编写的程序,这段ROM空间是不开放给用户使用的,仅供ST存放自己的ROM代码,ROM空间如图5.1.1所示:
9 V6 t/ T% |' a! D( ?$ P! n9 G( l- _$ z# s
1776aec2429d4a75ab3cdbc2588b4023.png
( K2 T. m; h  c3 b
! A' @% T, J+ O- c5 D图5.1.1 内部ROM空间
# y6 x; E5 i+ p& h; f- ?6 P8 {, V图5.1.1中CA7是Cortex-A7的缩写,可以看出A7内核有128KB的ROM空间,起始地址为0X00000000,STM32MP1上电以后会先运行这段ROM代码。STM32MP157有三个BOOT引脚:BOOT0~BOOT2,这三个BOOT引脚通过拉高/拉低来设置从哪种设备启动,正点原子STM32MP157开发板上的拨码开关就是控制这三个BOOT引脚的,大家可以打开底板原理图,找到BOOT相关原理图,如图5.1.2所示:
( h7 y' ]; g& k5 g8 q- l
- T/ }- R7 g  a; ]( S% F, y5 {0 i 0d87acc45c4244fba0b868adf62b8f0f.png
7 |- Q7 q9 R/ M$ K' g/ |; e
( M7 x  f0 x1 _$ U7 q7 b6 \$ f图5.1.2 BOOT原理图
" X4 ~0 o# P! `2 e+ Q" ~6 t8 h从图5.1.1可以看出,当BOOT0~BOOT1拨到“ON”的时候就会接到3.3V上,此时就是 逻辑1,拨到OFF的时候BOOT0~BOOT1就悬空(也可以外接下拉电阻),此时就是逻辑0(悬空或接地都为逻辑0),三个引脚不同电平对应的启动模式,如表5.1.1所示:
9 T) a) n  V$ }. N* y5 qBOOT2 BOOT1 BOOT0 启动模式 描述
8 I  e$ }! _0 ]- B+ d' O3 s% ]- E0 0 0 UART/USB - USART2/3/6、UART4/5/6/7/8。
3 \% A8 }5 T: b+ k2 H: s  ^+ C" q6 H5 `" K8 o
USB接口。
! s4 T% H5 W) j3 y  z$ w0 X5 O0 0 1 串行NOR 串行NOR或者QUADSPI
# N- i- O0 v0 f6 p) V5 _0 1 0 EMMC 连接到SDMMC2上的EMMC设备。
1 O: U: L, d8 c; f1 n0 1 1 并行NAND 连接到FMC上的并行SLC NAND# K4 ~9 D1 o( w. A0 y5 V1 E8 e: `( [
1 0 0 MCU 启动内部的M4内核。
8 K. }( X2 ?7 o$ G) I4 h! ^( I# l1 0 1 SD 连接到SDMMC1上的SD卡。- ~3 a9 }0 x- R7 u, S4 H# c( K
1 1 0 UART/USB 和000效果一样,从UART/USB启动。
/ V2 R& q+ W' U1 1 1 串行NAND 连接到QUADSPI上的串行NAND8 W* U% v* W7 D8 A
表5.1.1 启动模式2 h3 ^+ e3 o. V; q
注:STM32MP1的引脚具有复用功能,因此一个外设有很多不同引脚可以使用,比如SDMMC2的D0引脚就可以使用PB14或PE6。STM32MP1内部ROM代码肯定会有个默认的引脚,比如内部默认使用PB14,如果你自己绘制的板子用了PE6,那么就会出问题。当然了,可以通过修改OTP来设置启动设备所使用的引脚,但是笔者强烈不建议用户修改OTP,因为OTP只能改一次,一旦修改错误芯片就废了。所以大家在绘制自己板子的时候启动设备所占用的引脚一定要和ST官方的开发板一致!
# Q, d5 a7 w9 |1 J正点原子STM32MP157开发板上通过拨码开关来选择启动模式,开发板上有丝印提示如何选择不同的启动方式,如图5.1.3所示:
; G8 {5 h: }9 I5 |' e1 {; K 73d5dc2ae2b44eedb98d7a4e7938b4c2.png
6 F7 _: s4 T" W: h/ |
6 N" Z7 T; D8 j( K5 B图5.1.3 开发板拨码开关设置+ h# W$ ^: G" O
大家可以根据开发板上的丝印来选择启动模式,拨上去为ON,也就是‘1’,拨下来就是OFF,也就是‘0’。
  X) @: |4 m, _7 K! X$ _) ]但是要注意一点,开发板拨码开关3个开关从左到右依次对应:BOOT0,BOOT1和BOOT2,因此图5.1.2中丝印从左到右依次是BOOT0、BOOT1和BOOT2,顺序刚好和表5.1.1是相反的,表5.1.1中从左到右是BOOT2、BOOT1和BOOT0,所以大家看到开发板上丝印和ST数据手册里面不同的时候不要以为是丝印标错了。
/ ~8 }; E3 T& K& e6 q3 ^4 s1 b; z, x1 }, V
5.2 STM32MP1启动流程详解
6 k6 f5 r0 {- S& O- z7 G上一小节我们说了,STM32MP1内部有一段ST自己编写的ROM代码,这段ROM代码上电以后就会自动运行,ROM代码会读取BOOT0~BOOT2这三个引脚电平,获取启动模式信息,比如读取到是从EMMC启动的,那么ROM代码就会从EMMC中读取相关程序。说起来很简单,但是实际操作比较复杂,本节我们就来详细学习一下这个启动过程。. V* ^0 c, \  ~+ d
4 `; o6 U& l1 e9 r( p# x1 }" O
5.2.1 内部ROM代码
. {  }- o# I7 |. J1 A3 C8 [5 w内部ROM代码支持如下功能:
. u" k% n- }# Y: @  y①、Secure boot(安全启动),不管是串行启动还是从Flash设备启动。
. B- t/ v5 v7 k' y6 M1 S②、Engineering boot(工程启动?),当BOOT2~BOOT0设置为100的时候,我们就可以通过STLINK访问A7或者M4内核。一般是通过此方法来调试M4内核代码。
% t! a3 s0 [; C6 m1 }2 I* ?# ^③、Secondary core boot(第二个内核启动),复位以后,STM32MP157的每个A7内核都会启动,并且运行相同的指令。内部ROM代码会分离执行流,只有Core0才会运行ROM代码,另外一个内核会处于一个死循环状态,等待应用程序发送信号来进行下一步操作。这个信号是由SGI(软中断)和另外两个BACKUP寄存器:MAGIC_NUMBER、BRANCH_ADDRESS组成的。如果要启动Core1,运行在Core0的应用程序需要:
' ?5 P( g# e: b( t$ ~* y; S% B& ]· 将跳转地址写入BRANCH_ADDRESS寄存器。, O6 I0 u( c6 G( a( q
· 将0xCA7FACE1这个值写入到MAGIC_NUMBER寄存器。) B3 o; g  I2 j% C# `& O
· 向Core1和发送SGI中断。0 }  k2 y3 f& ?6 C3 I  n
总结一下,只要你不自己开启第二个核,那么由于内部ROM代码的作用,此时STM32MP157就相当于单核A7。这样有利于我们编写的STM32MP157的A7裸机例程,因为无需考虑多核情况。5 b2 M. m& L! }8 z
④、RMA boot,RMA是Return Material Authorization的缩写,笔者没研究过此种启动方式。
% u+ h. L! o9 \+ b6 B⑤、低功耗唤醒。
0 _0 @* S; A; U/ N: P  S⑥、提供安全相关服务。
3 B0 C6 z0 n4 i2 f内部ROM启动流程如图5.2.1.1所示:/ L( i0 R0 m$ y* R9 |. [7 {& }- H
( W7 s2 W& _9 x3 J
e491bd7c0d1f4a86b14d4a52a1d71607.png
( o& N& Z7 {7 B6 f* S8 e  r. i- {  {1 J+ G0 T6 c
图5.2.1.1 ROM流程图. L9 T6 R1 Y' a7 R
我们只关心图5.2.1.1中红色部分,因为这个是最常用的启动流程,也就是上电或复位以后运行流程:$ }# R$ r; C3 L) S
①、首先检查当前是不是CPU0运行,如果不是的话就启动CPU1,正常上电肯定是CPU0在运行。1 \* R5 |% ?9 I: ^9 m$ ~
②、如果是CPU0在运行,检查复位原因。* S4 {! q7 V: P+ ?& C$ a6 I5 l) ^
③、检查是否为退出Standby而导致的复位,如果不是的话就进入RMA检查。
5 ^# P2 C/ ?" I0 _6 b) Q& M③、检查是否为RMA启动,不是的话是否为ENGI启动。
' C( P0 C% W2 ~2 d& P" ?* |( n7 ?& v④、如果不是ENGI启动的话直接进去冷启动。
; V8 e7 N4 F+ y⑤、进入冷启动以后就从flash中加载系统,并且进行鉴权,如果鉴权成功的话就运行系统。
4 y$ q% C! U8 @3 T& v% k: \9 n% ?
4 M. y) `3 |9 q( K. [5.2.2 安全启动' N1 p0 J) I& l$ W& L6 _8 O7 d; u# u
首先了解两个概念:( M) E4 A6 \/ }" e: R1 e0 \
FSBL:全称为First stage boot loader,也就是第一阶段启动文件。
8 z) g5 i+ A+ z7 m4 xSSBL:全称为Second stage boot loader,也就是第二阶段启动文件。0 m; H1 n4 x" D$ G' y0 `, i3 f
当我们设置好BOOT2~BOOT0,选择从外部Flash,比如EMMC、NAND或NOR等启动的时候就会进入安全启动流程。STM32MP157的安全启动流程比较复杂,这里我们就简单了解一下安全启动的基本流程:# C% Y7 q4 L" D$ H
①、首先ROM代码从选定的Flash设备中加载FSBL镜像文件,FSBL镜像就是ROM加载的第一个用户编写的可执行程序,一般是TF-A镜像,但是我们可以换成自己编写的程序,比如A7裸机代码。当然了,这个FSBL镜像是有要求的,不是简单的把bin文件丢过来就可以,而是需要在bin文件前面添加一个头部信息,否则内部ROM代码不知道如何处理这个bin文件,这个头部信息以后稍后讲解。' I" _9 v8 f3 R. K5 J
②、FSBL镜像加载以后需要对其进行鉴权。
# b" k6 a$ B! P" o& N# e0 N1 a③、如果鉴权成功,那么就会跳转到FSBL镜像入口地址,开始运行FSBL固件。
- b! a6 s# z; ]6 A) C1 w内部ROM首先从选定的Flash设备中读取FSBL镜像文件并运行,但是此时DDR还没有初始化,那么FSBL镜像在哪里运行呢?肯定是内部RAM啊,ST32MP1内部有256KB的SYSRAM,如图5.2.2.1所示:* b+ C2 i0 A+ C* v& Z
! ?, i( T# h/ z  C" w2 r* m
593caf145cdd4bcfb0c56ff390884673.png
' u, r0 N6 ~: \5 j
) z5 y9 n5 u7 I% a图5.2.2.1 STM32MP1内部SYSRAM; O5 H9 A. h; f" |+ z7 j
' }/ w1 _! J' ^+ e& X
从图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足够我们编写裸机例程了。) A7 K$ |. @) C
FSBL镜像鉴权成功以后,ROM代码会boot上下文的起始地址保存到R0寄存器,然后跳转到FSBL镜像的入口地址,这个入口地址会定义到头部里面,其实就是上面讲的0X2FFC2500。
5 C0 Z! t5 B# Q6 cboot上下文我们不需要管,但是还要简单讲一下,boot上下文会保存到SYSRAM的前512字节里面,boot上下文包含了boot信息,比如选定的boot设备,还有一些和安全启动鉴权有关的服务。结构体boot_api_context_t定义了boot上下文结构,boot_api_context_t结构体是定义在TF-A源码里面的(plat/st/stm32mp1/include/boot_api.h),内容如下所示:2 A. ?( F8 E* [" U; T

0 ~2 y# A; i: @( a
  1. 示例代码5.2.2.1 boot上下文结构
    ! T+ T: E- S7 T* X$ F, c0 C( \$ o2 I
  2. 1  typedef struct {6 F9 J# I8 x, x  [4 @: \1 |
  3. 2           /*
    8 M& l6 C% X1 x' k, @
  4. 3            * Boot interface used to boot : take values from defines. \: k, u% b5 g/ p/ W& x4 D( N9 \% d
  5. 4            * BOOT_API_CTX_BOOT_INTERFACE_SEL_XXX above3 K9 B1 y+ K: p2 q3 z3 U
  6. 5            */
    . k! F" F( V$ ]% [3 X# N9 R( [
  7. 6           uint16_t boot_interface_selected;4 h0 B" c; M2 P4 f9 U
  8. 7           uint16_t boot_interface_instance;! j0 Q/ v) c  b' Z2 i2 M# }' n2 u
  9. 8           uint32_t reserved1[12];+ [# C/ B1 [7 Y5 M4 W/ N+ \# @
  10. 9           uint32_t usb_context;
    9 K8 S# {+ y% q
  11. 10          uint32_t otp_afmux_values[3];" k$ r4 Q9 o# h7 @4 `' W' S
  12. 11          uint32_t reserved[2];( y  @* O2 `% u3 t4 l' H
  13. 12          /*
    - ?0 Z" ~  X* R  `+ D  R( b' `$ ^, v
  14. 13           * Log to boot context, what was the kind of boot action
    2 _3 g( e$ \; M1 A. W4 E
  15. 14           * takes values from defines BOOT_API_BOOT_ACTION_XXX above
    * y1 U% h  ~1 E& C6 Z7 _
  16. 15           */0 w7 @" u: c# P# c7 o1 `
  17. 16          uint32_t boot_action;3 u6 _( L/ i2 o2 Q9 J1 t6 r
  18. 17          /*
    $ F8 y& `6 |3 V4 {, z1 d+ \4 i
  19. 18            * STANDBY Exit status to be checked by FSBL in case. x" M$ i: N6 e2 E* k
  20. 19           * field 'boot_action' == BOOT_API_CTX_BOOT_ACTION_WAKEUP_STANDBY
    $ w. c/ }/ G- E% s/ R; K' a
  21. 20           * take values from defines above 'BOOT_API_CTX_STBY_EXIT_STATUS_XXX'
    4 N9 W5 t0 n1 o8 j
  22. 21           * depending on encountered situation
    : N* b* W9 q# r3 h+ k
  23. 22           */# Y4 t* |* N) `( A% `1 K9 C
  24. 23          uint32_t stby_exit_status;6 e' F8 N, d4 q7 S* M6 u$ ^
  25. 24          /*
    - o7 @' `1 L$ N5 c0 c6 I3 U& ?  ?
  26. 25           * CSTANDBY Exit status to be checked by FSBL in case
    - S2 o0 [7 S& s; O
  27. 26           * boot_action == BOOT_API_CTX_BOOT_ACTION_WAKEUP_CSTANDBY  i, x. U  o9 \0 W) J
  28. 27      * take values from defines above 'BOOT_API_CTX_CSTBY_EXIT_STATUS_XXX'1 P- I& n" x7 i
  29. 28           * depending on encountered situation
    2 d5 b! z4 n* z' q
  30. 29           */
    9 Q8 N1 h+ r. H8 _& d# ]! O
  31. 30          uint32_t cstby_exit_status;
    ' L( {+ z. k' Y
  32. 31          uint32_t auth_status;! b$ E) c' S  L, u" |/ _- |: f' X/ p
  33. 32
    / V! V5 @( s  F6 C
  34. 33          /*8 S  t0 x: @1 C6 f, h9 H$ q
  35. 34           * Pointers to bootROM External Secure Services* ?, _, N( \: L$ D
  36. 35           * - ECDSA check key& T7 s1 D. M  a7 k$ v
  37. 36           * - ECDSA verify signature/ g8 x2 f8 p- X$ I
  38. 37           * - ECDSA verify signature and go
    - ?( _9 l) }* C( N, m
  39. 38           */
    7 I' L" t# r. k. w( x. F
  40. 39          uint32_t (*bootrom_ecdsa_check_key)(uint8_t *pubkey_in,4 a2 i8 u7 _, T, J2 K
  41. 40                      uint8_t *pubkey_out);
    ) q9 {* f' b9 I# C" @- M
  42. 41          uint32_t (*bootrom_ecdsa_verify_signature)(uint8_t *hash_in,! }, c0 d/ B) W9 r
  43. 42                         uint8_t *pubkey_in,* s, Z& j1 p0 `( f3 F7 B# z9 [, g
  44. 43                         uint8_t *signature,
    . G5 a0 \" T3 \9 F* L' F
  45. 44                         uint32_t ecc_algo);
    * D* o0 a4 v% c
  46. 45          uint32_t (*bootrom_ecdsa_verify_and_go)(uint8_t *hash_in,
    , m- ~0 N" B1 Z8 `$ \0 ?' l! Y$ @
  47. 46                      uint8_t *pub_key_in,
    ! G- j1 k7 H  T) j8 ~; A, u/ Y
  48. 47                      uint8_t *signature,
    8 W9 j( m, k/ F% W5 o- u5 r
  49. 48                      uint32_t ecc_algo,
    7 W3 E& f' `+ ^) w
  50. 49                      uint32_t *entry_in);8 [2 ?8 y  S3 ]; R) E
  51. 50
    ! o+ p. b' J! i! Q% T- y: v( q
  52. 51          /*7 M9 r+ n# M! E) |; `+ O0 \4 w
  53. 52           * Information specific to an SD boot
    ! l. v; J$ p. O. G
  54. 53           * Updated each time an SD boot is at least attempted,
    ) {- h, ~2 I. D' B* {
  55. 54           * even if not successful5 ^: ]  V$ g1 D9 ]2 ^: d
  56. 55           * Note : This is useful to understand why an SD boot failed( h+ R- W9 G, ?& |7 b& P0 P& B
  57. 56           * in particular1 s# x: }, a$ `0 u
  58. 57           */
    ) R9 R' f' ]3 s5 [6 `; |! D+ g' a
  59. 58          uint32_t sd_err_internal_timeout_cnt;
    ( p& f. F1 P) }; [
  60. 59          uint32_t sd_err_dcrc_fail_cnt;( o& @2 {+ b- J" o
  61. 60          uint32_t sd_err_dtimeout_cnt;5 w4 M% v5 _6 W  b( V$ K; g
  62. 61          uint32_t sd_err_ctimeout_cnt;
    3 W: j1 P7 O$ @& _  Z4 x
  63. 62          uint32_t sd_err_ccrc_fail_cnt;
    6 q- K$ A; C/ P2 I
  64. 63          uint32_t sd_overall_retry_cnt;: t" u/ }  X& X, I8 K9 q
  65. 64                  /*" U& F1 H1 \) X0 D( a" u0 r3 y
  66. 65           * Information specific to an eMMC boot
    5 I6 }3 E& h& U7 `( Z6 m! H7 M& j
  67. 66           * Updated each time an eMMC boot is at least attempted,: z+ {; F" F, I7 }4 E- b- O
  68. 67           * even if not successful7 Q- |4 S. p% a: s9 T' c
  69. 68           * Note : This is useful to understand why an eMMC boot failed$ Z$ R) F- L* _
  70. 69          * in particular
    % C/ n+ a# W, F' @9 @( c
  71. 70           */' a. f5 ~0 K# g# N9 I) w! j8 e5 _. M
  72. 71          uint32_t emmc_xfer_status;
    ; `3 [2 C! }3 K& k& y
  73. 72          uint32_t emmc_error_status;& D+ |; B5 m" w5 f- @6 Q
  74. 73          uint32_t emmc_nbbytes_rxcopied_tosysram_download_area;
    5 B8 h3 v0 _: b  {& R& }3 Q
  75. 74          uint32_t hse_clock_value_in_hz;7 h: S: w4 ]9 }; J
  76. 75          /*
    " }  ?9 a: \8 k" j# Y3 W
  77. 76           * Boot partition :
    - o6 r( C- |$ S- T/ @, Y- q, ]
  78. 77           * ie FSBL partition on which the boot was successful
    % l2 ^; Y* W: C% W! `
  79. 78           */
    ) x9 q: \" d; W
  80. 79          uint32_t boot_partition_used_toboot;
    3 u, L+ `$ K$ J8 b, v; [; S
  81. 80          /*
    9 J  @6 E. B. L) P4 E/ U# D
  82. 81           * Address of SSP configuration structure :
    ; e5 b6 J  d2 A( O/ Y  g" ^
  83. 82           * given and defined by bootROM
    , A! d7 y$ n2 d5 b% ^
  84. 83           * and used by FSBL. The structure is of type, G# N! @' v" o1 d0 t
  85. 84           * 'boot_api_ssp_config_t'. Z, k9 H+ M# B* h, F( Q6 Y
  86. 85           */. B( F) P. U) \7 S  E7 \7 x; e
  87. 86          boot_api_ssp_config_t *p_ssp_config;
    / i* a8 o8 V* ~
  88. 87          /*) ^" O. F5 ~6 A% @
  89. 88           * boot context field containing bootROM updated SSP Status) W' ~# ]& M- |; Q# t4 `0 i/ i
  90. 89           * Values can be of type BOOT_API_CTX_SSP_STATUS_XXX8 O5 t( ~, S( c
  91. 90           */( e, |" Q. C9 C! n
  92. 91          uint32_t    ssp_status;7 l( {( D7 g6 ]- {' Z4 c6 ?8 N
  93. 92 % @* Q4 b4 X4 j2 N' t2 w
  94. 93          /* Pointer on ROM constant containing ROM information */% ~5 r1 j" c! V) o. j* ]( C) l3 Y
  95. 94          const boot_api_rom_version_info_t *p_rom_version_info;
    : X% E. S$ \) h$ S8 Y
  96. 95 ; R6 z3 X/ w3 M; Q& `# d0 f
  97. 96 }         __packed boot_api_context_t;
复制代码

( A2 m5 X5 K5 T2 c( t0 Uboot_api_context_t结构体目前不需要去研究,后面学习TF-A的时候根据实际情况在看是否有必要学习。
; C6 ^6 i% T& `, o& H" ^7 \5.2.3 串行启动
- y7 F+ Z& j/ }( m7 R  M( |4 @当我们设置BOOT2~BOOT0为串行启动,也就是从USB或UART启动的时候就会进入此模式。当选择串行启动以后ROM代码就会并行扫描所有可以启动的UART以及USB OTG接口。当扫描到某个活动的串行接口以后,ROM代码就会使用此串行接口,并且忽略掉其他的串行接口。
( M! b* r' F" J) F, d: B/ }0 N# F1、USB启动
+ L. k/ S' n2 t, o: r4 f# N5 R# V. e内部ROM代码支持USB OTG启动,我们一般使用STM32CubeProgrammer软件通过USB OTG接口来向STM32MP1烧写系统。USB OTG需要一个48M和60M的时钟,这两个时钟由HSE生成。ROM代码支持的HSE时钟值如下:
* u5 ^1 z6 B9 ^, f, Z* k" J8, 10, 12, 14, 16, 20, 24, 25, 26, 28, 32, 36, 40, 48 MHz, \+ m0 i8 t: w7 c$ \, S' l$ e& ~
正点原子STM32MP157开发板使用24M有源晶振作为HSE时钟源。我们可以通过设置OTP来更改ROM代码的HSE晶振大小,设置如表5.2.3.1所示:8 y5 j$ Q, o8 Q7 B/ e6 {
OTP WORD 3值
$ m/ T5 \% v5 P+ O; H* W  P(2bit) 描述9 j) m. M7 o2 L1 _& j2 Z) v
00 默认模式,ROM代码自动检测HSE频率,HSE频率必须为8, 10, 12, 14, 16, 20, 24, 25, 26, 28, 32, 36, 40或 48MHz,如果没有检测到的话就直接认为是24MHz
1 d0 C# i5 S0 U* G01 HSE=24MHz6 P2 T2 A. ?/ q$ v  @& m" F, L
10 HSE=25MHz. a' u* j0 R5 B
11 HSE=26MHz/ a6 I; q+ B# q4 i
表5.2.3.1 HSE时钟OTP设置表5 t2 t7 c' s& m$ s5 V
从表可以看出,默认情况下HSE选择24MHz,虽然可以通过修改OTP来更改HSE,但是强烈不建议!因为OTP只能修改一次,一旦修改错误芯片就废了!所以大家在自己做核心板的时候外部HSE时钟最好选择24MHz,不要特立独行!7 G- W9 u# P: ^8 b
2、UART启动
3 A, o# R. K4 B$ m3 e9 ~( A/ s; U如果要送UART启动,也就是通过UART烧写系统,那么只能使用USART2、USART3 UART4、UART5、USART6、UART7或UART8,此时串口工作模式为:1位起始位、8位数据位、偶校验、1位停止位、波特率115200。
1 V$ J/ E* ]& e) f3 C+ o  w' u由于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所示:  Z* J& W/ F, d3 i
串口 串口引脚 所使用的IO 复用编号(AF); S& ^6 {% @  Q, ^
USART2 USART2_RX PA3 AF07- u2 \7 U0 w2 o- u' w7 c
USART2_TX PA2 AF07
: o# ^. B" J! v. M# X! q7 BUSART3 USART3_RX PB12 AF08$ K/ o6 q- b- Q4 X
USART3_TX PB10 AF07
' \' C5 Q& p; R; |2 |UART4 UART4_RX PB2 AF08
# h5 Z- \1 x8 mUART4_TX PG11 AF06
4 L1 r0 Y3 q: h3 U9 lUART5 UART5_RX PB5 AF12. x# M3 s4 W2 R. f6 K) C
UART5_TX PB13 AF14, z  i& [  T0 c7 i  c
USART6 USART6_RX PC7 AF073 Q; j$ v' d* L, z, }: R
USART6_TX PC6 AF07
# O; t5 {) X, g3 RUART7 UART7_RX PF6 AF07
. n& w0 T( I% s# b" O# JUART7_TX PF7 AF07! r( m( `0 f# i$ v% o3 [
UART8 UART8_RX PE0 AF08- W+ f* A$ m) _* U! t
UART8_TX PE1 AF08/ y( x0 ~- B- @7 h, r3 e! w/ o
表5.2.3.2 ROM代码串口默认IO
$ D, H- m: ^8 O, L5 `大家在做板子的时候,如果要使用串口启动,那么相关串口IO一定要参考表5.2.3.2中定义的IO引脚,比如正点原子开发板UART4的RX引脚使用PB2,TX引脚使用PG11。
% N/ p4 u3 g# h  M5.3 Flash设备启动要求$ l. V2 O) Q3 y) ?6 ^& ^
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应该放到哪里等等。4 k. I+ D" M# T# ~7 D' R6 G
针对Flash设备,可以通过创建不同的分区来存放不同的文件,ST针对STM32MP1系列给出了官方分区建议,这些建议包含了Flash分区数量、分区最小空间、分区存放的内容等,如表5.3.1所示:2 o+ r( {1 m, g# u4 k( @7 h1 ^
尺寸 分区 描述6 i6 L: @4 P6 ~; O% j/ G) ?1 z6 c
256KB~512KB fsbl 第一阶段启动代码,此分区存放TF-A或者uboot的SPL部分,如果写A7裸机例程的话此分区也用来存放裸机代码。
9 V$ L  \3 w. B: R2MB ssbl 第二阶段启动代码,一般是uboot,如果uboot使用设备树的话,设备树添加到后面。
- z9 L. [3 o. ]7 ]64MB bootfs boot文件分区,可以存放如下内容:6 G5 X& }  o- v( M  `
· init ram文件系统,可以将此文件系统拷贝到RAM中,在linux内核挂载正式根文件系统之前可以使用init ram文件系统。
/ q# c) q' B& l2 a! t# Y# x# Y·linux内核设备树
: [: j$ o. I' k3 _- Y·linux内核
% j4 P1 @9 U7 C) z8 y/ ^·uboot显示的启动界面
/ X" B$ m/ ?1 }8 V$ t# c. c·uboot发行配置文件extlinux.conf6 p$ j0 v5 l; o0 {7 P
16MB vendorfs 此分区存放第三方的版权信息,确保它们不会受到任何开源许可的污染,比如GPL V3。
/ J+ A; }" |7 C, ]768MB rootfs linux根文件系统。
7 w9 E4 U! y. x+ k0 U1 P: T剩余空间 userfs 用户自行使用的剩余空间
0 D/ B/ w# Z: Z5 k表5.3.1 ST官方的Flash分区建议4 t' ]1 H0 w& Q) B$ i
5.3.1 从NAND启动: k' r  g7 L, [: r
NAND前几个块(block)里面包含了多份FSBL,ROM代码会从第一个块开始扫描,并且加载第一个有效块里面的FSBL。ROM代码支持并行NAND和串行NAND,并行NAND连接到FMC总线上,串行NAND连接到QSPI上。
3 |7 ^: g' m7 o% |+ ~ROM代码支持的并行NAND要求如表5.3.1.1所示:' U! P. }3 v% y
块大小(KB) 页大小(KB) 数据宽度 ECC(bit数和编码)
- M9 f$ D8 N% q4 v/ h128 2 8,16 4(bch),8(bch),1(hamming)
3 p/ ?: g! ~( G* n8 b- k) f5 U; J256 4 8,16 4(bch),8(bch),1(hamming)
6 V# |3 d& W# C: ^5 N/ z512 4 8,16 4(bch),8(bch),1(hamming)6 u+ a# Y4 \9 T$ q/ V
512 8 8,16 4(bch),8(bch),1(hamming)- N# S# n" w+ i/ s1 u
表5.3.1.1 并行NAND参数要求
9 w" g# j  ^8 IROM代码支持串行NAND要去如表5.3.1.2所示:9 G, V, }5 D9 }" [2 m* \- ]
块大小(KB) 页大小(KB)
( N: Q5 e. W  N0 D128 2* |+ p- m+ t1 w; F. ~
256 4$ d; l3 g- V' b; h& L+ L8 W
512 4* n; m! a8 G1 U; L  l0 @( \, j4 O7 K
512 8
0 m. \) K: c& m+ X" S表5.3.1.2 串行NAND参数要求
$ e0 g, _8 w) e9 D' V! g所以大家在制作STM32MP1硬件的时候,NAND Flash选型一定要符合表5.3.1.1中的参数。8 |$ B3 b& {* e
5.3.2 从EMMC启动
- r6 l3 h, a* E0 g- o2 c1 A: BEMMC在物理结构上有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所示:3 q5 q6 ~9 k8 b: e, C
2 [; s% m" V2 k5 M2 I
0413c8cadfdc459e82052f346843b543.png
$ g* p$ o! ^" T' ?
' x# z) {- x  X3 H0 z图5.3.2.1 三星KLM系列EMMC分区结构5 w  l; X0 G6 t9 t0 x, N
我们一般知道和常用的就是UDA分区,也就是用户数据区域,很少会关心boot1、boot2这样的分区。boot1、boot2、RPMB这三个分区代销是固定的,用户不能修改,boot1、boot2分区存在的意义就是用于引导系统。正点原子STM32MP157开发板所使用的EMMC型号为KLM8G1GETF,这是三星的一颗8GB EMMC 5.1芯片,boot1、boot2和RPMB分区大小如图5.3.2.2所示:7 V7 o, d9 ^- f$ {' t0 }

# {: L: {; Z4 v% W3 B' | 10a7f98b0be34cdb89befd54b34829ee.png " e7 |8 Y" [# N1 b, G2 S
; u- D4 b3 ?: P. d% b+ ~0 e: r  {  E) K
图5.3.2.2 KLM系列EMMC分区6 c: C( c5 _$ Q: l
从图5.3.2.2中可以看出,对于三星的8GB的EMMC而言,boot1和boot2分区默认大小为4096KB,RPMB为512KB。1 M( V1 o- }3 A& ]. V
ST会使用EMMC的boot1和boot2这两个分区作为FSBL,但是同一时间只有一个有效,ROM代码会加载有效的哪个FSBL。ROM代码使用单bit模式来操作EMMC,默认情况下ROM代码使用连接到SDMMC2上的EMMC,可以通过OTP来修改EMMC所使用的SDMMC接口,但是这里不建议!# ^% n  j. f  W% y2 B# [3 t' V. \
5.3.3 从SD卡启动( ?( u" b5 c4 B4 F
SD卡也包含两个FSBL,但是SD卡没有boot1和boot2这样的物理分区。ROM代码默认尝试加载第一个FSBL,如果第一个FSBL加载失败,那么ROM代码就会加载第二个FSBL。2 b6 h, ~, [0 u, v0 A
ROM代码首先在SD卡上查找GPT分区,如果找到的话就查找名字以“fsbl”开始的两个FSBL分区。如果没有找到GPT分区的话就直接根据物理地址查找两个FSBL,第一个FSBL的起始偏移地址为LBA34,地址位34512=17408=0X4400,所以第一个FSBL的起始地址为0X4400。第二个FSBL的起始偏移地址为LBA546,地址为 546512=279552=0X44400,所以第二个FSBL的起始地址为0X44400。# p' c6 x6 \' Q" Y  m7 @: Q
ROM代码默认也是使用单bit模式操作SD卡,并且默认使用连接到SDMMC1接口上的SD卡。
9 Y% Z4 m5 G" G3 G" `8 q/ D$ X* @& E$ G5.4 STM32MP1二进制头部信息
+ w7 {" {2 C+ j; @7 g) C& T, B前面讲了STM32MP1内部的ROM代码会先读取FSBL代码,一般是TF-A或者Uboot的SPL,也可以是A7裸机代码。比如TF-A我们之间编译生成二进制bin文件,但是这个bin文件不能直接拿来用,需要在前面添加一段头部信息,这段头部信息也包含了鉴权内容。加入头部信息以后的FSBL代码结构如图5.4.1所示:
4 s) E) K: f# J, q
& \1 B- F8 r( q! I* t" Z& o cff5b4eb064c42cab0563ed3dc821b07.png
( s& i9 B2 G4 H) l1 M
+ O' ]; z' I7 D1 ^1 q图5.4.1 FSBL镜像组织结构, K7 J$ U  U- U6 q1 m  G
头部信息一共是256字节,这256个字节的头部信息具体含义如表5.4.1所示:) \% `  T" n1 k' ]
名字 长度 偏移(B) 描述% B5 I( T( c0 O: w. G8 p9 L- j
Magic number 32bits 0 魔术数,4字节,为‘S’、‘T’、‘M’和0X32的组合,固定为0X53544D32。注意,这4个字节是大端模式,也就是高字节存储在低地址处,低字节数据在高地址处。) D6 W; l/ [: i3 @+ o
Image signature 512bits 4 ECDSA签名,用于镜像鉴权。
; N" M  t& j9 G  BImage checksum 32bits 68 镜像校验和。4 B0 I+ E& Z5 A. B( N
Header version 32bits 72 头部版本信息,V1.0的话为0X00010000,含义:: |, o% U- a( O$ s  H+ v
Byte0:保留/ U0 N' d! r: q, E& y7 T1 R
Byte1:主版本号为0X01。/ ~/ T8 r0 J5 A9 b9 c, K
Byte2:次版本号为0X00。# c9 c0 }1 E( e# m
Byte3:保留
' f9 n9 u+ m' @8 w( yImage length 32bits 76 镜像长度,不包含头部,单位为字节。5 u) ~" {# R  N: }) S  d& |
Image entry Point 32bits 80 镜像入口地址。0 b# `; C4 B3 w) u! i% M$ o
Reserved1 32bits 84 保留。  f) f' y& \% \& I8 p
Load address 32bits 88 镜像加载地址,ROM代码不使用此地址。
  k( m4 ~$ M+ y, n: D4 @* K) lReserved2 32bits 92 保留。. U8 H5 t2 p+ u. h/ m# @
Version number 32bits 96 镜像版本信息。
: Z4 `! B; i) ~1 ~9 L( jOption flags 32bits 100 可选字段,b0=1的话表示不需要验证签名。( X+ X3 m8 x* V5 J
ECDSA algorithm 32bits 104 ECDSA算法,1:P-256 NIST;2:brainpool 256
' s) ~& \5 S5 E- gECDSA pubilc key 512bits 108 ECDSA公共秘钥,签名的时候使用。1 D+ S8 S2 {7 W- m0 N# v
Padding 83Bytes 172 保留的填充区域,必须全部为07 s( G7 C( N: o" M1 l
Binary type 1Byte 255 二进制文件类型:+ X5 i( W, k. s5 ]
0X00:U-Boot, j  |4 D$ V- A- `& Y
0X10-0X1F:TF-A
8 x; ^. M+ b* t. n) S0X20-0X2F:OPTEE
7 s& ^1 b. t. L/ i4 O0 E0X30:Copro& E) \9 x$ f7 ^' n4 x
表5.4.1 头部信息含义9 ?% T( P4 R" K8 m7 c
头部信息不需要我们自己手动添加,我们在编译ST官方提供的TF-A或者Uboot的时候会自动添加,因为ST提供了个名为“stm32image”的工具专门用于在bin文件前面添加头部信息。我们已经从TF-A源码中提取出来了stm32image并放到了开发板光盘中,路径为:开发板光盘5、开发工具2、ST官方开发工具stm32imagestm32image.c。在编写A7裸机的时候需要自己使用stm32image工具在bin文件前面添加头部信息,stm32image是在Ubuntu下运行的,所以需要先编译,将stm32image.c发送到Ubuntu下,然后输入如下命令编译:4 r, Z$ r5 M$ @7 c  ^/ o8 `
gcc stm32image.c -o stm32image
9 j/ ?7 R' H& W! y3 ^. i7 b' V2 e编译成功以后就会生成一个名为stm32image的可执行文件,如不5.4.2所示:% E1 b; T, D! W& s" a6 X
" k% ?0 p- `( Q* w
6241fd2c43e2486faed955acab75dd62.png
( n/ F$ M( r) U' m
  K0 t( Y1 T/ N; C' g- `图5.4.2 stm32image工具
9 B/ _. M: B6 j; `运行图5.4.2编译出来的stm32image工具,输入“-s”选项可以查看使用方法,如图5.4.3所示:) C" V* l% F, k: `: L1 ]5 X" c

" S" @% F, _6 ]# `! L  `4 W af748a43c7de4babbbe1541cebe0cd87.png
3 X+ j5 y* d( \- i* R8 k& T% A9 Z) q! c- K) W* U' K1 G
图5.4.3 stm32image使用方法: q( r! W3 w  g$ g9 B; x; @
从图5.4.3可以看出,stm32image在使用的时候需要搭配一系列的参数:
; U. U1 }/ g+ R3 ?3 {/ W* y! W) x-s:指定源文件。6 j- {. N6 s! Y- x9 p# v
-d:生成的目标文件。2 A9 A) V6 u5 Q* ]  E9 s/ }
-l:加载地址。) ^# ]& k$ L  x) e+ m7 ?
-e:入口地址。
% V6 R9 C' u/ F- i  B-m:出版本号。
6 a7 T: x$ _( E5 u9 b& T! S, J+ e-n:次版本号。
" y7 j, H% W, ]% y6 u( W: Y4 m大家在开发板光盘里面找到正点原子出厂的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所示:  V  t; `+ A( @2 b. d
) |% }! f1 a" b( ]
be0523a1e7e44ffe93e61e0b0b28efe5.png % D$ c, T; n; i9 ^9 @, o9 E5 b

. R: P+ v' {1 d; W1 ?3 l图5.4.4 tf-a-stm32mp157d-atk-trusted.stm32文件内容% w9 ]3 n0 R( ?) C  d# I
图5.4.4就是tf-a-stm32mp157d-atk-trusted.stm32文件原始数据,其中前256个字节就是头部信息。这里我们根据图5.4.4中的内容,分析一下tf-a头部信息中几个比较重要的参数:
7 I/ L+ O/ ^/ E) ~" D* S/ |5 qMagic number:起始偏移地址为0,长度为4个字节,值依次为:0X53、0X54、0X4D、0X32,合起来就是0X53544D32,这个就是表5.4.1中的魔术数,注意这四个字节的顺序是大端模式。0 P/ `# T8 D/ E9 W% n  x6 m) K
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。3 T  C8 _( S2 n$ W
Image length:起始偏移地址为76,长度为4个字节,也就是图76~79这4个字节的数据,为:0X40、0XB0、0X03和0X00,按照小端模式拼起来就是0X0003B040=241728≈236.1KB,说明此TF-A的bin镜像大小为236.1KB。& B8 P3 x5 l1 D: A; d3 L$ u
Image entry Point:起始偏移地址为80,长度为4个字节,也就是图80~83这4个字节的数据,为:0X00、0X60、0XFD和0X2F,按照小端模式拼起来就是0X2FFD6000,说明入口地址为0X2FFD6000。2 g  _+ l# F, j
Load address:起始偏移地址为88,长度为4个字节,也就是图88~91这4个字节的数据,为:0X00、0X25、0XFC和0X2F,按照小端模式拼起来就是0X2FFC2500,说明加载地址为0X2FFC2500,这个不正是我们前面在5.2.2小节中分析的FSBL镜像起始地址。
1 V/ f% n, l7 t2 PBinary type:起始偏移地址为255,也就是最后一个字节,为0X10,表示当前二进制文件是TF-A。
: t$ R) `( s- q0 K* G  h5.5 STM23MP1 Linux系统启动过程9 C  g# W) k* H4 B& O1 x/ c
前面已经对STM32MP1的启动流程做了详细的讲解,STM32MP1是面向Linux领域的,因此所以的这些启动过程都是为了启动Linux内核。STM32MP1xil启动Linux内核的流程如图5.5.1所示:, _4 l) V& P; f) P/ }0 t, \

; A* Z4 r$ T) ]/ G* y: T 3f193ad3432d445e90152a1bdfe3e5a8.png 5 }0 M# ^7 _4 Q( H6 n  i

6 K" q+ I2 p( j1 E7 R" W7 v图5.5.1 MP1 Linux启动流程
* B8 J* E, x3 X7 U从图5.5.1可以看出,STM32MP1启动linux内核一共分为5个步骤,我们依次来看一下这五个步骤的内容:
# S. u. H4 b- c" c# f0 p) S. u7 x①、ROM代码7 I' T- o& i* K2 @4 s6 D4 u
前面说了很多次了,这是ST自己编写的代码,在STM32MP1出厂的时候就已经烧写进去的,不能被修改的。ROM代码因为保存在STM32内部ROM里面,因此也就直接简单明了的叫做“ROM代码”了。它是处理器上电以后首先执行的程序,ROM代码的主要工作就是读取STM32MP1的BOOT引脚电平,然后根据电平判断当前启动设备,最后从选定的启动设备里面读取FSBL代码,并将FSBL代码放到对应的RAM空间。
- W; Z* y, H  f( F1 r2 x$ }现在很多产品对设备上运行的应用都提出了安全要求,从图5.5.1中可以看出,STM32MP1启动Linux内核的过程是一个链式结构:ROM CodeFSBLSSBLLinux kernelrootfs,系统启动的过程中要保证整个链式结构都是安全的。ROM代码作为第一链,首先要对FSBL代码进行鉴权,同样的,FSBL以及后面的每一链都要对下一个阶段的镜像进行鉴权,直到设备系统正确启动。
4 b9 F9 f- t4 @7 }②、FSBL
9 E$ l' _" d* @- P6 Z6 Y# h- OFSBL代码初始化时钟树、初始化外部RAM控制器,也就是DDR。最终FSBL将SSBL加载到DDR里面并运行SSBL代码。
0 s% v/ K$ o6 ]3 F7 q4 p一般FSBL代码是TF-A或者Uboot的SPL代码,前面我们说了,也可以将FSBL换成我们自己编写的STM32MP1 A7内核裸机代码。: i# b. R9 h( d1 n$ D5 I
③、SSBL" L6 r! L/ k' g
由于SSBL代码运行在DDR里面,无需担心空间不够,因此SSBL代码的功能就可以做的很全面,比如使能USB、网络、显示等等。这样我们就可以在SSBL中灵活的加载linux内核,比如从Flash设备上读取,或者通过网络下载下载等,用户使用起来也非常的友好。SSBL一般是Uboot,用来启动Linux内核。
& [4 l( Y; V1 [) Q6 B- M* ~; O③、Linux内核/ `/ A* n" `+ Z9 z# b& X# x2 F
SSBL部分的Uboot就一个使命,启动Linux内核,Uboot会将Linux内核加载到DDR上并运行。Linux内核启动过程中会初始化板子上的各种外设。
" \7 f# ~! U  ~' p. G2 S  |④、Linux用户空间$ N2 m) {/ v$ L: z/ ^) r8 f: I( _
系统启动的时候会通过init进程切换到用户空间,在这个过程中会初始化根文件系统里面的各种框架以及服务。" z% _4 g+ E: ?3 @
————————————————
- {2 ?/ C+ {+ _8 o版权声明:正点原子
0 F! v1 D. I4 U4 F/ L6 F$ S: e! b7 X! e/ Q, V
1 J# }/ E& c1 C% j- u
收藏 评论0 发布时间:2022-9-21 15:49

举报

0个回答

所属标签

相似分享

官网相关资源

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