ARM引导程序:禁用MMU和缓存

3
根据某些教程,在引导程序开始时我们会禁用MMU和I/D-Caches。如果我理解正确的话,它的目的是在程序中直接使用物理地址,如果我有误请纠正我。谢谢!
其次,我们这样做是为了禁用MMU和Caches:
mrc P15,0,R0,C1,C0,0 bic R0,R0,#0x00002300 @清除位13,9:8 bic R0,R0,#0x00000087 @清除位7,2:0 orr R0,R0,#0x00000002 @设置位2(A)Align orr R0,R0,#0x00001000 @设置位12(I)I-Cache mcr P15,0,R0,C1,C0,0
通过清除位2:0禁用了D-Cache、MMU和数据地址对齐故障检查,但为什么我们在以下工具中立即启用位2呢?是为了确保此操作有效吗?
最后一个问题是为什么禁用了D-cache但可以使用I-caches?是为了加快工具处理速度吗?

如果您关闭MMU,则希望同时关闭DCache,以便不会缓存CSR(根据系统的不同,如果关闭MMU,则可能无法使用DCache)。 - old_timer
为什么D缓存被禁用但I缓存可用?这是一种设计选择。引导代码通常只使用非常少的变量,并且它所使用的数据通常不适合缓存(从设备X复制代码到Y)。如果引导代码执行图像验证(SHA/签名验证),它可能会启用dcache,以平衡物理=virt MMU表设置。 - artless noise
不,这不是设计决策!在MMU禁用时启用d-cache是错误的,实际上这是一种被禁止的组合。请参见http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.faqs/ka13835.html。 - mishmashru
2个回答

10
为什么D-cache被禁用,但I-cache可以使用?是为了加快工具流程吗?
MMU有设置来确定哪些内存区域可以使用缓存。如果您没有开启MMU但打开了数据缓存(如果可能的话),则无法安全地与外设通信。例如,如果您读取UART状态寄存器,则该操作与任何其他数据操作一样经过缓存,直到该缓存行被驱逐并且您有一次访问实际寄存器的机会。假设您有一些代码轮询UART状态寄存器等待rx缓冲区中的字符。如果第一次读取显示没有字符,则该状态进入缓存,您将永远停留在循环中,因为您永远不会再次访问状态寄存器,而只会得到寄存器的缓存副本。如果其中有一个字符,则该状态也被缓存,您读取rx寄存器,然后可能执行某些操作,如果当您回来时状态未从数据缓存中驱逐,则您将获得旧状态,其中显示有一个字符,您的rx缓冲区读取可能也被缓存,因此您可能会在缓存中获得旧值或者外设在读取时没有新值或者您可能会获得新值,但在这些情况下您无法正确访问外设。当开启MMU时,您使用MMU将该外设使用的地址空间标记为不可(数据)缓存,并且您就不会遇到这个问题。对于ARM系统,关闭数据缓存是必要的。
保留I-cache是可以的,因为指令获取只读取指令...对于裸机应用程序来说这是可以的,例如,如果您使用具有潜在读干扰(SPI或I2C闪存)的闪存。但是,这个应用程序是一个引导加载程序,所以您必须要特别小心。例如,您的引导程序在地址0x8000处有一些代码,它至少运行一次,然后您选择将其用作引导程序,引导程序可能位于地址0x10000000,使您可以在0x8000处加载新程序,此加载使用数据访问,因此不经过指令缓存。因此,指令缓存可能具有上一次您在0x8000区域中的所有或部分代码,当您跳转到0x8000处的引导加载程序时,您将获得缓存中的旧程序或旧程序和新程序的混合体(对于未缓存的部分)。因此,如果您的引导加载程序允许使用I-cache,则需要在跳转到引导加载程序之前使缓存无效。
最后,如果您或使用此引导加载程序的任何人想要使用jtag,则会遇到更严重的问题,即不经过i-cache的数据循环被用来将新程序写入ram,当您告诉jtag调试器运行新程序时,您将得到1)仅新程序,2)来自缓存的新程序和旧程序片段的混合体3)来自缓存的旧程序。
因此,在没有mmu的情况下,d-cache是不好的,因为其中包括不在ram中的外围设备等。i-cache可以自担风险使用,但是在调试时您无法控制。
如果您担心或已确认(外部)flash中存在读取干扰,则建议打开i-cache,使用紧密的循环将应用复制到ram中,跳转到ram副本并在那里运行,关闭i-cache(或自担风险使用),不再触碰flash,特别是不要大量读取小区域。像命令行解析器一样的紧密的uart轮询循环是容易受到读取干扰的好地方。

这很明确,但你也折磨了我的可怜英语 :) 非常感谢!! - user2122968
外设访问合理化对我来说没有太多意义。如果您有一个使用1MB节的phys=virt表,您只需将DRAM标记为可缓存/可缓冲即可。设置这些表并不需要太多工作。在启用d-cache时编码肯定更复杂。典型的引导加载程序问题,例如IMB、写缓冲区刷新等,更具有SOC/架构特定性,因此编写通用ARM引导加载程序(例如u-boot)并维护小代码大小(回到设计)更加困难。 - artless noise
关闭mmu后,您没有映射,也无法将其标记为“不可缓存/缓存”。这就是整个问题所在。如果启用了mmu,则dcache是完全可以的,因为您可以将其标记为可缓存或不可缓存。ARM mmus确实具有某种兼容模式(例如查看Linux内核以查看其运行情况),以便您可以编写一项适用于所有或几乎所有ARM的内容。 - old_timer

2
你没有说明你在哪个ARM上工作。不同的ARM之间可能会有很大差异(例如ARM9和ARM Cortex A15之间的差距很大)。
在给定的代码中,第二位被清除然后被设置,但这并不重要,因为这些更改是在R0中完成的。在写入CP15寄存器之前(由指令“mcr P15, 0, R0, C1, C0, 0”执行),ARM的行为不会发生任何变化。
关于启用d-cache/i-cache,这只是一个选择问题,没有必要。在我所使用的产品中,引导加载程序启用L1 I-cache、D-cache、L2 cache和MMU(在跳转到Linux之前禁用所有这些东西)。如果在引导加载程序中使用缓存和MMU,请务必遵循ARM文档关于缓存失效和内存屏障的规定(根据您实际使用的ARM Core)。

非常感谢。我正在分析针对arm920t和s3c2440的u-boot,但我认为在引导加载程序的第一阶段,没有太大的区别,对吧? - user2122968
根据启动设备是文件系统还是原始映像,可能会有很大的差异。当解析文件系统以定位引导映像时,必须维护许多表。代码通常需要多次访问根目录条目、FAT表等。将这些内容缓存起来可以提高加载性能。如果u-boot只是从NAND闪存到RAM进行原始加载(甚至使用NOR进行XIP并设置DRAM等),则几乎没有好处。 - artless noise

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接