【问题标题】:Memory mapped IO and GCC optimization内存映射 IO 和 GCC 优化
【发布时间】:2013-06-24 17:22:46
【问题描述】:

我一直在搞乱剑桥烘焙 pi 教程(基本 OS 开发,带有一些树莓派的小演示)。只有我一直在用 C 编写代码。我已经设置好开发环境,并且可以在关闭但未打开 GCC 优化的情况下成功运行我的代码。

问题出现在我使用内存映射 IO 的代码中(如果我编译除以下文件之外的所有其他内容并进行优化,它就可以工作)。我最初认为是我的指针没有被声明为易失性,因此编译器正在优化对内存的实际写入并改用寄存器。但即使我将它们声明为 volatile,问题仍然存在。

这是我写内存的方法:

#define UART0_CR ((volatile uint32_t *) (UART0_BASE + 0x30))
...
*UART_CR = 0;

指针是 volatile 类型,所以我不明白为什么 GCC 会决定不进行实际写入。还有什么我需要注意的吗?我对 volatile 的使用有误解吗?

完整的工作文件(无论如何都关闭了优化):

#include <stdint.h>
#include <uart.h>

#define GPIO_BASE   0x20200000
#define GPPUD       ((volatile uint32_t *) (GPIO_BASE + 0x94))
#define GPPUDCLK0   ((volatile uint32_t *) (GPIO_BASE + 0x98))

#define UART0_BASE      0x20201000
#define UART0_DR        ((volatile uint32_t *) (UART0_BASE + 0x00))
#define UART0_FR        ((volatile uint32_t *) (UART0_BASE + 0x18))
#define UART0_IBRD      ((volatile uint32_t *) (UART0_BASE + 0x24))
#define UART0_FBRD      ((volatile uint32_t *) (UART0_BASE + 0x28))
#define UART0_LCRH      ((volatile uint32_t *) (UART0_BASE + 0x2C))
#define UART0_CR        ((volatile uint32_t *) (UART0_BASE + 0x30))
#define UART0_IMSC      ((volatile uint32_t *) (UART0_BASE + 0x38))
#define UART0_ICR       ((volatile uint32_t *) (UART0_BASE + 0x44))

static void delay(int32_t count) {
    asm volatile("__delay%=: subs %[count], %[count], #1; bne __delay%=\n"
                :
                : [count]"r"(count) 
                : "cc"
    );
}

void uart_init() {    
    *UART0_CR = 0; // Disable UART0.    
    *GPPUD = 0;     // Disable pull up/down for all GPIO pins & delay for 150 cycles.
    delay(150);   
    *GPPUDCLK0 = (1 << 14) | (1 << 15); // Disable pull up/down for pin 14,15 & delay for 150 cycles.
    delay(150);   
    *GPPUDCLK0 = 0; // Write 0 to GPPUDCLK0 to make it take effect.    
    *UART0_ICR = 0x7FF; // Clear pending interrupts.
    *UART0_IBRD = 1; //Set rate
    *UART0_FBRD = 40;     
    *UART0_LCRH = (1 << 4) | (1 << 5) | (1 << 6); // Enable FIFO & 8 bit data transmissio (1 stop bit, no parity).    
    *UART0_IMSC = (1 << 1) | (1 << 4) | (1 << 5) | (1 << 6) | (1 << 7) | (1 << 8) | (1 << 9) | (1 << 10); // Mask all interrupts.   
    *UART0_CR = (1 << 0) | (1 << 8) | (1 << 9);  // Enable UART0, receive & transfer part of UART.  
}

void uart_putc(uint8_t byte) {    
    while (1) { // wait for UART to become ready to transmit    
        if (!(*UART0_FR & (1 << 5))) break;
    }   
    *UART0_DR = byte; // Transmit
}

void uart_puts(const char *str) {
    while (*str) {
        uart_putc(*str++);
    }
}

编辑:

查找了如何查看程序集,非常有用,谢谢。如果我从 uart i init 中取出前 2 次写入(直到第一次延迟调用),我得到:

未优化:

