当前位置:   article > 正文

Linux ARM64 SMP系统PSCI启动方式ATF固件处理分析_linux atf

linux atf

在之前一篇文章Linux ARM64 spin-table启动方式实现Hiberate功能_Dingjun798077632的博客-CSDN博客中尝试了使用spin-table启动方式实现Hiberate功能,原本应该采用PSIC启动方式,所以学习了一下PSIC启动方式,这里分析下PSIC启动

时涉及到的ATF Bl31固件代码。

关于PSIC启动的介绍可以参考linux cpu管理(三) psci启动 - 知乎

我们知道PSIC启动方式,主cpu启动secondary cpu时,最后是主CPU通过SMC陷入EL3的BL31固件中,调用bl31中电源管理相关服务的接口,完成secondary cpu的启动。psci接口规定了命令对应的function_id、接口的输入参数以及返回值。 其中输入参数可通过x0–x7寄存器传递,而返回值通过x0–x4寄存器传递。通过function_id,Linux内核指定调用BL31或者BL32(如果存在)的相关服务。

首先分析BL31的异常处理相关代码,这里用到ATF固件代码是从NXP官网下载的,对应NXP imx8系列。

一、BL31启动入口与异常向量入口配置

       从BL31 中的bl31.ld.S 中SECTIONS定义可以知道,bl31的入口在文件bl31_entrypoint.s中,并且入口函数就是bl31_entrypoint。
