【发布时间】: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