uart_init:
    @ args = 0, pretend = 0, frame = 0
    @ frame_needed = 1, uses_anonymous_args = 0
    stmfd   sp!, {fp, lr}
    add fp, sp, #4
    ldr r3, .L3
    mov r2, #0
    str r2, [r3, #0]
    ldr r3, .L3+4
    mov r2, #0
    str r2, [r3, #0]

优化:

uart_init:
    @ args = 0, pretend = 0, frame = 0
    @ frame_needed = 0, uses_anonymous_args = 0
    @ link register save eliminated.
    ldr r3, .L2
    ldr r2, .L2+4
    mov r1, #0
    str r1, [r3, #48]
    str r1, [r2, #148]

唯一的区别似乎是对于未优化的,它不会向 .L2 和 .L2+4 标签添加偏移量,而优化的标签会这样做。除非这些标签上的指针已经准备好在它们上面计算偏移量。

我有一个分叉的 qemu (qemu-rpi) 为支持树莓派而修改,所以我将尝试检查 r3 和 r2 寄存器中加载了哪些值,以查看它们是否是正确的指针,然后再进行存储和然后我要检查putc是否使用断点在传输上循环。对我的环境还不是很熟练,所以这可能需要我一段时间!

【问题讨论】:

  • 你有什么证据表明实际的写入没有完成?你看过程序集转储吗?
  • 代码乍一看还不错。优化后会发生什么? uart_putc() 是否会一直循环等待第 5 位?
  • 出于这个和其他原因,我抽象了我的 Mem/IO 读写,github.com/dwelch67/raspberrypi。您可以玩游戏,直到您对编译器特定的属性感到沮丧,并且在某些时候仍然会遇到优化器问题。在我的例子中,硬件 I/O 是在 asm 中,但基本上是在一个单独的文件中,不是优化的一部分。
  • 是的,我想我可能会将我的 mmio 写入一个单独的汇编函数中。现在问题已解决,但 C 中的硬件 IO 似乎太脆弱了,我没有编译器知识来正确处理这些问题。谢谢大家。
  • “C 中的硬件 I/O”并不脆弱。你只需要注意你“在金属上”的事实。考虑到这一点,您不能使用抽象或以其他方式尝试通过分层来消除自己。 C 对此非常方便,否则需要 ASM。请记住,C 最初被设计为可移植的汇编程序。

标签: c io raspberry-pi memory-mapping


【解决方案1】:

将优化器与与硬件对话的代码一起使用可能会导致奇怪的行为。

尝试在与您的硬件通信的函数前面使用__attribute__((noinline))。我还建议将所有与硬件对话的代码放入自己的文件中并关闭优化。优化器可以对这些项目重新排序或内联。

【讨论】:

  • 好的,这解决了问题。我尝试使用该属性,发现它只需要在内联汇编延迟功能上。我认为这会破坏延迟并导致串行设备太快接收命令。为什么是这样?优化器是否有可能将延迟代码内联到方法中而忘记了它的易失性并优化了延迟?
  • 非常感谢。我想我会将我的 mmio 写入放入一个汇编函数中,我将从 C 中调用,以避免将来出现此类问题!
  • 我不记得优化器可以对您的代码做的所有事情。但它的重新排序、删除和内联选择非常了不起。
  • 很好奇。即使函数是内联的,asm 中的 volatile 也应该防止它被移动或组合。不过,我想知道:这个特殊的 gcc 变体是否支持 %= 用于唯一化标签?如果没有,内联扩展延迟会导致一些严重的问题。 (本地标签可用吗?即0:..., 0b 分支回到最近的标签0。)
  • 我的经验是 volatile 只会告诉编译器,如果没有明显的引用,则无法优化该项目。要使用 in-line 制作唯一标签,请将函数名称添加到所有标签中。 ASM 标签是全局可见的。
【解决方案2】:

我正在尝试完成同样的事情 但我没有定义,而是像这样分配:

volatile const int *Register = (volatile const int *)0xFFFEF000;

然后你可以通过名称直接将值发送到寄存器:

Register = 0xFFFFFFFF;

【讨论】:

  • 我认为volatile int *const Register 会更好。你永远不想改变硬件寄存器的地址,但它的内容很可能需要改变……
猜你喜欢
  • 2011-04-22
  • 2020-06-13
  • 2012-04-02
  • 2019-06-07
  • 2015-08-31
  • 1970-01-01
  • 2017-11-26
  • 2011-04-20
  • 2012-01-21
相关资源
最近更新 更多