I'm trying to write an operating system using OSDev and others. Now, I'm stuck making a keyboard interrupt handler. When I compile my OS and run the kernel using qemu-system-i386 -kernel kernel/myos.kernel everything works perfectly. When I put everything into an ISO image and try to run it using qemu-system-i386 -cdrom myos.iso, it restarts when I press a key. I think it's caused by some problems in my interrupt handler or a bad IDT entry.
My keyboard handler (AT&T syntax):
.globl keyboard_handler
.align 4
keyboard_handler:
pushal
cld
call keyboard_handler_main
popal
iret
My main handler in C:
void keyboard_handler_main(void) {
unsigned char status;
char keycode;
/* write EOI */
write_port(0x20, 0x20);
status = read_port(KEYBOARD_STATUS_PORT);
/* Lowest bit of status will be set if buffer is not empty */
if (status & 0x01) {
keycode = read_port(KEYBOARD_DATA_PORT);
if(keycode < 0)
return;
if(keycode == ENTER_KEY_CODE) {
printf("\n");
return;
}
printf("%c", keyboard_map[(unsigned char) keycode]);
}
}
C function I use to load the:
void idt_init(void)
{
//unsigned long keyboard_address;
unsigned long idt_address;
unsigned long idt_ptr[2];
auto keyboard_address = (*keyboard_handler);
IDT[0x21].offset_lowerbits = keyboard_address & 0xffff;
IDT[0x21].selector = KERNEL_CODE_SEGMENT_OFFSET;
IDT[0x21].zero = 0;
IDT[0x21].type_attr = INTERRUPT_GATE;
IDT[0x21].offset_higherbits = (keyboard_address & 0xffff0000) >> 16;
/* Ports
* PIC1 PIC2
*Command 0x20 0xA0
*Data 0x21 0xA1
*/
write_port(0x20 , 0x11);
write_port(0xA0 , 0x11);
write_port(0x21 , 0x20);
write_port(0xA1 , 0x28);
write_port(0x21 , 0x00);
write_port(0xA1 , 0x00);
write_port(0x21 , 0x01);
write_port(0xA1 , 0x01);
write_port(0x21 , 0xff);
write_port(0xA1 , 0xff);
idt_address = (unsigned long)IDT ;
idt_ptr[0] = (sizeof (struct IDT_entry) * IDT_SIZE) + ((idt_address & 0xffff) << 16);
idt_ptr[1] = idt_address >> 16 ;
load_idt(idt_ptr);
printf("%s\n", "loadd");
}
Files are organized the same way as OSDev's Meaty Skeleton. I do have a different bootloader.
Based on experience I believed that this issue was related to a GDT not being set up. Often when someone says interrupts work with QEMU's -kernel option but not a real version of GRUB it is often related to the kernel developer not creating and loading their own GDT. The Mulitboot Specification says:
‘GDTR’
Even though the segment registers are set up as described above, the ‘GDTR’ may be invalid, so the OS image must not load any segment registers (even just reloading the same values!) until it sets up its own ‘GDT’.
When using QEMU with the -kernel option the GDTR is usually valid but it isn't guaranteed to be that way. When using a real version of GRUB (installed to a hard drive, virtual image, ISO etc) to boot you may discover that the GDTR isn't in fact valid. The first time you attempt to reload any segment register with a selector (even if it is the same value) it may fault. When using interrupts the code segment (CS) will be modified which may cause a triple fault and reboot.
As well the Multiboot Specification doesn't say which selectors point to code or data descriptors. Since the layout of the GDT entries is not known or guaranteed by the Multiboot specification it poses a problem for filling in the IDT entries. Each IDT entry needs to specify a specific selector that points to a code segment.
The Meaty Skeleton tutorial on OSDev doesn't set up interrupts, nor do they modify any of the segment registers so that code likely works with QEMU's -kernel option and a real version of GRUB. Adding IDT and interrupt code on top of the base tutorial probably lead to the failure you are seeing when booting with GRUB. That tutorial should probably make it clearer that you should set up your own GDT and not rely on the one set up by the Multiboot loader.
Related
My Environment
IDE : Keil
Processor : AT89C4051 (Simulation Mode)
I'm trying to work with P1 register (address 0x90 specified by datasheet) by setting its value to all 0 (RESET state) and setting some specific pin to be 1 (SET state). I try this code
int main() {
*((unsigned char*)0x90) = 0;
while(1) {
*((unsigned char*)0x90) = 0xE0;
}
}
But nothing change.
When I use this example every work flawlessly
sfr P1 = 0x90;
int main()
{
P1 = 0;
P1 = 0xE0;
while(1);
}
My question is, what make the different between these code since it's all pointing at address 0x90 using sfr and unsigned char pointer.
You can't access SFRs of the 8051 series microcontroller by indirect accesses.
On devices with more than 128 bytes internal RAM, the I/O addresses 0x80 to 0xFF are used for both the SFRs and the upper half of the internal RAM:
SFRs are accessed by direct addressing only.
The internal RAM is accessed by indirect addressing only.
EDIT:
Source: Right on the product summary page there is the 8051 instruction set that documents this on the very first page.
This question already has answers here:
Creating a C function without compiler generated prologue/epilogue & RET instruction?
(3 answers)
Closed 3 years ago.
I'm trying to implement keyboard interrupt handler using C and QEMU. But when I execute the program my handler print only one character. After that the handler doesn't work at all.
My IDT setup:
struct IDT_entry {
unsigned short int offset_lowerbits;
unsigned short int selector;
unsigned char zero;
unsigned char type_attr;
unsigned short int offset_higherbits;
};
void setup_idt() {
struct IDT_entry IDT[256];
unsigned long keyboard_address;
unsigned long idt_address;
unsigned long idt_ptr[2];
keyboard_address = (unsigned long) keyboard_handler;
IDT[0x21].offset_lowerbits = keyboard_address & 0xffff;
IDT[0x21].selector = 0x8;
IDT[0x21].zero = 0;
IDT[0x21].type_attr = 0x8e;
IDT[0x21].offset_higherbits = (keyboard_address & 0xffff0000) >> 16;
/*
PIC1 PIC2
Commands 0x20 0xA0
Data 0x21 0xA1
*/
// ICW1 - init
outb(0x20, 0x11);
outb(0xA0, 0x11);
// ICW2 - reset offset address if IDT
// first 32 interrpts are reserved
outb(0x21, 0x20);
outb(0xA1, 0x28);
// ICW3 - setup cascading
outb(0x21, 0b0);
outb(0xA1, 0b0);
// ICW4 - env info
outb(0x21, 0b00000011);
outb(0xA1, 0b00000011);
// init finished
// disable IRQs except IRQ1
outb(0x21, 0xFD);
outb(0xA1, 0xff);
idt_address = (unsigned long)IDT;
idt_ptr[0] = (sizeof (struct IDT_entry) * 256) + ((idt_address & 0xffff) << 16);
idt_ptr[1] = idt_address >> 16;
__asm__ __volatile__("lidt %0" :: "m" (*idt_ptr));
__asm__ __volatile__("sti");
}
My keyboard handler:
// Variables for printing ==
unsigned int location = 0;
char* vga = (char*)0xb8000;
char letter;
// =========================
void keyboard_handler() {
if (inb(0x64) & 0x01 && (letter = inb(0x60)) > 0) {
vga[location] = keyboard_map[letter];
vga[location+1] = 0x4;
location += 2;
}
outb(0x20, 0x20);
// __asm__ __volatile__("iret");
}
Main function (it is executed from my asm bootloader):
void kmain() {
setup_idt();
for (;;) {}
}
I think the problem is in "iret" instruction. Without it my kernel prints at least something (only one charachter, like I said before). But when I execute asm volatile("iret"); QEMU prints some garbage and then clear it after every keystroke ("SeaBios ..."). What do I have to do?
Thank you!
If you compile without optimization, asm("iret") will probably run while the stack pointer is still pointing at a saved EBP value, because -fno-omit-frame-pointer is the default and the cleanup epilogue happens after the last C statement of the function.
Or it could be pointing at other saved registers. Anyway, tricking the compiler and jumping out of an inline asm statement is never going to be safe (unless you use asm goto to maybe jump to a C label inside the function, but that doesn't solve your problem).
Also, the C calling convention allows functions to clobber EAX, ECX, EDX, and the FPU state. Even if you did manage to hack an iret into your function, it would corrupt the state of the code that was interrupted. GCC will use SSE/x87 to implement _Atomic int64_t load/store in 32-bit mode, and for copying large objects, unless you compile with -mgeneral-regs-only
Also see #MichaelPetch's answer on the linked duplicate: Creating a C function without compiler generated prologue/epilogue & RET instruction? for more interesting points, and some non-GCC info.
There are 2 solutions here:
write a pure-asm wrapper that saves the call-clobbered regs, calls your C function, then returns with iret
declare your function with __attribute__((interrupt)) to tell GCC it's an interrupt handler. The gcc manual's x86 function attributes has an example.
x86 support for that attribute is somewhat recent compared to traditionally-embedded ISAs like ARM, but modern GCC does know how emit functions that preserve all regs and end with iret. But you still need -mgeneral-regs-only.
See also https://wiki.osdev.org/Interrupt_Service_Routines#GCC_.2F_G.2B.2B which tells you the same thing as this answer.
(It also suggests an evil hack with pushad / popad; leave; iret which only works with optimzation disabled. I would not recommend that if you can possibly use a newer GCC that supports the interrupt attribute.)
The earlier parts of the wiki page cover the general problems with trying to use your own iret, so you can see what the total asm (compiler-generated + yours) would look like for your attempt.
I'm fairly new to OS development and I recently started a hobby project of creating a simple-as-possible text-only operating system. It's written in C with some help from assembly and uses GRUB for booting, and I've been testing it in VirtualBox and also occasionally putting it on a flash drive for testing on an ancient (~2009) laptop. So far I've implemented some basic text output functions, and I think my GDT and IDT implementations are okay given the lack of crashing lately. Currently I'm trying to get an interrupt-driven keyboard driver working.
I think I've got the PICs set up correctly, and it seems I've had luck in giving commands to the PS/2 controller and keyboard and capturing responses via an interrupt handler. For example, here's the debug output when giving the keyboard an identify command:
Initializing kernel...
Setting PS/2 controller status: 0x05
Sending keyboard command: 0xF2
Keyboard interrupt: 0xFA
Keyboard interrupt: 0xAB
Keyboard interrupt: 0x83
The data returned seems to be correct, and this proves that my interrupt handler is able to work multiple times in succession without crashing or anything, so I'm not too worried about my IDT or ISR implementation. Now here's the output when I send the 0xF4 command to the keyboard to start scanning for key presses:
Initializing kernel...
Setting PS/2 controller status: 0x05
Sending keyboard command: 0xF4
Keyboard interrupt: 0xFA
The interrupt with the "acknowledge" status code 0xFA seems promising, but afterwards nothing happens when I press keys. For both examples, I got the same results when running both in VirtualBox and on the laptop I've been using.
Here's some relevant code from the keyboard driver:
#define KEYBD_DATA 0x60
#define KEYBD_CMD 0x64
// wrapper for interrupt service routine written in assembly
extern void keyboard_interrupt();
// called from assembly ISR
void keyboard_handler() {
u8 data = read_port(KEYBD_DATA);
print("Keyboard interrupt: 0x");
printx(data);
putc('\n');
pic_eoi();
}
// functions to print command before sending it to the port
void keyboard_command(u8 cmd) {
print("Sending keyboard command: 0x");
printx(cmd);
putc('\n');
write_port(KEYBD_DATA, cmd);
}
void controller_command(u8 cmd) {
print("Sending controller command: 0x");
printx(cmd);
putc('\n');
write_port(KEYBD_CMD, cmd);
}
void setup_keyboard() {
// flush keyboard output
while(read_port(KEYBD_CMD) & 1)
read_port(KEYBD_DATA);
// set interrupt descriptor table entry (default code segment and access flags)
set_idt_entry(0x21, &keyboard_interrupt);
// activate device
write_port(KEYBD_CMD, 0xAE);
wait();
// get status
write_port(KEYBD_CMD, 0x20);
wait();
u8 status = (read_port(KEYBD_DATA) | 1) & 0x05;
print("Setting PS/2 controller status: 0x");
printx(status);
putc('\n');
wait();
// set status
write_port(KEYBD_CMD, 0x60);
wait();
write_port(KEYBD_DATA, status);
wait();
// enable keyboard scanning
keyboard_command(0xf4);
}
Not that I think it's the root of the problem, but here's the assembly part of the interrupt handler just in case (in GNU assembly):
.extern keyboard_handler
.global keyboard_interrupt
keyboard_interrupt:
cli
pusha
cld
call keyboard_handler
popa
sti
iret
Here's the code that sets up the PICs beforehand:
#define MASTER_CMD 0x20
#define MASTER_DATA 0x21
#define SLAVE_CMD 0xA0
#define SLAVE_DATA 0xA1
#define PIC_EOI 0x20
// hopefully this gives a long enough delay
void wait() {
for (u8 i = 0; i < 255; i++);
}
// alert the PICs that the interrupt handling is done
// (later I'll check whether the slave PIC needs to be sent the EOI, but for now it doesn't seem to hurt to give it anyway)
void pic_eoi() {
write_port(MASTER_CMD, PIC_EOI);
write_port(SLAVE_CMD, PIC_EOI);
wait();
}
void setup_pic() {
write_port(MASTER_CMD, 0x11);
write_port(SLAVE_CMD, 0x11);
wait();
write_port(MASTER_DATA, 0x20);
write_port(SLAVE_DATA, 0x28);
wait();
write_port(MASTER_DATA, 0x4);
write_port(SLAVE_DATA, 0x2);
wait();
write_port(MASTER_DATA, 0x1);
write_port(SLAVE_DATA, 0x1);
wait();
write_port(MASTER_DATA, 0x0);
write_port(SLAVE_DATA, 0x0);
wait();
}
Here's the order of initializations in the main part of the kernel:
// initialize global descriptor table and interrupt descriptor table
setup_gdt();
setup_idt();
// setup hardware interrupts
setup_pic();
setup_keyboard();
activate_idt(); // assembly routine with lidt and sti
I also know that the keyboard is in fact doing its thing and putting scan codes on port 0x60, and I've been able to get a polling method of getting keypresses working, but it's messy and it would make it much harder to handle things like key repetition and keeping track of the shift key. Let me know if more code is needed. Hopefully there's just something obvious I'm either forgetting or doing wrong :)
General reasons why a specific IRQ, some IRQs, or all IRQs may not appear to work:
You haven't enabled interrupts on the CPU with sti (or equivalent)
You haven't enabled the interrupts(s) with a mask sent to the master and slave PICs when you initialise them.
Not properly acknowledging an EOI when an interrupt does occur can disable some or all interrupts depending on the priority of the interrupt.
You have disabled the PICs
You won't get a keyboard interrupt from the PS/2 keyboard unless you have sent a PS/2 controller configuration byte with bit 0 set (bit 1 is interrupt for the mouse)
I'd narrow down the problem space by masking off all external interrupts except the one you are testing. In your case you are interested in IRQ1. To mask off all external interrupts except IRQ1 you can change setup_pic so that:
write_port(MASTER_DATA, 0x0);
write_port(SLAVE_DATA, 0x0);
Becomes:
write_port(MASTER_DATA, ~0x2);
write_port(SLAVE_DATA, ~0x0);
Bits that are set mask off an interrupt and ones that are zero enable them. ~0x2 is the bitmask 0b11111101 and ~0x0 is the bitmask 0b11111111. That should disable all but IRQ1 (bit 1 of master PIC).
You discovered that the problem disappeared by using the suggestion above and then mention your default interrupt handler just does an IRET. You need to send a proper EOI even in your default do nothing IRQ handlers. Don't send EOIs for interrupts unless they come from the PICs. In your case IDT entry 0x20 to 0x2f (inclusive) need to have handlers that send proper EOIs. More detailed information on properly handling EOIs can be found on the OSDev Wiki
I'd guess what is going on is that on the first timer interrupt (IRQ0) you send no EOI, and that would effectively disable all external interrupts. Until an EOI is sent all external interrupts of equal or lower priority will be disabled. IRQ0 (timer) is the highest priority, so not sending an EOI effectively disables all external interrupts until an EOI is sent.
I'm trying to use SysTick_Handler in SW4STM32 for Linux, but whenever the SysTick interrupt is triggered, execution jumps to somewhere in system memory. By my understanding, it should jump into the void SysTick_Handler(void) that I declared, or failing that, into Default_Handler declared in startup_stm32.s where the interrupt vector table is defined. I set a break point in my SysTick_Handler, but it is never reached. In the code below, it gets through init_systick() and stays in the endless for loop if I don't include SysTick_CTRL_TICKINT_Msk, as expected, but when I do include it, the debugger tells me it ends up somewhere around address 0x1fffda7c.
main.c:
#include "stm32f0xx.h"
volatile uint32_t ticks = 0;
void SysTick_Handler(void) {
ticks++;
}
void init_systick(void) {
SysTick->LOAD = 43999;
SCB->SHP[1] |= 0x40000000L;
SysTick->VAL = 0;
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_TICKINT_Msk | SysTick_CTRL_ENABLE_Msk;
}
int main(void)
{
init_systick();
for(;;);
}
I verified from the .map file that the linker is using the declared SysTick_Handler instead of Default_Handler.
I also tried the following variation to use the standard peripheral library for setup, along with other interrupt priority values, with the same results:
#include "stm32f0xx.h"
volatile uint32_t ticks = 0;
void SysTick_Handler(void) {
ticks++;
}
void init_systick(void) {
SysTick_Config(44000);
NVIC_EnableIRQ(SysTick_IRQn);
NVIC_SetPriority(SysTick_IRQn, 0);
}
int main(void)
{
init_systick();
for(;;);
}
This shouldn't be relevant, but since the target doesn't have a timing crystal, I also modified void SetSysClock(void) in system_stm32f0xx.c to use the HSI clock and PLL, which appears to be working correctly:
static void SetSysClock(void)
{
RCC->CFGR = (RCC->CFGR & ~RCC_CFGR_SW) | RCC_CFGR_SW_HSI;
while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_HSI) ;
FLASH->ACR = FLASH_ACR_PRFTBE | FLASH_ACR_LATENCY;
RCC->CR &= ~RCC_CR_PLLON;
while (RCC->CR & RCC_CR_PLLRDY) ;
RCC->CFGR = (RCC->CFGR & ~RCC_CFGR_PLLMUL & ~RCC_CFGR_PLLSRC) | RCC_CFGR_PLLMUL11; // PLL takes 8 MHz HSI / 2 as input
RCC->CR |= RCC_CR_PLLON;
while (!(RCC->CR & RCC_CR_PLLRDY)) ;
RCC->CFGR = (RCC->CFGR & ~RCC_CFGR_SW) | RCC_CFGR_SW_PLL;
while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL) ;
}
-- EDIT: More info requested in the comments --
It's an M0 core, so it doesn't have vector table relocation. From the reference manual section 2.5 (page 44):
Unlike Cortex ® M3 and M4, the M0 CPU does not support the vector table relocation.
Address 0x00000000 should be mapped either to FLASH memory at 0x08000000, system memory at 0x1fffd800, or to SRAM at 0x20000000. The memory at address 0x00000000 matches system memory at 0x1fffd800, even though SYSCFG_CFGR1 MEM_MODE is set to 00, which should map FLASH memory there. Main FLASH memory at address 0x08000000 contains the correct vector table, but address 0x00000000 is populated with address 0x1fffd99d for the SysTick vector (and all other non-NULL vectors except the reset vector, which is 0x1fffdc41); the vectors shown by the debugger at address 0x00000000 are consistent with the observed behavior. All of this information was collected while paused at a breakpoint at address 0x08000298 (a correct position in FLASH memory where the correct code has been loaded), before executing the interrupt.
The VTOR is referenced in the arm-v6 reference manual. It is a good idea to check this.
https://static.docs.arm.com/ddi0419/d/DDI0419D_armv6m_arm.pdf
0xE000ED08 VTOR RW 0x00000000a Vector Table Offset Register, VTOR on page B3-231
That reference manual is for the arm-v6M architecture which is the architecture for cortex-m0 processors. This is the bible. Most of the generic cortex-m0 features of the stm line will not be mentioned in stm refrenece manuals, just in the arm arm.
I'll reiterate, check the VTOR.
And make sure you are building for the right line of STM32F030!
The STM32F030x4 and STM32F030x6 micros have a different memory map than the STM32F030x8.
This sounds like there might be a problem with the linker file.
Can you verify that your linker file has something that looks like the following? (if it is a default file it will probably be far more complex).
. = 0;
.text 0 :
{
*(.vector);
crt0*(.text*);
main*(.text*);
*(.text*);
} > flash
Basically what this is saying is that the 'text' (code) of the program starts at address 0x0, and the first thing to put there is the vector table, followed by startup code, main, and then other code.
You will also then want to check that you have some file specifying this vector table's contents that also agrees it should be at address 0x0. This example is from an ATSAMD21E18A.
.section .vector, "a", %progbits
.equ stack_base, 0x20004000
.word stack_base
.word reset_handler
.word nmi_handler
.word hardfault_handler
.word 0
// ...
.word 0
.word systick_handler
// ...
The important thing is that it is marked as the section vector which the linker file will try to put at 0x0. For a processor with VTOR (like the M0+), this table might be specified in C without any special markings, and its location won't matter, but since you don't have VTOR, you need to make sure the linker knows to put this section right at 0x0.
jonnconn is correct. VTOR is the issue. I just wanted to add to his post (but cannot because my reputation isn't high enough) that I ran into this issue recently with a new CubeMx project.
VTOR is set to 0 on reset. VTOR is initialized in low level init before main. SystemInit in system_stm32l4xx.c (or similar for your part) seems to only initialize VTOR if USER_VECT_TAB_ADDRESS is defined. But CubeMx had this commented out. I had to uncomment it (it is in the same file), and leave VECT_TAB_OFFSET as 0. Afterwards, VTOR config was correctly set to FLASH_BASE (assuming VECT_TAB_SRAM was left undefined).
According the datasheet, that region is the system memory (for built-in bootloader). You may try two things:
Double check BOOTx pins to make sure the MCU loads FLASH instead of the system memory.
Make sure you assigned SCB->VTOR to the correct address of your own interrupt vector table.
From this site: http://balau82.wordpress.com/2010/02/28/hello-world-for-bare-metal-arm-using-qemu/
I can use C code to print out a string in qemu simulator.
volatile unsigned int * const UART0DR = (unsigned int *)0x101f1000;
void print_uart0(const char *s) {
while(*s != '\0') { /* Loop until end of string */
*UART0DR = (unsigned int)(*s); /* Transmit char */
s++; /* Next char */
}
}
I need to do the same thing in C code with gumstix connex board in qemu (with -M connex option), which uses 0x40100000 or 0x40700000 for the memory mapped uart address, but nothing is shown in the screen.
I tried with some data checking code, but it doesn't still work.
volatile unsigned int * const UART0DR = (unsigned int *)0x40100000;
volatile unsigned int * const UART_LSR = (unsigned int *)0x40100014;
#define LSR_TDRQ (1 << 5) // Transmit Data Request
void print_uart0(const char *s) {
while(*s != '\0') { /* Loop until end of string */
while(( *UART_LSR & LSR_TDRQ ) == 0 );
*UART0DR = (unsigned int)(*s); /* Transmit char */
s++; /* Next char */
}
}
What might be wrong? Is PXA255 uses different way to use uart?
I searched the source code of pxa, and gumstix, maybe gumstix may use different methods to do hart communication in qemu.
From your linked page you may have noticed the following text:
The code that emulates the serial port inside QEMU (here in the source repository) implements a subset of the functionalities of the PL011 Prime Cell UART from ARM
and
The QEMU model of the PL011 serial port ignores the transmit FIFO capabilities; in a real system on chip the “Transmit FIFO Full” flag must be checked in the UARTFR register before writing on the UARTDR register.
You won't be able to use the same code on both QEMU and the PXA255 since the implementation of the UART is different.
To have the UART function correctly on the PXA255 board will require a lot more setup and would typically involve the following:
Configuration of the clock subsystem registers to ensure that the UART peripheral is receiving a clock from the main clock system on the CPU.
Configuration of the UART peripheral registers according to the desired use. You may need to configure registers which control baud rate register, parity control, number of data bits.
Modification of your code which writes to the UART. UART peripherals typically contain a FIFO (sometimes just a single byte) which is used during transmission and reception. To transmit a character you first have to ensure that the previous character has finished transmission before placing the next character for transmit in the output data register.
There is no substitute for reading the UART data sheet in detail and following all the information listed there.
qemu should be relaxed about the accurate emulation of the UART.
Below code works:
startup.s:
.global startup
startup:
ldr sp, startup_stack_top
bl c_startup
b .
startup_stack_top:
.word 0xa4000000
os.c:
void write_uart(char *str)
{
char c;
volatile char *uart = (char *)0x40100000;
while (1) {
c = *str;
if (!c)
break;
*uart = c;
str++;
}
}
void c_startup()
{
write_uart("hello\r\n");
}
linker script os.ld:
ENTRY(startup)
SECTIONS
{
. = 0x0;
.startup : { startup.o(.text) }
.text : { *(.text) }
.rodata : { *(.rodata) }
.data : { *(.data) }
.bss : { *(.bss COMMON) }
}
Build commands:
#pfx=arm-none-eabi-
$(pfx)as -g -march=armv5te startup.s -o startup.o
$(pfx)gcc -g -c -march=armv5te os.c -o os.o
$(pfx)ld -T os.ld os.o startup.o -o os.elf
$(pfx)objcopy -O binary os.elf os.bin
dd of=flash.img bs=128k count=128 if=/dev/zero
dd of=flash.img bs=128k conv=notrunc if=os.bin
Run command:
qemu-system-arm -M connex -m 128M -snapshot -pflash flash.img
Unless there's a bunch of other code you aren't showing, you're missing several important things here:
Clocks and power for the UART module must be enabled before it will function. See section 3 of the PXA255 manual.
The GPIOs used by the UART must be configured before the UART will work correctly (e.g, by setting appropriate pin directions and alternate functions). See section 4.1 of the PXA255 manual.
The UART must be configured (e.g, baud rate, etc.) before you start writing data to it. The PXA255 manual does not explicitly include information on these registers; you will need to cross-reference the 16550 datasheet.
While writing data to the UART, you must ensure the UART is in an appropriate state to receive data (e.g, that the transmit buffer is not full), and wait for it to enter an appropriate state if it is not. Refer to the 16550 datasheet, or to a general tutorial on use of this UART.
The UART implementation in QEMU is intended as a debugging tool, not as a full emulation of the UART in a real device. Just because something works in QEMU doesn't mean it will work on real hardware!
As Austin Phillips pointed out, it requires a lot of setup code to make it work with UART serial communication. What confused me before was that I could make the UART communication work without any setup with qemu-system-arm -M versatilepb -m 128M -nographic -kernel test.bin, but this is versatilepb board emulation. My guess is that for versatilepb board, UART setup is just finished before my binary runs.
Mimicking that, what I did was to use a uboot, which does all of the UART setup, and load my binary image from the uboat, and everything works fine.