bl31.ld.S代码如下:

  1. OUTPUT_FORMAT(PLATFORM_LINKER_FORMAT)
  2. OUTPUT_ARCH(PLATFORM_LINKER_ARCH)
  3. //!指定了BL31入口函数
  4. ENTRY(bl31_entrypoint)
  5. ......
  6. MEMORY {
  7. RAM (rwx): ORIGIN = BL31_BASE, LENGTH = BL31_LIMIT - BL31_BASE
  8. #if SEPARATE_NOBITS_REGION
  9. NOBITS (rw!a): ORIGIN = BL31_NOBITS_BASE, LENGTH = BL31_NOBITS_LIMIT - BL31_NOBITS_BASE
  10. #else
  11. #define NOBITS RAM
  12. #endif
  13. ......
  14. SECTIONS
  15. {
  16. . = BL31_BASE;
  17. ASSERT(. == ALIGN(PAGE_SIZE),
  18. "BL31_BASE address is not aligned on a page boundary.")
  19. __BL31_START__ = .;
  20. #if SEPARATE_CODE_AND_RODATA
  21. .text . : {
  22. __TEXT_START__ = .;
  23. //指定了BL31入口文件
  24. *bl31_entrypoint.o(.text*)
  25. *(SORT_BY_ALIGNMENT(SORT(.text*)))
  26. *(.vectors)
  27. . = ALIGN(PAGE_SIZE);
  28. __TEXT_END__ = .;
  29. } >RAM
  30. ......
  31. }

在文件bl31\aarch64\bl31_entrypoint.S 中找到入口函数bl31_entrypoint代码如下:

  1. func bl31_entrypoint
  2. #if (defined COCKPIT_A53) || (defined COCKPIT_A72)
  3. /*
  4. * running on cluster Cortex-A53:
  5. * => proceed to bl31 with errata
  6. */
  7. mrs x20, mpidr_el1
  8. ands x20, x20, #MPIDR_CLUSTER_MASK /* Test Affinity 1 */
  9. beq bl31_entrypoint_proceed_errata
  10. /*
  11. * running on cluster Cortex-A72:
  12. * => trampoline to 0xC00000xx
  13. * where bl31 version for A72 stands
  14. */
  15. mov x20, #0xC0000000
  16. add x20, x20, #bl31_entrypoint_proceed - bl31_entrypoint
  17. br x20
  18. bl31_entrypoint_proceed_errata:
  19. #endif
  20. #if (defined COCKPIT_A53) || (defined COCKPIT_A72)
  21. bl31_entrypoint_proceed:
  22. #endif
  23. /* ---------------------------------------------------------------
  24. * Stash the previous bootloader arguments x0 - x3 for later use.
  25. * ---------------------------------------------------------------
  26. */
  27. mov x20, x0
  28. mov x21, x1
  29. mov x22, x2
  30. mov x23, x3
  31. #if !RESET_TO_BL31
  32. ... ...
  33. #else
  34. /* ---------------------------------------------------------------------
  35. * For RESET_TO_BL31 systems which have a programmable reset address,
  36. * bl31_entrypoint() is executed only on the cold boot path so we can
  37. * skip the warm boot mailbox mechanism.
  38. * ---------------------------------------------------------------------
  39. */
  40. el3_entrypoint_common \
  41. _init_sctlr=1 \
  42. _warm_boot_mailbox=!PROGRAMMABLE_RESET_ADDRESS \
  43. _secondary_cold_boot=!COLD_BOOT_SINGLE_CPU \
  44. _init_memory=1 \
  45. _init_c_runtime=1 \
  46. _exception_vectors=runtime_exceptions \
  47. _pie_fixup_size=BL31_LIMIT - BL31_BASE
  48. /* ---------------------------------------------------------------------
  49. * For RESET_TO_BL31 systems, BL31 is the first bootloader to run so
  50. * there's no argument to relay from a previous bootloader. Zero the
  51. * arguments passed to the platform layer to reflect that.
  52. * ---------------------------------------------------------------------
  53. */
  54. mov x20, 0
  55. mov x21, 0
  56. mov x22, 0
  57. mov x23, 0
  58. #endif /* RESET_TO_BL31 */
  59. /* --------------------------------------------------------------------
  60. * Perform BL31 setup
  61. * --------------------------------------------------------------------
  62. */
  63. mov x0, x20
  64. mov x1, x21
  65. mov x2, x22
  66. mov x3, x23
  67. bl bl31_setup
  68. #if ENABLE_PAUTH
  69. /* --------------------------------------------------------------------
  70. * Program APIAKey_EL1 and enable pointer authentication
  71. * --------------------------------------------------------------------
  72. */
  73. bl pauth_init_enable_el3
  74. #endif /* ENABLE_PAUTH */
  75. /* --------------------------------------------------------------------
  76. * Jump to main function
  77. * --------------------------------------------------------------------
  78. */
  79. bl bl31_main
  80. /* --------------------------------------------------------------------
  81. * Clean the .data & .bss sections to main memory. This ensures
  82. * that any global data which was initialised by the primary CPU
  83. * is visible to secondary CPUs before they enable their data
  84. * caches and participate in coherency.
  85. * --------------------------------------------------------------------
  86. */
  87. adrp x0, __DATA_START__
  88. add x0, x0, :lo12:__DATA_START__
  89. adrp x1, __DATA_END__
  90. add x1, x1, :lo12:__DATA_END__
  91. sub x1, x1, x0
  92. bl clean_dcache_range
  93. adrp x0, __BSS_START__
  94. add x0, x0, :lo12:__BSS_START__
  95. adrp x1, __BSS_END__
  96. add x1, x1, :lo12:__BSS_END__
  97. sub x1, x1, x0
  98. bl clean_dcache_range
  99. b el3_exit
  100. endfunc bl31_entrypoint

代码用到了一个宏el3_entrypoint_common配置el3的异常向量及地址。在文件include\arch\aarch64\el3_common_macros.S中,可以看到宏el3_entrypoint_common的定义如下:

  1. .macro el3_entrypoint_common \
  2. _init_sctlr, _warm_boot_mailbox, _secondary_cold_boot, \
  3. _init_memory, _init_c_runtime, _exception_vectors, \
  4. _pie_fixup_size
  5. .if \_init_sctlr
  6. /* -------------------------------------------------------------
  7. * This is the initialisation of SCTLR_EL3 and so must ensure
  8. * that all fields are explicitly set rather than relying on hw.
  9. * Some fields reset to an IMPLEMENTATION DEFINED value and
  10. * others are architecturally UNKNOWN on reset.
  11. *
  12. * SCTLR.EE: Set the CPU endianness before doing anything that
  13. * might involve memory reads or writes. Set to zero to select
  14. * Little Endian.
  15. *
  16. * SCTLR_EL3.WXN: For the EL3 translation regime, this field can
  17. * force all memory regions that are writeable to be treated as
  18. * XN (Execute-never). Set to zero so that this control has no
  19. * effect on memory access permissions.
  20. *
  21. * SCTLR_EL3.SA: Set to zero to disable Stack Alignment check.
  22. *
  23. * SCTLR_EL3.A: Set to zero to disable Alignment fault checking.
  24. *
  25. * SCTLR.DSSBS: Set to zero to disable speculation store bypass
  26. * safe behaviour upon exception entry to EL3.
  27. * -------------------------------------------------------------
  28. */
  29. mov_imm x0, (SCTLR_RESET_VAL & ~(SCTLR_EE_BIT | SCTLR_WXN_BIT \
  30. | SCTLR_SA_BIT | SCTLR_A_BIT | SCTLR_DSSBS_BIT))
  31. msr sctlr_el3, x0
  32. isb
  33. .endif /* _init_sctlr */
  34. #if DISABLE_MTPMU
  35. bl mtpmu_disable
  36. #endif
  37. .if \_warm_boot_mailbox
  38. /* -------------------------------------------------------------
  39. * This code will be executed for both warm and cold resets.
  40. * Now is the time to distinguish between the two.
  41. * Query the platform entrypoint address and if it is not zero
  42. * then it means it is a warm boot so jump to this address.
  43. * -------------------------------------------------------------
  44. */
  45. bl plat_get_my_entrypoint
  46. cbz x0, do_cold_boot
  47. br x0
  48. do_cold_boot:
  49. .endif /* _warm_boot_mailbox */
  50. .if \_pie_fixup_size
  51. #if ENABLE_PIE
  52. /*
  53. * ------------------------------------------------------------
  54. * If PIE is enabled fixup the Global descriptor Table only
  55. * once during primary core cold boot path.
  56. *
  57. * Compile time base address, required for fixup, is calculated
  58. * using "pie_fixup" label present within first page.
  59. * ------------------------------------------------------------
  60. */
  61. pie_fixup:
  62. ldr x0, =pie_fixup
  63. and x0, x0, #~(PAGE_SIZE_MASK)
  64. mov_imm x1, \_pie_fixup_size
  65. add x1, x1, x0
  66. bl fixup_gdt_reloc
  67. #endif /* ENABLE_PIE */
  68. .endif /* _pie_fixup_size */
  69. /* ---------------------------------------------------------------------
  70. * Set the exception vectors.
  71. * ---------------------------------------------------------------------
  72. */
  73. adr x0, \_exception_vectors
  74. msr vbar_el3, x0
  75. Isb
  76. ......

二、BL31 ATF固件异常处理流程分析

接上文,在宏定义el3_entrypoint_common中,通过下面两行代码

adr x0, \_exception_vectors

msr vbar_el3, x0

配置了el3的异常向量及地址为runtime_exceptions。arm64位与32位架构不同,arm64的异常向量表地址不固定,在启动的时候由软件将异常向量表的地址设置到专用寄存器VBAR(Vector Base Address Register)中。

runtime_exceptions在文件bl31\aarch64\runtime_exceptions.S中,Linux内核通过SMC指令陷入到el3,对应的el异常处理入口就在该文件中。这里又涉及到另一个宏vector_base,在文件include/arch/aarch64/asm_macros.S。 宏 vector_base 将向量表放到了 .vectors 段中并且配置ax属性(可执行)等,至于.vectors 段,这里就不在分析了。

include/arch/aarch64/asm_macros.S代码,宏vector_base

bl31\aarch64\runtime_exceptions.S代码

  1. vector_base runtime_exceptions
  2. /* ---------------------------------------------------------------------
  3. * Current EL with SP_EL0 : 0x0 - 0x200
  4. * ---------------------------------------------------------------------
  5. */
  6. vector_entry sync_exception_sp_el0
  7. #ifdef MONITOR_TRAPS
  8. stp x29, x30, [sp, #-16]!
  9. mrs x30, esr_el3
  10. ubfx x30, x30, #ESR_EC_SHIFT, #ESR_EC_LENGTH
  11. /* Check for BRK */
  12. cmp x30, #EC_BRK
  13. b.eq brk_handler
  14. ldp x29, x30, [sp], #16
  15. #endif /* MONITOR_TRAPS */
  16. /* We don't expect any synchronous exceptions from EL3 */
  17. b report_unhandled_exception
  18. end_vector_entry sync_exception_sp_el0
  19. vector_entry irq_sp_el0
  20. /*
  21. * EL3 code is non-reentrant. Any asynchronous exception is a serious
  22. * error. Loop infinitely.
  23. */
  24. b report_unhandled_interrupt
  25. end_vector_entry irq_sp_el0
  26. vector_entry fiq_sp_el0
  27. b report_unhandled_interrupt
  28. end_vector_entry fiq_sp_el0
  29. vector_entry serror_sp_el0
  30. no_ret plat_handle_el3_ea
  31. end_vector_entry serror_sp_el0
  32. /* ---------------------------------------------------------------------
  33. * Current EL with SP_ELx: 0x200 - 0x400
  34. * ---------------------------------------------------------------------
  35. */
  36. vector_entry sync_exception_sp_elx
  37. /*
  38. * This exception will trigger if anything went wrong during a previous
  39. * exception entry or exit or while handling an earlier unexpected
  40. * synchronous exception. There is a high probability that SP_EL3 is
  41. * corrupted.
  42. */
  43. b report_unhandled_exception
  44. end_vector_entry sync_exception_sp_elx
  45. vector_entry irq_sp_elx
  46. b report_unhandled_interrupt
  47. end_vector_entry irq_sp_elx
  48. vector_entry fiq_sp_elx
  49. b report_unhandled_interrupt
  50. end_vector_entry fiq_sp_elx
  51. vector_entry serror_sp_elx
  52. #if !RAS_EXTENSION
  53. check_if_serror_from_EL3
  54. #endif
  55. no_ret plat_handle_el3_ea
  56. end_vector_entry serror_sp_elx
  57. /* ---------------------------------------------------------------------
  58. * Lower EL using AArch64 : 0x400 - 0x600
  59. * ---------------------------------------------------------------------
  60. */
  61. vector_entry sync_exception_aarch64
  62. /*
  63. * This exception vector will be the entry point for SMCs and traps
  64. * that are unhandled at lower ELs most commonly. SP_EL3 should point
  65. * to a valid cpu context where the general purpose and system register
  66. * state can be saved.
  67. */
  68. apply_at_speculative_wa
  69. check_and_unmask_ea
  70. handle_sync_exception
  71. end_vector_entry sync_exception_aarch64
  72. vector_entry irq_aarch64
  73. apply_at_speculative_wa
  74. check_and_unmask_ea
  75. handle_interrupt_exception irq_aarch64
  76. end_vector_entry irq_aarch64
  77. vector_entry fiq_aarch64
  78. apply_at_speculative_wa
  79. check_and_unmask_ea
  80. handle_interrupt_exception fiq_aarch64
  81. end_vector_entry fiq_aarch64
  82. vector_entry serror_aarch64
  83. apply_at_speculative_wa
  84. #if RAS_EXTENSION
  85. msr daifclr, #DAIF_ABT_BIT
  86. b enter_lower_el_async_ea
  87. #else
  88. handle_async_ea
  89. #endif
  90. end_vector_entry serror_aarch64
  91. /* ---------------------------------------------------------------------
  92. * Lower EL using AArch32 : 0x600 - 0x800
  93. * ---------------------------------------------------------------------
  94. */
  95. vector_entry sync_exception_aarch32
  96. /*
  97. * This exception vector will be the entry point for SMCs and traps
  98. * that are unhandled at lower ELs most commonly. SP_EL3 should point
  99. * to a valid cpu context where the general purpose and system register
  100. * state can be saved.
  101. */
  102. apply_at_speculative_wa
  103. check_and_unmask_ea
  104. handle_sync_exception
  105. end_vector_entry sync_exception_aarch32
  106. vector_entry irq_aarch32
  107. apply_at_speculative_wa
  108. check_and_unmask_ea
  109. handle_interrupt_exception irq_aarch32
  110. end_vector_entry irq_aarch32
  111. vector_entry fiq_aarch32
  112. apply_at_speculative_wa
  113. check_and_unmask_ea
  114. handle_interrupt_exception fiq_aarch32
  115. end_vector_entry fiq_aarch32
  116. vector_entry serror_aarch32
  117. apply_at_speculative_wa
  118. #if RAS_EXTENSION
  119. msr daifclr, #DAIF_ABT_BIT
  120. b enter_lower_el_async_ea
  121. #else
  122. handle_async_ea
  123. #endif
  124. end_vector_entry serror_aarch32

handle_sync_exception中,

mrs x30, esr_el3

ubfx x30, x30, #ESR_EC_SHIFT, #ESR_EC_LENGTH

将esr_el3读取到x30寄存器,ubfx x30, x30, #ESR_EC_SHIFT, #ESR_EC_LENGTH 相当于x30=(x30 >> ESR_EC_SHIFT) & (1 << ESR_EC_LENGTH -1),

 实际就是取出esr_el3的26-31位,即获取异常原因。esr_el3寄存器是异常综合寄存器,当smc陷入el3时,该寄存器ec域(esr_el3[31:26])保存触发异常的原因,esr_el3具体定义如下:

 

上面获取异常原因,接下来做相应的处理,对应的代码

cmp x30, #EC_AARCH32_SMC

b.eq smc_handler32

cmp x30, #EC_AARCH64_SMC

b.eq smc_handler64

/* Synchronous exceptions other than the above are assumed to be EA */

ldr x30, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_LR]

b enter_lower_el_sync_ea

EC_AARCH64_SMC的值位0x17,就是对应的上面esr_el3的ec域SMC instruction execution in AArch64 state。上面的代码就很好理解了,AARCH64状态下触发异常smc异常,调用smc_handler64。AARCH32状态下触发异常smc异常,调用smc_handler32(AARCH64和AARCH32状态下smc的异常入口是不一样的,上面有说明,但是sync_exception_aarch64和sync_exception_aarch32都使用了handle_sync_exception,所以这需要区分EC_AARCH32_SMC和EC_AARCH64_SMC)。

smc_handler64代码如下:

  1. smc_handler64:
  2. /* NOTE: The code below must preserve x0-x4 */
  3. /*
  4. * Save general purpose and ARMv8.3-PAuth registers (if enabled).
  5. * If Secure Cycle Counter is not disabled in MDCR_EL3 when
  6. * ARMv8.5-PMU is implemented, save PMCR_EL0 and disable Cycle Counter.
  7. */
  8. bl save_gp_pmcr_pauth_regs
  9. #if ENABLE_PAUTH
  10. /* Load and program APIAKey firmware key */
  11. bl pauth_load_bl31_apiakey
  12. #endif
  13. /*
  14. * Populate the parameters for the SMC handler.
  15. * We already have x0-x4 in place. x5 will point to a cookie (not used
  16. * now). x6 will point to the context structure (SP_EL3) and x7 will
  17. * contain flags we need to pass to the handler.
  18. */
  19. mov x5, xzr
  20. mov x6, sp
  21. /*
  22. * Restore the saved C runtime stack value which will become the new
  23. * SP_EL0 i.e. EL3 runtime stack. It was saved in the 'cpu_context'
  24. * structure prior to the last ERET from EL3.
  25. */
  26. ldr x12, [x6, #CTX_EL3STATE_OFFSET + CTX_RUNTIME_SP]
  27. /* Switch to SP_EL0 */
  28. msr spsel, #MODE_SP_EL0
  29. /*
  30. * Save the SPSR_EL3, ELR_EL3, & SCR_EL3 in case there is a world
  31. * switch during SMC handling.
  32. * TODO: Revisit if all system registers can be saved later.
  33. */
  34. mrs x16, spsr_el3
  35. mrs x17, elr_el3
  36. mrs x18, scr_el3
  37. stp x16, x17, [x6, #CTX_EL3STATE_OFFSET + CTX_SPSR_EL3]
  38. str x18, [x6, #CTX_EL3STATE_OFFSET + CTX_SCR_EL3]
  39. /* Clear flag register */
  40. mov x7, xzr
  41. #if ENABLE_RME
  42. /* Copy SCR_EL3.NSE bit to the flag to indicate caller's security */
  43. ubfx x7, x18, #SCR_NSE_SHIFT, 1
  44. /*
  45. * Shift copied SCR_EL3.NSE bit by 5 to create space for
  46. * SCR_EL3.NS bit. Bit 5 of the flag correspondes to
  47. * the SCR_EL3.NSE bit.
  48. */
  49. lsl x7, x7, #5
  50. #endif /* ENABLE_RME */
  51. /* Copy SCR_EL3.NS bit to the flag to indicate caller's security */
  52. bfi x7, x18, #0, #1
  53. mov sp, x12
  54. /* Get the unique owning entity number */
  55. ubfx x16, x0, #FUNCID_OEN_SHIFT, #FUNCID_OEN_WIDTH
  56. ubfx x15, x0, #FUNCID_TYPE_SHIFT, #FUNCID_TYPE_WIDTH
  57. orr x16, x16, x15, lsl #FUNCID_OEN_WIDTH
  58. /* Load descriptor index from array of indices */
  59. adrp x14, rt_svc_descs_indices
  60. add x14, x14, :lo12:rt_svc_descs_indices
  61. ldrb w15, [x14, x16]
  62. /* Any index greater than 127 is invalid. Check bit 7. */
  63. tbnz w15, 7, smc_unknown
  64. /*
  65. * Get the descriptor using the index
  66. * x11 = (base + off), w15 = index
  67. *
  68. * handler = (base + off) + (index << log2(size))
  69. */
  70. adr x11, (__RT_SVC_DESCS_START__ + RT_SVC_DESC_HANDLE)
  71. lsl w10, w15, #RT_SVC_SIZE_LOG2
  72. ldr x15, [x11, w10, uxtw]
  73. /*
  74. * Call the Secure Monitor Call handler and then drop directly into
  75. * el3_exit() which will program any remaining architectural state
  76. * prior to issuing the ERET to the desired lower EL.
  77. */
  78. #if DEBUG
  79. cbz x15, rt_svc_fw_critical_error
  80. #endif
  81. blr x15
  82. b el3_exit
  83. smc_unknown:
  84. /*
  85. * Unknown SMC call. Populate return value with SMC_UNK and call
  86. * el3_exit() which will restore the remaining architectural state
  87. * i.e., SYS, GP and PAuth registers(if any) prior to issuing the ERET
  88. * to the desired lower EL.
  89. */
  90. mov x0, #SMC_UNK
  91. str x0, [x6, #CTX_GPREGS_OFFSET + CTX_GPREG_X0]
  92. b el3_exit

函数 smc_handler64主要做了下面事情:

1. 保存Non-Secure world中的 spsr_el3 、elr_el3、scr_el3到栈中;

2. 切换sp到SP_EL0;

3. 根据 function_id 找到对应的 runtime service, 查找方法:
Index = (function_id >> 24 & 0x3f) | ((function_id >> 31) << 6);
Handler=__RT_SVC_DESCS_START__+RT_SVC_DESC_HANDLE+ rt_svc_descs_indices[Index] << 5
__RT_SVC_DESCS_START__ 为 rt_svc_descs 的起始地址,RT_SVC_DESC_HANDLE 为服务处理函数 handle 在结构体rt_svc_desc 中的偏移,左移5,是因为结构体 rt_svc_desc 大小为 32字节。

4. 跳转到 runtime service 的处理函数 handle 中执行。

5. 最后退出el3

接下来需要结合Linux PSCI相关代码分析,先简单分析Linux PSCI SMC调用处理。

三、Linux PSCI启动方式SMC调用分析

PSIC启动方式,主cpu启动secondary cpu时,最后是主cpu调用到下面的psci_cpu_on接口(至于为什么调用到psci_cpu_on接口,相关文章很多,篇幅有限,这里略过)。

Linux代码drivers\firmware\psci.c

设置invoke_psci_fn的代码如下:

该接口中invoke_psci_fn指向__invoke_psci_fn_smc也就是smc调用(部分soc可能没有实现el3,采用hvc切换到el2,不在本文讨论范围),fn是本地调用的function_id,参数cpuid是需要启动的cpu(一般主cpuid为0,secondary cpu为1、2、3...),entry_point则指向secondary cpu在linux内核的入口地址。

 

 

以PSCI0.2为例,PSCI_FN_CPU_ON对应的function_id为PSCI_0_2_FN64_CPU_ON,也就是0x84000000 + 0x40000000 + 0x03 = 0xC4000003,也就是psci_cpu_on中smc调用的第一个参数fn为0xC4000003。

 __invoke_psci_fn_smc的具体实现如下:

 __invoke_psci_fn_smc最终通过汇编代码smc #0陷入到BL31(el3),也就是上文的vector_entry sync_exception_aarch64这里fn(function_id)、cpuid、entry_point放入通用寄存器x0、x1、x2中,作为参数传递到了BL31。

四、BL31 SMC服务分析

SMC调用的function_id有相应的规范,相关定义如下图,也可以参考文章

【转】ATF中SMC深入理解_arm smc_Hkcoco的博客-CSDN博客

 上面的代码中smc_handler64中,把相关宏定义也列出来,加上注释分析这段代码

  1. #define FUNCID_TYPE_SHIFT U(31)
  2. #define FUNCID_TYPE_MASK U(0x1)
  3. #define FUNCID_TYPE_WIDTH U(1)
  4. #define FUNCID_CC_SHIFT U(30)
  5. #define FUNCID_CC_MASK U(0x1)
  6. #define FUNCID_CC_WIDTH U(1)
  7. #define FUNCID_OEN_SHIFT U(24)
  8. #define FUNCID_OEN_MASK U(0x3f)
  9. #define FUNCID_OEN_WIDTH U(6)
  10. #define FUNCID_NUM_SHIFT U(0)
  11. #define FUNCID_NUM_MASK U(0xffff)
  12. #define FUNCID_NUM_WIDTH U(16)
  13. ......
  14. /* Get the unique owning entity number */
  15. //上文已分析,x0保存的是function_id,具体值0xC4000003,这里取出了function_id的OEN保存到x16,OEN值为4
  16. ubfx x16, x0, #FUNCID_OEN_SHIFT, #FUNCID_OEN_WIDTH
  17. //取出smc调用类型, x15为1
  18. ubfx x15, x0, #FUNCID_TYPE_SHIFT, #FUNCID_TYPE_WIDTH
  19. //x16 = (x15 << 6) | x16,将OEN作为低6位,type作为第7位组合成已数字index
  20. orr x16, x16, x15, lsl #FUNCID_OEN_WIDTH
  21. /*上面的代码相当于Index = (function_id >> 24 & 0x3f) | ((function_id >> 31) << 6),
  22. 接下来的代码查找function_id对应的Handler ,
  23. Handler = __RT_SVC_DESCS_START__ + RT_SVC_DESC_HANDLE + rt_svc_descs_indices[Index] << 5
  24. 这里__RT_SVC_DESCS_START__中 */
  25. /* Load descriptor index from array of indices */
  26. adrp x14, rt_svc_descs_indices
  27. add x14, x14, :lo12:rt_svc_descs_indices
  28. ldrb w15, [x14, x16]
  29. /* Any index greater than 127 is invalid. Check bit 7. */
  30. tbnz w15, 7, smc_unknown
  31. /*
  32. * Get the descriptor using the index
  33. * x11 = (base + off), w15 = index
  34. *
  35. * handler = (base + off) + (index << log2(size))
  36. */
  37. adr x11, (__RT_SVC_DESCS_START__ + RT_SVC_DESC_HANDLE)
  38. lsl w10, w15, #RT_SVC_SIZE_LOG2
  39. ldr x15, [x11, w10, uxtw]
  40. /*
  41. * Call the Secure Monitor Call handler and then drop directly into
  42. * el3_exit() which will program any remaining architectural state
  43. * prior to issuing the ERET to the desired lower EL.
  44. */
  45. #if DEBUG
  46. cbz x15, rt_svc_fw_critical_error
  47. #endif
  48. blr x15
  49. b el3_exit

根据 x0中保存的function_id ,找到对应的 runtime service, 查找方法:
Index = (function_id >> 24 & 0x3f) | ((function_id >> 31) << 6);
Handler=__RT_SVC_DESCS_START__+RT_SVC_DESC_HANDLE+ rt_svc_descs_indices[Index] << 5
__RT_SVC_DESCS_START__ 为 rt_svc_descs 的起始地址,RT_SVC_DESC_HANDLE 为服务处理函数 handle 在结构体rt_svc_desc 中的偏移,左移5,是因为结构体 rt_svc_desc 大小为 32字节。查找handle 的方法需要结合rt_svc_descs_indices初始化代码分析。

rt_svc_descs_indices初始化在文件common/runtime_svc.c中,代码如下:

  1. void __init runtime_svc_init(void)
  2. {
  3. int rc = 0;
  4. uint8_t index, start_idx, end_idx;
  5. rt_svc_desc_t *rt_svc_descs;
  6. ... ...
  7. /* Initialise internal variables to invalid state */
  8. (void)memset(rt_svc_descs_indices, -1, sizeof(rt_svc_descs_indices));
  9. rt_svc_descs = (rt_svc_desc_t *) RT_SVC_DESCS_START;
  10. for (index = 0U; index < RT_SVC_DECS_NUM; index++) {
  11. rt_svc_desc_t *service = &rt_svc_descs[index];
  12. ... ...
  13. if (service->init != NULL) {
  14. rc = service->init();
  15. if (rc != 0) {
  16. ERROR("Error initializing runtime service %s\n",
  17. service->name);
  18. continue;
  19. }
  20. }
  21. /*
  22. * Fill the indices corresponding to the start and end
  23. * owning entity numbers with the index of the
  24. * descriptor which will handle the SMCs for this owning
  25. * entity range.
  26. */
  27. start_idx = (uint8_t)get_unique_oen(service->start_oen,
  28. service->call_type);
  29. end_idx = (uint8_t)get_unique_oen(service->end_oen,
  30. service->call_type);
  31. assert(start_idx <= end_idx);
  32. assert(end_idx < MAX_RT_SVCS);
  33. for (; start_idx <= end_idx; start_idx++)
  34. rt_svc_descs_indices[start_idx] = index;
  35. }
  36. }

上面RT_SVC_DESCS_START就是__section("rt_svc_descs")的首地址,__section("rt_svc_descs")保存这个各rt_svc_desc_t结构体。

 可以看到,所有OEN为4的fast SMC都由std_svc_smc_handler处理。

  1. static uintptr_t std_svc_smc_handler(uint32_t smc_fid,
  2. u_register_t x1,
  3. u_register_t x2,
  4. u_register_t x3,
  5. u_register_t x4,
  6. void *cookie,
  7. void *handle,
  8. u_register_t flags)
  9. {
  10. if (((smc_fid >> FUNCID_CC_SHIFT) & FUNCID_CC_MASK) == SMC_32) {
  11. /* 32-bit SMC function, clear top parameter bits */
  12. x1 &= UINT32_MAX;
  13. x2 &= UINT32_MAX;
  14. x3 &= UINT32_MAX;
  15. x4 &= UINT32_MAX;
  16. }
  17. /*
  18. * Dispatch PSCI calls to PSCI SMC handler and return its return
  19. * value
  20. */
  21. if (is_psci_fid(smc_fid)) {
  22. uint64_t ret;
  23. #if ENABLE_RUNTIME_INSTRUMENTATION
  24. /*
  25. * Flush cache line so that even if CPU power down happens
  26. * the timestamp update is reflected in memory.
  27. */
  28. PMF_WRITE_TIMESTAMP(rt_instr_svc,
  29. RT_INSTR_ENTER_PSCI,
  30. PMF_CACHE_MAINT,
  31. get_cpu_data(cpu_data_pmf_ts[CPU_DATA_PMF_TS0_IDX]));
  32. #endif
  33. ret = psci_smc_handler(smc_fid, x1, x2, x3, x4,
  34. cookie, handle, flags);
  35. #if ENABLE_RUNTIME_INSTRUMENTATION
  36. PMF_CAPTURE_TIMESTAMP(rt_instr_svc,
  37. RT_INSTR_EXIT_PSCI,
  38. PMF_NO_CACHE_MAINT);
  39. #endif
  40. SMC_RET1(handle, ret);
  41. }
  42. ... ...
  43. }

  1. u_register_t psci_smc_handler(uint32_t smc_fid,
  2. u_register_t x1,
  3. u_register_t x2,
  4. u_register_t x3,
  5. u_register_t x4,
  6. void *cookie,
  7. void *handle,
  8. u_register_t flags)
  9. {
  10. u_register_t ret;
  11. if (is_caller_secure(flags))
  12. return (u_register_t)SMC_UNK;
  13. /* Check the fid against the capabilities */
  14. if ((psci_caps & define_psci_cap(smc_fid)) == 0U)
  15. return (u_register_t)SMC_UNK;
  16. if (((smc_fid >> FUNCID_CC_SHIFT) & FUNCID_CC_MASK) == SMC_32) {
  17. /* 32-bit PSCI function, clear top parameter bits */
  18. ... ...
  19. } else {
  20. /* 64-bit PSCI function */
  21. switch (smc_fid) {
  22. case PSCI_CPU_SUSPEND_AARCH64:
  23. ret = (u_register_t)
  24. psci_cpu_suspend((unsigned int)x1, x2, x3);
  25. break;
  26. case PSCI_CPU_ON_AARCH64:
  27. ret = (u_register_t)psci_cpu_on(x1, x2, x3);
  28. break;
  29. case PSCI_AFFINITY_INFO_AARCH64:
  30. ret = (u_register_t)
  31. psci_affinity_info(x1, (unsigned int)x2);
  32. break;
  33. case PSCI_MIG_AARCH64:
  34. ret = (u_register_t)psci_migrate(x1);
  35. break;
  36. case PSCI_MIG_INFO_UP_CPU_AARCH64:
  37. ret = psci_migrate_info_up_cpu();
  38. break;
  39. case PSCI_NODE_HW_STATE_AARCH64:
  40. ret = (u_register_t)psci_node_hw_state(
  41. x1, (unsigned int) x2);
  42. break;
  43. case PSCI_SYSTEM_SUSPEND_AARCH64:
  44. ret = (u_register_t)psci_system_suspend(x1, x2);
  45. break;
  46. #if ENABLE_PSCI_STAT
  47. case PSCI_STAT_RESIDENCY_AARCH64:
  48. ret = psci_stat_residency(x1, (unsigned int) x2);
  49. break;
  50. case PSCI_STAT_COUNT_AARCH64:
  51. ret = psci_stat_count(x1, (unsigned int) x2);
  52. break;
  53. #endif
  54. case PSCI_MEM_CHK_RANGE_AARCH64:
  55. ret = psci_mem_chk_range(x1, x2);
  56. break;
  57. case PSCI_SYSTEM_RESET2_AARCH64:
  58. /* We should never return from psci_system_reset2() */
  59. ret = psci_system_reset2((uint32_t) x1, x2);
  60. break;
  61. default:
  62. WARN("Unimplemented PSCI Call: 0x%x\n", smc_fid);
  63. ret = (u_register_t)SMC_UNK;
  64. break;
  65. }
  66. }
  67. return ret;
  68. }

对于PSCI启动方式CPU_ON,显然会走

case PSCI_CPU_ON_AARCH64:

ret = (u_register_t)psci_cpu_on(x1, x2, x3);

  1. int psci_cpu_on(u_register_t target_cpu,
  2. uintptr_t entrypoint,
  3. u_register_t context_id)
  4. {
  5. int rc;
  6. entry_point_info_t ep;
  7. /* Determine if the cpu exists of not */
  8. rc = psci_validate_mpidr(target_cpu);
  9. if (rc != PSCI_E_SUCCESS)
  10. return PSCI_E_INVALID_PARAMS;
  11. /* Validate the entry point and get the entry_point_info */
  12. rc = psci_validate_entry_point(&ep, entrypoint, context_id);
  13. if (rc != PSCI_E_SUCCESS)
  14. return rc;
  15. /*
  16. * To turn this cpu on, specify which power
  17. * levels need to be turned on
  18. */
  19. return psci_cpu_on_start(target_cpu, &ep);
  20. }

接下来的流程大致如下

  1. ->psci_cpu_on() //lib/psci/psci_main.c
  2. ->psci_validate_entry_point() //验证入口地址有效性并 保存入口点到一个结构ep中
  3. ->psci_cpu_on_start(target_cpu, &ep) //ep入口地址
  4. ->psci_plat_pm_ops->pwr_domain_on(target_cpu)
  5. ->qemu_pwr_domain_on //实现核上电(平台实现)
  6. /* Store the re-entry information for the non-secure world. */
  7. ->cm_init_context_by_index() //重点: 会通过cpu的编号找到 cpu上下文(cpu_context_t),存在cpu寄存器的值,异常返回的时候写写到对应的寄存器中,然后eret,旧返回到了el1!!!
  8. ->cm_setup_context() //设置cpu上下文
  9. -> write_ctx_reg(state, CTX_SCR_EL3, scr_el3); //lib/el3_runtime/aarch64/context_mgmt.c
  10. write_ctx_reg(state, CTX_ELR_EL3, ep->pc); //注: 异常返回时执行此地址 于是完成了cpu的启动!!!
  11. write_ctx_reg(state, CTX_SPSR_EL3, ep->spsr);

在psci_cpu_on中调用了psci_validate_entry_point和psci_cpu_on_start。

psci_validate_entry_point调用了psci_get_ns_ep_info将Linux SMC调用是x1寄存器保存的entry_point(secondary cpu在linux入口地址)保存到了ep->pc, 并且生成了ep->spsr。

 

 

 

 把ep->pc、 ep->spsr保存到contex后,return后回到smc_handler64,接着调用el3_exit, el3_exit最终会调用eret回到el1,也就是Linxu内核中。

这里给出一张SMC调用的大致流程图如下:

五、PSIC启动secondary cpu的处理

        好像上文整个流程,secondary cpu就启动完成了,实际不是的。 上面的过程都是主CPU在处理,主处理器通过smc进入el3请求开核服务,atf中会响应这种请求,通过平台的开核操作来启动secondary cpu,并且设置从处理的一些寄存器如:scr_el3、spsr_el3、elr_el3,然后主处理器,恢复现场,eret再次回到el1(Linux内核)。psci_cpu_on主要完成开核的工作,然后会设置一些异常返回后寄存器的值(eg:从el1 -> el3 -> el1),重点关注 ep->pc写到cpu_context结构的CTX_ELR_EL3偏移处(从处理器启动后会从这个地址取指执行)。

        而secondary cpu开核之后会从bl31_warm_entrypoint开始执行,在plat_setup_psci_ops中会设置(每个平台都有自己的启动地址寄存器,通过写这个寄存器来获得上电后执行的指令地址)。

bl31_warm_entrypoint代码如下,在el3_exit中会使用之前保存cpu_context结构中的数据,写入到cscr_el3、spsr_el3、elr_el3,最后通过eret指令使自己进入到Linxu内核。

  1. */
  2. func bl31_warm_entrypoint
  3. #if ENABLE_RUNTIME_INSTRUMENTATION
  4. /*
  5. * This timestamp update happens with cache off. The next
  6. * timestamp collection will need to do cache maintenance prior
  7. * to timestamp update.
  8. */
  9. pmf_calc_timestamp_addr rt_instr_svc, RT_INSTR_EXIT_HW_LOW_PWR
  10. mrs x1, cntpct_el0
  11. str x1, [x0]
  12. #endif
  13. /*
  14. * On the warm boot path, most of the EL3 initialisations performed by
  15. * 'el3_entrypoint_common' must be skipped:
  16. *
  17. * - Only when the platform bypasses the BL1/BL31 entrypoint by
  18. * programming the reset address do we need to initialise SCTLR_EL3.
  19. * In other cases, we assume this has been taken care by the
  20. * entrypoint code.
  21. *
  22. * - No need to determine the type of boot, we know it is a warm boot.
  23. *
  24. * - Do not try to distinguish between primary and secondary CPUs, this
  25. * notion only exists for a cold boot.
  26. *
  27. * - No need to initialise the memory or the C runtime environment,
  28. * it has been done once and for all on the cold boot path.
  29. */
  30. el3_entrypoint_common \
  31. _init_sctlr=PROGRAMMABLE_RESET_ADDRESS \
  32. _warm_boot_mailbox=0 \
  33. _secondary_cold_boot=0 \
  34. _init_memory=0 \
  35. _init_c_runtime=0 \
  36. _exception_vectors=runtime_exceptions \
  37. _pie_fixup_size=0
  38. /*
  39. * We're about to enable MMU and participate in PSCI state coordination.
  40. *
  41. * The PSCI implementation invokes platform routines that enable CPUs to
  42. * participate in coherency. On a system where CPUs are not
  43. * cache-coherent without appropriate platform specific programming,
  44. * having caches enabled until such time might lead to coherency issues
  45. * (resulting from stale data getting speculatively fetched, among
  46. * others). Therefore we keep data caches disabled even after enabling
  47. * the MMU for such platforms.
  48. *
  49. * On systems with hardware-assisted coherency, or on single cluster
  50. * platforms, such platform specific programming is not required to
  51. * enter coherency (as CPUs already are); and there's no reason to have
  52. * caches disabled either.
  53. */
  54. #if HW_ASSISTED_COHERENCY || WARMBOOT_ENABLE_DCACHE_EARLY
  55. mov x0, xzr
  56. #else
  57. mov x0, #DISABLE_DCACHE
  58. #endif
  59. bl bl31_plat_enable_mmu
  60. #if ENABLE_RME
  61. /*
  62. * At warm boot GPT data structures have already been initialized in RAM
  63. * but the sysregs for this CPU need to be initialized. Note that the GPT
  64. * accesses are controlled attributes in GPCCR and do not depend on the
  65. * SCR_EL3.C bit.
  66. */
  67. bl gpt_enable
  68. cbz x0, 1f
  69. no_ret plat_panic_handler
  70. 1:
  71. #endif
  72. #if ENABLE_PAUTH
  73. /* --------------------------------------------------------------------
  74. * Program APIAKey_EL1 and enable pointer authentication
  75. * --------------------------------------------------------------------
  76. */
  77. bl pauth_init_enable_el3
  78. #endif /* ENABLE_PAUTH */
  79. bl psci_warmboot_entrypoint
  80. #if ENABLE_RUNTIME_INSTRUMENTATION
  81. pmf_calc_timestamp_addr rt_instr_svc, RT_INSTR_EXIT_PSCI
  82. mov x19, x0
  83. /*
  84. * Invalidate before updating timestamp to ensure previous timestamp
  85. * updates on the same cache line with caches disabled are properly
  86. * seen by the same core. Without the cache invalidate, the core might
  87. * write into a stale cache line.
  88. */
  89. mov x1, #PMF_TS_SIZE
  90. mov x20, x30
  91. bl inv_dcache_range
  92. mov x30, x20
  93. mrs x0, cntpct_el0
  94. str x0, [x19]
  95. #endif
  96. b el3_exit
  97. endfunc bl31_warm_entrypoint

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/不正经/article/detail/129969
推荐阅读
相关标签
  

闽ICP备14008679号