In my application microcontroller stm32f103 is receiving by USART fixed lenght messages, they contains gps velocity which is big endian data. But elements in structure are small endian. Is there any way without doing it manually to write it in correct way?
typedef struct {
uint32_t test1;
uint16_t test2;
}Mst;
uint8_t myArray[6] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 };
void main()
{
Mst * myStruct_p = (Mst)myArray;
}
But after that myStruct_p->test1 equals 0x030201, but should be 0x010203, and myStruct_p->test2 equals 0x0605, but should be 0x0506.
As it is ARM-Cortex M3 we can use special processor instructions. ARM CMSIS have a very handy intrinsic functions __REV & __REV16 which actually compile to the single machine code instruction.
typedef union
{
struct
{
uint32_t test1;
uint16_t test2;
};
uint8_t bytes[6];
}Mst;
Mst mst = {.bytes = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 }};
void main()
{
mst.test1 = __REV(mst.test1);
mst.test2 = __REV16(mst.test2);
}
Casting in that way won't work.
You can do a "deserialize" operation.
Although this might be slower than some other methods, it allows you to control the protocol better (e.g. the struct member order doesn't have to follow the protocol) And, there might be padding in the struct, which would show up if we added (e.g.) uint32_t test3; to the end of the struct.
Here's the code:
#include <stdio.h>
#include <stdint.h>
typedef struct {
uint32_t test1;
uint16_t test2;
} Mst;
uint8_t myArray[6] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 };
uint32_t
get32(uint8_t **base)
{
uint8_t *ptr;
uint32_t val = 0;
ptr = *base;
for (int len = sizeof(uint32_t); len > 0; --len) {
val <<= 8;
val |= *ptr++;
}
*base = ptr;
return val;
}
uint16_t
get16(uint8_t **base)
{
uint8_t *ptr;
uint16_t val = 0;
ptr = *base;
for (int len = sizeof(uint16_t); len > 0; --len) {
val <<= 8;
val |= *ptr++;
}
*base = ptr;
return val;
}
int
main(void)
{
Mst myS;
uint8_t *arr = myArray;
myS.test1 = get32(&arr);
myS.test2 = get16(&arr);
printf("test1=%8.8X test2=%4.4X\n",myS.test1,myS.test2);
return 0;
}
UPDATE:
Yes guys that will work but, i would like to use the processor as little as possible. This is rather manualy putting bytes in to correct order. Also Procedure Mst * struct_p = (Mst*)myArray works safe, because while defining struct i use __attribute__((packed)), just forgoten to write this
I was going to mention/suggest packed as a possibility.
In either case you can use [under GNU]: byteswap.h to get bswap_*. Endian swapping is quite common, so these are [highly] optimized for the given arch. They can even invoke compiler intrinsics (e.g. __builtin_bswap32) which utilize any special instructions the arch has (e.g. x86 has the bswap instruction, and arm has rev16).
So, you can do [i.e. replace the for loop with] (e.g.) bswap_*:
#include <stdio.h>
#include <stdint.h>
#include <byteswap.h>
typedef struct {
uint32_t test1;
uint16_t test2;
uint32_t test3;
} __attribute__((__packed__)) Mst;
uint8_t myArray[] = {
0x01, 0x02, 0x03, 0x04,
0x05, 0x06,
0x07, 0x08, 0x09, 0x0A
};
void
getall(Mst *myS)
{
myS->test1 = bswap_32(myS->test1);
myS->test2 = bswap_16(myS->test2);
myS->test3 = bswap_32(myS->test3);
}
int
main(void)
{
Mst *myS = (Mst *) myArray;
getall(myS);
printf("test1=%8.8X test2=%4.4X test3=%8.8X\n",
myS->test1,myS->test2,myS->test3);
return 0;
}
Overlaying a struct pointer onto a byte array may not necessarily work due to alignment issues and structure padding.
The proper way would be to first use memcpy to copy over the elements:
Mst myStruct;
memcpy(&myStruct.test1, myArray, sizeof(myStruct.test1);
memcpy(&myStruct.test2, myArray + 4, sizeof(myStruct.test2);
Then use ntohs and ntohl which converts 16 and 32 bit values respectively from big endian format to the host's endianness.
myStruct.test1 = ntohl(myStruct.test1);
myStruct.test2 = ntohs(myStruct.test2);
If you just need to convert 8bit data into big endian let say data format will be in order (A B C D) and in little endian data format will be (D C B A) if final result will be stored in 32bit variable and if 16 bit variable then format will (A B) and (B A) .
So this can be done with little bit shifting technique if you know your UART data comming in which format.
Let us first assume that your UART data comes in Little endian and you want it in big endian then you can do like this.
//no structure required
uint32_t myValue;
uint16_t value;
uint8_t myArray[4] = { 0x04, 0x03, 0x02, 0x01 };
// little endian data for 32 bit
uint8_t A[2] = {0x02 ,0x01}; // little endian data for 16 bit
void main()
{
myValue = myArray[3]<<24 | myArray[2]<<16 | myArray[1]<<8 | myArray[0]; // result in big endian 32 bit value
value = A[1]<<8 | A[0]; // result in big endian 16bit value
}
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 have been measuring clock cycle count on the cortex m4 and would now like to do it on the cortex m7. The board I use is STM32F746ZG.
For the m4 everything worked with:
volatile unsigned int *DWT_CYCCNT;
volatile unsigned int *DWT_CONTROL;
volatile unsigned int *SCB_DEMCR;
void reset_cnt(){
DWT_CYCCNT = (volatile unsigned int *)0xE0001004; //address of the register
DWT_CONTROL = (volatile unsigned int *)0xE0001000; //address of the register
SCB_DEMCR = (volatile unsigned int *)0xE000EDFC; //address of the register
*SCB_DEMCR = *SCB_DEMCR | 0x01000000;
*DWT_CYCCNT = 0; // reset the counter
*DWT_CONTROL = 0;
}
void start_cnt(){
*DWT_CONTROL = *DWT_CONTROL | 0x00000001 ; // enable the counter
}
void stop_cnt(){
*DWT_CONTROL = *DWT_CONTROL & 0xFFFFFFFE ; // disable the counter
}
unsigned int getCycles(){
return *DWT_CYCCNT;
}
The problem is that the DWT_CTRL register isn't changed when I run on the m7 and remains 0x40000000 instead of changing to 0x40000001 so the cycle count is always zero. From what I have read in other posts it seems like you need to set the FP_LAR register to 0xC5ACCE55 to be able to change DWT_CTRL.
I added these defines (have tried both FP_LAR_PTR addresses below):
#define FP_LAR_PTR ((volatile unsigned int *) 0xe0000fb0) //according to reference
//#define FP_LAR_PTR ((volatile unsigned int *) 0xe0002fb0) //according to guy on the internet
// Lock Status Register lock status bit
#define DWT_LSR_SLK_Pos 1
#define DWT_LSR_SLK_Msk (1UL << DWT_LSR_SLK_Pos)
// Lock Status Register lock availability bit
#define DWT_LSR_SLI_Pos 0
#define DWT_LSR_SLI_Msk (1UL << DWT_LSR_SLI_Pos)
// Lock Access key, common for all
#define DWT_LAR_KEY 0xC5ACCE55
and this function:
void dwt_access_enable(unsigned int ena){
volatile unsigned int *LSR;
LSR = (volatile unsigned int *) 0xe0000fb4;
uint32_t lsr = *LSR;;
//printf("LSR: %.8X - SLI MASK: %.8X\n", lsr, DWT_LSR_SLI_Msk);
if ((lsr & DWT_LSR_SLI_Msk) != 0) {
if (ena) {
//printf("LSR: %.8X - SLKMASK: %.8X\n", lsr, DWT_LSR_SLK_Msk);
if ((lsr & DWT_LSR_SLK_Msk) != 0) { //locked: access need unlock
*FP_LAR_PTR = DWT_LAR_KEY;
printf("FP_LAR directly after change: 0x%.8X\n", *FP_LAR_PTR);
}
} else {
if ((lsr & DWT_LSR_SLK_Msk) == 0) { //unlocked
*FP_LAR_PTR = 0;
//printf("FP_LAR directly after change: 0x%.8X\n", *FP_LAR_PTR);
}
}
}
}
When I call the uncommented print I get 0xC5ACCE55 but when I printed it after the return of the function I get 0x00000000 and I have no idea why. Am I on the right track or is this completely wrong?
Edit: I think it also would be good to mention that I have tried without all the extra code in the function and only tried to change the LAR register.
BR
Gustav
Looking at the docs again, I'm now incredibly suspicious of a typo or copy-paste error in the ARM TRM. 0xe0000fb0 is given as the address of ITM_LAR, DWT_LAR and FP_LSR (and equivalently for *_LSR). Since all the other ITM registers are in page 0xe0000000, it looks an awful lot like whoever was responsible for that part of the Cortex-M7 documentation took the Cortex-M4 register definitions, added the new LAR and LSR to the ITM page, then copied them to the DWT and FPB pages updating the names but overlooking to update the addresses.
I'd bet my dinner that you're unwittingly unlocking ITM_LAR (or the real FP_LAR), and DWT_LAR is actually at 0xe0001fb0.
EDIT by dwelch
Somebody owes somebody a dinner.
hexstring(GET32(0xE0001FB4));
hexstring(GET32(0xE0001000));
hexstring(GET32(0xE0001004));
hexstring(GET32(0xE0001004));
PUT32(0xE000EDFC,0x01000000);
hexstring(GET32(0xE0001FB4));
hexstring(GET32(0xE0001000));
hexstring(GET32(0xE0001004));
hexstring(GET32(0xE0001004));
PUT32(0xE0001000,0x40000001);
hexstring(GET32(0xE0001FB4));
hexstring(GET32(0xE0001000));
hexstring(GET32(0xE0001004));
hexstring(GET32(0xE0001004));
PUT32(0xE0001FB0,0xC5ACCE55);
PUT32(0xE0001000,0x40000001);
hexstring(GET32(0xE0001FB4));
hexstring(GET32(0xE0001000));
hexstring(GET32(0xE0001004));
hexstring(GET32(0xE0001004));
output
00000000
00000000
00000000
00000000
00000003
40000000
00000000
00000000
00000003
40000000
00000000
00000000
00000001
40000001
0000774F
0000B311
The table in the TRM is funny looking and as the other documentation shows you add the 0xFB0 and 0xFB4 to the base, the rest of the DWT for the Cortex-M7 is 0xE0001xxx and indeed it appears that the LAR and LSR are ate 0xE0001FB0 and 0xE0001FB4.
I would advise against creating your own register definitions when they are defined as part of the CMSIS - to do so requires that both the documentation and your interpretation of it are correct. In this case it appears that the documentation is indeed incorrect, but that the CMSIS headers are correct. It is a lot easier to validate the CMSIS headers automatically than it is to verify the documentation is correct, so I would trust the CMSIS every time.
I am not sure what register FP_LAR might refer to, but your address assignment refers to ITM_LAR, but it seems more likely that you intended DWT_LAR which Cortex-M4 lacks.
Despite my advice to trust it, CMSIS 4.00 omits to define masks for DWT_LSR/SWT_LAR, but I believe they are identical to the corresponding ITM masks.
Note also that the LAR is a write-only register - any attempt to read it is meaningless.
Your code using CMSIS would be:
#include "core_cm7.h" // Applies to all Cortex-M7
void reset_cnt()
{
CoreDebug->DEMCR |= 0x01000000;
DWT->CYCCNT = 0; // reset the counter
DWT->CTRL = 0;
}
void start_cnt()
{
DWT->CTRL |= 0x00000001 ; // enable the counter
}
void stop_cnt()
{
DWT->CTRL &= 0xFFFFFFFE ; // disable the counter
}
unsigned int getCycles()
{
return DWT->CYCCNT ;
}
// Not defined in CMSIS 4.00 headers - check if defined
// to allow for possible correction in later versions
#if !defined DWT_LSR_Present_Msk
#define DWT_LSR_Present_Msk ITM_LSR_Present_Msk
#endif
#if !defined DWT_LSR_Access_Msk
#define DWT_LSR_Access_Msk ITM_LSR_Access_Msk
#endif
#define DWT_LAR_KEY 0xC5ACCE55
void dwt_access_enable( unsigned ena )
{
uint32_t lsr = DWT->LSR;;
if( (lsr & DWT_LSR_Present_Msk) != 0 )
{
if( ena )
{
if ((lsr & DWT_LSR_Access_Msk) != 0) //locked: access need unlock
{
DWT->LAR = DWT_LAR_KEY;
}
}
else
{
if ((lsr & DWT_LSR_Access_Msk) == 0) //unlocked
{
DWT->LAR = 0;
}
}
}
}
I'm trying to program a very simple kernel for learning purposes. After reading a bunch of articles about the PIC and IRQs in the x86 architecture,
I've figured out that IRQ1 is the keyboard handler. I'm using the following code to print the keys being pressed:
#include "port_io.h"
#define IDT_SIZE 256
#define PIC_1_CTRL 0x20
#define PIC_2_CTRL 0xA0
#define PIC_1_DATA 0x21
#define PIC_2_DATA 0xA1
void keyboard_handler();
void load_idt(void*);
struct idt_entry
{
unsigned short int offset_lowerbits;
unsigned short int selector;
unsigned char zero;
unsigned char flags;
unsigned short int offset_higherbits;
};
struct idt_pointer
{
unsigned short limit;
unsigned int base;
};
struct idt_entry idt_table[IDT_SIZE];
struct idt_pointer idt_ptr;
void load_idt_entry(char isr_number, unsigned long base, short int selector, char flags)
{
idt_table[isr_number].offset_lowerbits = base & 0xFFFF;
idt_table[isr_number].offset_higherbits = (base >> 16) & 0xFFFF;
idt_table[isr_number].selector = selector;
idt_table[isr_number].flags = flags;
idt_table[isr_number].zero = 0;
}
static void initialize_idt_pointer()
{
idt_ptr.limit = (sizeof(struct idt_entry) * IDT_SIZE) - 1;
idt_ptr.base = (unsigned int)&idt_table;
}
static void initialize_pic()
{
/* ICW1 - begin initialization */
write_port(PIC_1_CTRL, 0x11);
write_port(PIC_2_CTRL, 0x11);
/* ICW2 - remap offset address of idt_table */
/*
* In x86 protected mode, we have to remap the PICs beyond 0x20 because
* Intel have designated the first 32 interrupts as "reserved" for cpu exceptions
*/
write_port(PIC_1_DATA, 0x20);
write_port(PIC_2_DATA, 0x28);
/* ICW3 - setup cascading */
write_port(PIC_1_DATA, 0x00);
write_port(PIC_2_DATA, 0x00);
/* ICW4 - environment info */
write_port(PIC_1_DATA, 0x01);
write_port(PIC_2_DATA, 0x01);
/* Initialization finished */
/* mask interrupts */
write_port(0x21 , 0xff);
write_port(0xA1 , 0xff);
}
void idt_init()
{
initialize_pic();
initialize_idt_pointer();
load_idt(&idt_ptr);
}
load_idt just uses the lidt x86 instruction. Afterwards I'm loading the keyboard handler:
void kmain(void)
{
//Using grub bootloader..
idt_init();
kb_init();
load_idt_entry(0x21, (unsigned long) keyboard_handler, 0x08, 0x8e);
}
This is the implementation:
#include "kprintf.h"
#include "port_io.h"
#include "keyboard_map.h"
void kb_init(void)
{
/* 0xFD is 11111101 - enables only IRQ1 (keyboard)*/
write_port(0x21 , 0xFD);
}
void keyboard_handler(void)
{
unsigned char status;
char keycode;
char *vidptr = (char*)0xb8000; //video mem begins here.
/* Acknownlegment */
int current_loc = 0;
status = read_port(0x64);
/* Lowest bit of status will be set if buffer is not empty */
if (status & 0x01) {
keycode = read_port(0x60);
if(keycode < 0)
return;
vidptr[current_loc++] = keyboard_map[keycode];
vidptr[current_loc++] = 0x07;
}
write_port(0x20, 0x20);
}
This is the extra code I'm using:
section .text
global load_idt
global keyboard_handler
extern kprintf
extern keyboard_handler_main
load_idt:
sti
mov edx, [esp + 4]
lidt [edx]
ret
global read_port
global write_port
; arg: int, port number.
read_port:
mov edx, [esp + 4]
in al, dx
ret
; arg: int, (dx)port number
; int, (al)value to write
write_port:
mov edx, [esp + 4]
mov al, [esp + 4 + 4]
out dx, al
ret
This is my entry point:
bits 32
section .text
;grub bootloader header
align 4
dd 0x1BADB002 ;magic
dd 0x00 ;flags
dd - (0x1BADB002 + 0x00) ;checksum. m+f+c should be zero
global start
extern kmain
start:
; cli ;block interrupts
mov esp, stack_space ;set stack pointer
call kmain
hlt ;halt the CPU
section .bss
resb 8192 ;8KB for stack
stack_space:
I'm using QEMU to run the kernel:
qemu-system-i386 -kernel kernel
The problem is that I'm not getting any character on the screen. Instead, I still get the same output:
SeaBIOS (version Ubuntu-1.8.2-1-ubuntu1)
Booting from ROM...
How do I solve this problem? Any suggestions?
You have a number of issues with your code. The main ones are discussed individually below.
The HLT instruction will halt the current CPU waiting for the next interrupt. You do have interrupts enabled by this point. After the first interrupt (keystroke) the code after HLT will be executed. It will start executing whatever random data is in memory. You could modify your kmain to do an infinite loop with a HLT instruction. Something like this should work:
while(1) __asm__("hlt\n\t");
In this code:
load_idt:
sti
mov edx, [esp + 4]
lidt [edx]
ret
It is generally a better idea to use STI after you update the interrupt table, not before it. This would be better:
load_idt:
mov edx, [esp + 4]
lidt [edx]
sti
ret
Your interrupt handler needs to perform an iretd to properly return from an interrupt. Your function keyboard_handler will do a ret to return. To resolve this you could create an assembly wrapper that calls the C keyboard_handler function and then does an IRETD.
In a NASM assembly file you could define a global function called keyboard_handler_int like this:
extern keyboard_handler
global keyboard_handler_int
keyboard_handler_int:
call keyboard_handler
iretd
The code to setup the IDT entry would look like this:
load_idt_entry(0x21, (unsigned long) keyboard_handler_int, 0x08, 0x8e);
Your kb_init function eventually enables (via a mask) the keyboard interrupt. Unfortunately, you set up the keyboard handler after you enable that interrupt. It is possible for a keystroke to be pressed after the interrupt is enabled and before the entry is placed in the IDT. A quick fix is to set your keyboard handler up before the call to kb_init with something like:
void kmain(void)
{
//Using grub bootloader..
idt_init();
load_idt_entry(0x21, (unsigned long) keyboard_handler_int, 0x08, 0x8e);
kb_init();
while(1) __asm__("hlt\n\t");
}
The most serious problem that is likely causing your kernel to triple fault (and effectively rebooting the virtual machine) is the way you defined the idt_pointer structure. You used:
struct idt_pointer
{
unsigned short limit;
unsigned int base;
};
The problem is that default alignment rules will place 2 bytes of padding after limit and before base so that the unsigned int will be aligned at a 4 byte offset within the structure. To alter this behavior and pack the data without padding, you can use __attribute__((packed)) on the structure. The definition would look like this:
struct idt_pointer
{
unsigned short limit;
unsigned int base;
} __attribute__((packed));
Doing it this way means that there are no extra bytes placed between limit and base for alignment purposes. Failure to deal with the alignment issue effectively yields a base address that is incorrectly placed in the structure. The IDT pointer needs a 16-bit value representing the size of the IDT followed immediately by a 32-bit value representing the base address of your IDT.
More information on structure alignment and padding can be found in one of Eric Raymond's blogs. Because of the way that members of struct idt_entry are placed there are no extra padding bytes. If you are creating structs that you never want padded I recommend using __attribute__((packed));. This is generally the case when you are mapping a C data structure with a system defined structure. With that in mind I'd also pack struct idt_entry for clarity.
Other considerations
In the interrupt handler, although I suggested an IRETD, there is another issue. As your kernel grows and you add more interrupts you'll discover another problem. Your kernel may act erratically and registers may change values unexpectedly. The issue is that C functions acting as interrupt handlers will destroy the contents of some registers, but we don't save and restore them. Secondly, the direction flag (per the 32-bit ABI) is required to be cleared (CLD) before a function is called. You can't assume the direction flag is cleared upon entry to the interrupt routine. The ABI says:
EFLAGS The flags register contains the system flags, such as the direction
flag and the carry flag. The direction flag must be set to the
‘‘forward’’ (that is, zero) direction before entry and upon exit
from a function. Other user flags have no specified role in the
standard calling sequence and are not preserved
You could push all the volatile registers individually but for brevity you can use the PUSHAD and POPAD instructions. An interrupt handler would be better if it looked like:
keyboard_handler_int:
pushad ; Push all general purpose registers
cld ; Clear direction flag (forward movement)
call keyboard_handler
popad ; Restore all general purpose registers
iretd ; IRET will restore required parts of EFLAGS
; including the direction flag
If you were to save and restore all the volatile registers manually you'd have to save and restore EAX, ECX, and EDX as they don't need to be preserved across C function calls. It generally isn't a good idea to to use x87 FPU instructions in an interrupt handler (mostly for performance), but if you did you'd have to save and restore the x87 FPU state as well.
Sample Code
You didn't provide a complete example, so I filled in some of the gaps (including a simple keyboard map) and slight change to your keyboard handler. The revised keyboard handler only displays key down events and skips over characters that had no mapping. In all cases the code drops through to the end of the handler so that the PIC is sent an EOI (End Of Interrupt). The current cursor location is a static integer that will retain its value across interrupt calls. This allows the position to advance between each character press.
My kprintd.h file is empty, and I put ALL the assembler prototypes into your port_io.h. The prototypes should be divided properly into multiple headers. I only did it this way to reduce the number of files. My file lowlevel.asm defines all the low level assembly routines. The final code is as follows:
kernel.asm:
bits 32
section .text
;grub bootloader header
align 4
dd 0x1BADB002 ;magic
dd 0x00 ;flags
dd - (0x1BADB002 + 0x00) ;checksum. m+f+c should be zero
global start
extern kmain
start:
lgdt [gdtr] ; Load our own GDT, the GDTR of Grub may be invalid
jmp CODE32_SEL:.setcs ; Set CS to our 32-bit flat code selector
.setcs:
mov ax, DATA32_SEL ; Setup the segment registers with our flat data selector
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
mov esp, stack_space ; set stack pointer
call kmain
; If we get here just enter an infinite loop
endloop:
hlt ; halt the CPU
jmp endloop
; Macro to build a GDT descriptor entry
%define MAKE_GDT_DESC(base, limit, access, flags) \
(((base & 0x00FFFFFF) << 16) | \
((base & 0xFF000000) << 32) | \
(limit & 0x0000FFFF) | \
((limit & 0x000F0000) << 32) | \
((access & 0xFF) << 40) | \
((flags & 0x0F) << 52))
section .data
align 4
gdt_start:
dq MAKE_GDT_DESC(0, 0, 0, 0); null descriptor
gdt32_code:
dq MAKE_GDT_DESC(0, 0x00ffffff, 10011010b, 1100b)
; 32-bit code, 4kb gran, limit 0xffffffff bytes, base=0
gdt32_data:
dq MAKE_GDT_DESC(0, 0x00ffffff, 10010010b, 1100b)
; 32-bit data, 4kb gran, limit 0xffffffff bytes, base=0
end_of_gdt:
gdtr:
dw end_of_gdt - gdt_start - 1
; limit (Size of GDT - 1)
dd gdt_start ; base of GDT
CODE32_SEL equ gdt32_code - gdt_start
DATA32_SEL equ gdt32_data - gdt_start
section .bss
resb 8192 ; 8KB for stack
stack_space:
lowlevel.asm:
section .text
extern keyboard_handler
global read_port
global write_port
global load_idt
global keyboard_handler_int
keyboard_handler_int:
pushad
cld
call keyboard_handler
popad
iretd
load_idt:
mov edx, [esp + 4]
lidt [edx]
sti
ret
; arg: int, port number.
read_port:
mov edx, [esp + 4]
in al, dx
ret
; arg: int, (dx)port number
; int, (al)value to write
write_port:
mov edx, [esp + 4]
mov al, [esp + 4 + 4]
out dx, al
ret
port_io.h:
extern unsigned char read_port (int port);
extern void write_port (int port, unsigned char val);
extern void kb_init(void);
kprintf.h:
/* Empty file */
keyboard_map.h:
unsigned char keyboard_map[128] =
{
0, 27, '1', '2', '3', '4', '5', '6', '7', '8', /* 9 */
'9', '0', '-', '=', '\b', /* Backspace */
'\t', /* Tab */
'q', 'w', 'e', 'r', /* 19 */
't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\n', /* Enter key */
0, /* 29 - Control */
'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', /* 39 */
'\'', '`', 0, /* Left shift */
'\\', 'z', 'x', 'c', 'v', 'b', 'n', /* 49 */
'm', ',', '.', '/', 0, /* Right shift */
'*',
0, /* Alt */
' ', /* Space bar */
0, /* Caps lock */
0, /* 59 - F1 key ... > */
0, 0, 0, 0, 0, 0, 0, 0,
0, /* < ... F10 */
0, /* 69 - Num lock*/
0, /* Scroll Lock */
0, /* Home key */
0, /* Up Arrow */
0, /* Page Up */
'-',
0, /* Left Arrow */
0,
0, /* Right Arrow */
'+',
0, /* 79 - End key*/
0, /* Down Arrow */
0, /* Page Down */
0, /* Insert Key */
0, /* Delete Key */
0, 0, 0,
0, /* F11 Key */
0, /* F12 Key */
0, /* All other keys are undefined */
};
keyb.c:
#include "kprintf.h"
#include "port_io.h"
#include "keyboard_map.h"
void kb_init(void)
{
/* This is a very basic keyboard initialization. The assumption is we have a
* PS/2 keyboard and it is already in a proper state. This may not be the case
* on real hardware. We simply enable the keyboard interupt */
/* Get current master PIC interrupt mask */
unsigned char curmask_master = read_port (0x21);
/* 0xFD is 11111101 - enables only IRQ1 (keyboard) on master pic
by clearing bit 1. bit is clear for enabled and bit is set for disabled */
write_port(0x21, curmask_master & 0xFD);
}
/* Maintain a global location for the current video memory to write to */
static int current_loc = 0;
/* Video memory starts at 0xb8000. Make it a constant pointer to
characters as this can improve compiler optimization since it
is a hint that the value of the pointer won't change */
static volatile char *const vidptr = (char*)0xb8000;
void keyboard_handler(void)
{
signed char keycode;
keycode = read_port(0x60);
/* Only print characters on keydown event that have
* a non-zero mapping */
if(keycode >= 0 && keyboard_map[keycode]) {
vidptr[current_loc++] = keyboard_map[keycode];
/* Attribute 0x07 is white on black characters */
vidptr[current_loc++] = 0x07;
}
/* Send End of Interrupt (EOI) to master PIC */
write_port(0x20, 0x20);
}
main.c:
#include "port_io.h"
#define IDT_SIZE 256
#define PIC_1_CTRL 0x20
#define PIC_2_CTRL 0xA0
#define PIC_1_DATA 0x21
#define PIC_2_DATA 0xA1
void keyboard_handler_int();
void load_idt(void*);
struct idt_entry
{
unsigned short int offset_lowerbits;
unsigned short int selector;
unsigned char zero;
unsigned char flags;
unsigned short int offset_higherbits;
} __attribute__((packed));
struct idt_pointer
{
unsigned short limit;
unsigned int base;
} __attribute__((packed));
struct idt_entry idt_table[IDT_SIZE];
struct idt_pointer idt_ptr;
void load_idt_entry(int isr_number, unsigned long base, short int selector, unsigned char flags)
{
idt_table[isr_number].offset_lowerbits = base & 0xFFFF;
idt_table[isr_number].offset_higherbits = (base >> 16) & 0xFFFF;
idt_table[isr_number].selector = selector;
idt_table[isr_number].flags = flags;
idt_table[isr_number].zero = 0;
}
static void initialize_idt_pointer()
{
idt_ptr.limit = (sizeof(struct idt_entry) * IDT_SIZE) - 1;
idt_ptr.base = (unsigned int)&idt_table;
}
static void initialize_pic()
{
/* ICW1 - begin initialization */
write_port(PIC_1_CTRL, 0x11);
write_port(PIC_2_CTRL, 0x11);
/* ICW2 - remap offset address of idt_table */
/*
* In x86 protected mode, we have to remap the PICs beyond 0x20 because
* Intel have designated the first 32 interrupts as "reserved" for cpu exceptions
*/
write_port(PIC_1_DATA, 0x20);
write_port(PIC_2_DATA, 0x28);
/* ICW3 - setup cascading */
write_port(PIC_1_DATA, 0x04);
write_port(PIC_2_DATA, 0x02);
/* ICW4 - environment info */
write_port(PIC_1_DATA, 0x01);
write_port(PIC_2_DATA, 0x01);
/* Initialization finished */
/* mask interrupts */
write_port(0x21 , 0xff);
write_port(0xA1 , 0xff);
}
void idt_init()
{
initialize_pic();
initialize_idt_pointer();
load_idt(&idt_ptr);
}
void kmain(void)
{
//Using grub bootloader..
idt_init();
load_idt_entry(0x21, (unsigned long) keyboard_handler_int, 0x08, 0x8e);
kb_init();
while(1) __asm__("hlt\n\t");
}
In order to link this kernel I use a file link.ld with this definition:
/*
* link.ld
*/
OUTPUT_FORMAT(elf32-i386)
ENTRY(start)
SECTIONS
{
. = 0x100000;
.text : { *(.text) }
.rodata : { *(.rodata) }
.data : { *(.data) }
.bss : { *(.bss) }
}
I compile and link this code using a GCC i686 cross compiler with these commands:
nasm -f elf32 -g -F dwarf kernel.asm -o kernel.o
nasm -f elf32 -g -F dwarf lowlevel.asm -o lowlevel.o
i686-elf-gcc -g -m32 -c main.c -o main.o -ffreestanding -O3 -Wall -Wextra -pedantic
i686-elf-gcc -g -m32 -c keyb.c -o keyb.o -ffreestanding -O3 -Wall -Wextra -pedantic
i686-elf-gcc -g -m32 -Wl,--build-id=none -T link.ld -o kernel.elf -ffreestanding -nostdlib lowlevel.o main.o keyb.o kernel.o -lgcc
The result is a kernel called kernel.elf with debug information. I prefer an optimization level of -O3 rather than a default of -O0. Debug information makes it easier to debug with QEMU and GDB. The kernel can be debugged with these commands:
qemu-system-i386 -kernel kernel.elf -S -s &
gdb kernel.elf \
-ex 'target remote localhost:1234' \
-ex 'layout src' \
-ex 'layout regs' \
-ex 'break kmain' \
-ex 'continue'
If you wish to debug at the assembly code level replace layout src with layout asm. When run with the input the quick brown fox jumps over the lazy dog 01234567890 QEMU displayed this:
I am developing a kernel from scratch in C. I am having a problem with the keyboard. In Qemu, when I press a key on my keyboard, the keypress is handled normally. When I run it in VirtualBox or on an actual computer, it works fine until I press a key. In virtualbox, when it crashes, it gives me the error code VINF_EM_TRIPLE_FAULT. Here is my (important parts) of the code:
Assembly:
bits 32
section .text
;multiboot spec
align 4
dd 0x1BADB002 ;magic
dd 0x00 ;flags
dd - (0x1BADB002 + 0x00)
global start
global keyboard_handler
global read_port
global write_port
global load_idt
extern main ;this is defined in the c file
extern keyboard_handler_main
read_port:
mov edx, [esp + 4]
;al is the lower 8 bits of eax
in al, dx ;dx is the lower 16 bits of edx
ret
write_port:
mov edx, [esp + 4]
mov al, [esp + 4 + 4]
out dx, al
ret
load_idt:
mov edx, [esp + 4]
lidt [edx]
sti ;turn on interrupts
ret
keyboard_handler:
call keyboard_handler_main
iretd
start:
cli ;block interrupts
mov esp, stack_space
call main
hlt ;halt the CPU
section .bss
resb 8192; 8KB for stack
stack_space:
C (The important parts, I omitted parts that don't involve the keyboard):
struct IDT_entry{
unsigned short int offset_lowerbits;
unsigned short int selector;
unsigned char zero;
unsigned char type_attr;
unsigned short int offset_higherbits;
};
struct IDT_entry IDT[IDT_SIZE];
void idt_init(void)
{
unsigned long keyboard_address;
unsigned long idt_address;
unsigned long idt_ptr[2];
/* populate IDT entry of keyboard's interrupt */
keyboard_address = (unsigned long)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
*/
/* ICW1 - begin initialization */
write_port(0x20 , 0x11);
write_port(0xA0 , 0x11);
/* ICW2 - remap offset address of IDT */
/*
* In x86 protected mode, we have to remap the PICs beyond 0x20 because
* Intel have designated the first 32 interrupts as "reserved" for cpu exceptions
*/
write_port(0x21 , 0x20);
write_port(0xA1 , 0x28);
/* ICW3 - setup cascading */
write_port(0x21 , 0x00);
write_port(0xA1 , 0x00);
/* ICW4 - environment info */
write_port(0x21 , 0x01);
write_port(0xA1 , 0x01);
/* Initialization finished */
/* mask interrupts */
write_port(0x21 , 0xff);
write_port(0xA1 , 0xff);
/* fill the IDT descriptor */
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);
}
void kb_init(void)
{
/* 0xFD is 11111101 - enables only IRQ1 (keyboard)*/
write_port(0x21 , 0xFD);
}
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;
}
char c = keyboard_map[(unsigned char) keycode]; //keyboard_map converts key codes to ASCII
(prints the char)
}
}
}
void main(void)
{
idt_init();
kb_init();
while(1);
print("Welcome to Code OS");
}
And then of course they are compiled with a linker.
So why is this working in qemu but not an actual computer? (On an actual computer it works fine until I press any key.)