This code has potential bugs especially if you start building with optimizations on:
Code:
void gdt_set(){
__asm__ __volatile__("lgdtl (gdt_limit_ptr)");
__asm__ __volatile__(
"cli\n"
"movw $0x10, %ax \n"
"movw %ax, %ds \n"
"movw %ax, %es \n"
"movw %ax, %fs \n"
"movw %ax, %gs \n"
"movw %ax, %ss \n"
"ljmp $0x08, $next \n"
"next: \n"
);
}
Because you have added an extra empty selector Your Code Segment should be 0x10 (instead of 0x08) and data segment should be 0x18 (instead of 0x10). The defines should be changed to:
Code:
#define KERNEL_CS 0x10
#define KERNEL_DS 0x18
Multiple basic ASM statements (even if volatile) that appear one after another are not guaranteed to be output in the same order. It is frowned upon and bad practice to access a global variable directly with inline. It should be passed as a parameter using extended inline assembly. You modify the register EAX without telling the compiler it has been modified. This may result in the compiler assuming the value in EAX is the same after the inline assembly as it was before. You could rewrite this by combining the two and pass
gdt_limit_ptr as a constraint. You could do something like:
Code:
void gdt_set(){
__asm__ __volatile__(
"cli\n\t"
"lgdtl %[gdtr]\n\t"
"movw $0x18, %%ax\n\t"
"movw %%ax, %%ds\n\t"
"movw %%ax, %%es\n\t"
"movw %%ax, %%fs\n\t"
"movw %%ax, %%gs\n\t"
"movw %%ax, %%ss\n\t"
"ljmp $0x10, $next%=\n"
"next%=:"
: /* No output constraints */
: [gdtr]"m"(gdt_limit_ptr)
: "eax" /* clobber list */
);
}
You could clean this up and use constraints to have the compiler choose a register and pass the values of the segments in doing something like:
Code:
void gdt_set(){
__asm__ __volatile__(
"cli\n\t"
"lgdtl %[gdtr]\n\t"
"mov %[dataseg], %%ds\n\t"
"mov %[dataseg], %%es\n\t"
"mov %[dataseg], %%fs\n\t"
"mov %[dataseg], %%gs\n\t"
"mov %[dataseg], %%ss\n\t"
"ljmp %[codeseg], $next%=\n"
"next%=:"
: /* no output constraints */
: [dataseg]"r"(KERNEL_DS), /* Pass DS via a general purpose register */
[codeseg]"i"(KERNEL_CS), /* Pass CS via an immediate value so it can be used with ljmp */
[gdtr]"m"(gdt_limit_ptr) /* Pass a memory reference to the gdt record */
);
}
If you are new to inline assembly I recommend not using it and writing assembly code in an external assembly module. It is very easy to introduce subtle bugs with inline assembly if you don't do it correctly. Those bugs can be harder to nail down later.
PS: The
%= I put on the end of
next%= adds a unique number to the end of the label specific to the instance of the inline assembly generated. This avoids name collision with other labels you may wish to call
next in other inline assembly.
A more generic function that allows for the parameters to be passed to gdt_set:
Code:
void gdt_set(gdt_limit_ptr_t *gdtr, uint16 codeseg, uint16 dataseg){
__asm__ __volatile__(
"cli\n\t"
"lgdtl %[gdtr]\n\t"
"mov %[dataseg], %%ds\n\t"
"mov %[dataseg], %%es\n\t"
"mov %[dataseg], %%fs\n\t"
"mov %[dataseg], %%gs\n\t"
"mov %[dataseg], %%ss\n\t"
"push %k[codeseg]\n\t"
"push $next%=\n\t"
"ljmp *(%%esp)\n"
"next%=:\n\t"
"add $8, %%esp"
: /* no output constraints */
: [dataseg]"r"(dataseg),
[codeseg]"ri"(codeseg),
[gdtr]"m"(*gdtr)
);
}