Inline Assembly/Examples: Difference between revisions
Revision as of 18:51, 5 August 2014
What follows is a collection of Inline Assembly functions so common that they should be useful to most OS developers using GCC. Other compilers may have intrinsic alternatives (see references). Notice how these functions are implemented using GNU extensions to the C language and that particular keywords may cause you trouble if you disable GNU extensions. You can still use the disabled keywords such as asm if you instead use the alternate keywords in the reserved namespace such as __asm__. Be wary of getting inline assembly just right: The compiler doesn't understand the assembly it emits and can potentially cause rare nasty bugs if you lie to the compiler.
Memory access
FAR_PEEKx
Read a 8/16/32-bit value at a given memory location using another segment than the default C data segment. Unfortunately there is no constraint for manipulating segment registers directly, so issuing the mov <reg>, <segmentreg> manually is required.
staticinlineuint32_tfarpeekl(uint16_tsel,void*off) { uint32_tret; asm("push %%fs\n\t" "mov %1, %%fs\n\t" "mov %%fs:(%2), %0\n\t" "pop %%fs" :"=r"(ret):"g"(sel),"r"(off)); returnret; }
FAR_POKEx
Write a 8/16/32-bit value to a segment:offset address too. Note that much like in farpeek, this version of farpoke saves and restore the segment register used for the access.
staticinlinevoidfarpokeb(uint16_tsel,void*off,uint8_tv) { asm("push %%fs\n\t" "mov %0, %%fs\n\t" "movb %2, %%fs:(%1)\n\t" "pop %%fs" ::"g"(sel),"r"(off),"r"(v)); /* TODO: Should "memory" be in the clobber list here? */ }
I/O access
OUTx
Sends a 8/16/32-bit value on a I/O location. Traditional names are outb, outw and outl respectively. The a modifier enforces val to be placed in the eax register before the asm command is issued and Nd allows for one-byte constant values to be assembled as constants, freeing the edx register for other cases.
staticinlinevoidoutb(uint16_tport,uint8_tval) { asmvolatile("outb %0, %1"::"a"(val),"Nd"(port)); /* TODO: Is it wrong to use 'N' for the port? It's not a 8-bit constant. */ /* TODO: Should %1 be %w1? */ /* TODO: Is there any reason to force the use of eax and edx? */ }
INx
Receives a 8/16/32-bit value from an I/O location. Traditional names are inb, inw and inl respectively.
staticinlineuint8_tinb(uint16_tport) { uint8_tret; asmvolatile("inb %1, %0":"=a"(ret):"Nd"(port)); /* TODO: Is it wrong to use 'N' for the port? It's not a 8-bit constant. */ /* TODO: Should %1 be %w1? */ /* TODO: Is there any reason to force the use of eax and edx? */ returnret; }
IO_WAIT
Forces the CPU to wait for an I/O operation to complete. only use this when there's nothing like a status register or an IRQ to tell you the info has been received.
staticinlinevoidio_wait(void) { /* TODO: This is probably fragile. */ asmvolatile("jmp 1f\n\t" "1:jmp 2f\n\t" "2:"); }
Alternatively, you may use another I/O cycle on an 'unused' port (which has the nice property of being CPU-speed independent):
staticinlinevoidio_wait(void) { /* Port 0x80 is used for 'checkpoints' during POST. */ /* The Linux kernel seems to think it is free for use :-/ */ asmvolatile("outb %%al, 0ドルx80"::"a"(0)); /* TODO: Is there any reason why al is forced? */ }
Enabled?
Returns a true boolean value if irq are enabled for the CPU.
staticinlineboolare_interrupts_enabled() { unsignedlongflags; asmvolatile("pushf\n\t" "pop %0" :"=g"(f)); returnflags&(1<<9); }
LIDT
Define a new interrupt table.
staticinlinevoidlidt(void*base,uint16_tsize) { struct { uint16_tlength; uint32_tbase; }__attribute__((packed))IDTR; IDTR.length=size; IDTR.base=(uint32_t)base; asm("lidt (%0)"::"p"(&IDTR)); }
CPUID
Request for CPU identification. See CPUID for more information.
/* GCC has a <cpuid.h> header you should use instead of this. */ staticinlinevoidcpuid(intcode,uint32_t*a,uint32_t*d) { asmvolatile("cpuid":"=a"(*a),"=d"(*d):"0"(code):"ebx","ecx"); }
RDTSC
Read the current value of the CPU's time-stamp counter and store into EDX:EAX. The time-stamp counter contains the amount of clock ticks that have elapsed since the last CPU reset. The value is stored in a 64-bit MSR and is incremented after each clock cycle.
staticinlineuint64_trdtsc() { uint64_tret; asmvolatile("rdtsc":"=A"(ret)); returnret; }
This can be used to find out how much time it takes to do certain functions, very useful for testing/benchmarking /etc. Note: This is only an approximation.
READ_CRx
Read the value in a control register.
staticinlineunsignedlongread_cr0(void) { unsignedlongval; asmvolatile("mov %%cr0, %0":"=r"(val)); returnval; }
INVLPG
Invalidates the TLB (Translation Lookaside Buffer) for one specific virtual address. The next memory reference for the page will be forced to re-read PDE and PTE from main memory. Must be issued every time you update one of those tables. The m pointer points to a logical address, not a physical or virtual one: an offset for your ds segment.
staticinlinevoidinvlpg(void*m) { /* Clobber memory to avoid optimizer re-ordering access before invlpg, which may cause nasty bugs. */ asmvolatile("invlpg (%0)"::"m"(m):"memory"); }
WRMSR
Write a 64-bit value to a MSR. The A constraint stands for concatenation of registers EAX and EDX.
inlinevoidwrmsr(uint32_tmsr_id,uint64_tmsr_value) { asmvolatile("wrmsr"::"c"(msr_id),"A"(msr_value)); }
RDMSR
Read a 64-bit value from a MSR. The A constraint stands for concatenation of registers EAX and EDX.
inlineuint64_trdmsr(uint32_tmsr_id) { uint64_tmsr_value; asmvolatile("rdmsr":"=A"(msr_value):"c"(msr_id)); returnmsr_value; }