Inline Assembly/Examples
From OSDev Wiki
What follows is a collection of Inline Assembly functions so common that they should be useful to most OS developers.
Contents |
Memory access
FAR_PEEKx
read a byte (or word or dword) on a given memory location using another segment than the default C data segment. There's unfortunately no constraint for manipulating directly segment registers, so issuing the 'mov <reg>,<segmentreg>' manually is required.
static __inline__ dword farpeekl(word sel,void *off) { dword ret; asm("push %%fs;mov %1,%%fs;" "mov %%fs:(%2),%0;" "pop %%fs":"=r"(ret):"g"(sel),"r"(off)); return ret; }
FAR_POKEx
write a byte (or word or dword) 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.
static __inline__ void farpokeb(word sel, void *off, byte v) { asm("push %%fs; mov %0,%%fs;" "movb %2,%%fs:(%1);" "pop %%fs": :"g"(sel),"r"(off),"r"(v)); }
I/O access
OUTx
sends a byte (or word or dword) on a I/O location. Traditionnal names are outb, outw and outl respectively. the "a" modifier enforce value to be placed in eax register before the asm command is issued and "Nd" allows for one-byte constant values to be assembled as constants and use edx register for other cases.
static __inline__ void outb(unsigned short port, unsigned char val) { asm volatile("outb %0,%1"::"a"(val), "Nd" (port)); }
INx
receives a byte (or word or dword) from an I/O location. Traditionnal names are inb, inw and inl respectively
static __inline__ unsigned char inb(unsigned short port) { unsigned char ret; asm volatile ("inb %1,%0":"=a"(ret):"Nd"(port)); return ret; }
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.
static __inline__ void io_wait(void) { asm volatile("jmp 1f;1:jmp 1f;1:"); }
alternatively, you may use another I/O cycle on an 'unused' port (which has the nice property of being CPU-speed independent):
static __inline__ void io_wait(void) { asm volatile("outb %%al, $0x80" : : "a"(0)); // port 0x80 is used for 'checkpoints' during POST. // linux kernel seems to think it's free for use :-/ }
Interrupt-related functions
Enabled?
returns a 'true' boolean value if irq are enabled at the CPU
static __inline__ int irqEnabled() { int f; asm volatile ("pushf;popl %0":"=g" (f)); return f & (1<<9); }
acknowledge
sends the PIC chip (8259a) that we're ready for more interrupts. as pics are cascaded for int 8-15, if no>=8, we will have to send a 'clearance code' for both master and slave PICs.
static __inline__ void irqUnlock(int no) { /* Val, Port */ if (no>7) outb(0x20,0xa0); outb(0x20,0x20); }
LIDT
define a new interrupt table
void lidt(void *base, unsigned int size) { unsigned int i[2]; i[0] = size << 16; i[1] = (unsigned int) base; asm ("lidt (%0)": :"p" (((char *) i)+2)); }
the 'char* +2' trick avoids you to wonder whether the structure packing/padding will work or not for that weird 6-bytes IDTR stuff.
Alternatively, you can take the more straightforward approach
void lidt(void *base, unsigned short size) { struct { unsigned short length; unsigned long base; } __attribute__((__packed__)) IDTR; IDTR.length = size; IDTR.base = (unsigned long) base; asm ("lidt (%0)": :"p" (&IDTR)); }
Cpu-related functions
CPUID
request for CPU identification. See CPUID for more information.
/** issue a single request to CPUID. Fits 'intel features', for instance */ static inline void cpuid(int code, dword *a, dword *d) { asm volatile("cpuid":"=a"(*a),"=d"(*d):"0"(code)); }
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 it increments after each clock cycle.
static inline void rdtsc(dword *upper, dword *lower) { asm volatile("rdtsc\n" : "=a"(*lower), "=d"(*upper)); }
this can be used to find out how much time it takes to do certain functions. It's very useful for testing/benchmarking/etc. Note: This is only an approximation.
READ_CRx
read the value in a control register
static inline unsigned read_cr0() { unsigned val; asm volatile("mov %%cr0, %0":"=r"(val)); return val; }
PGFLUSHTLB
invalidates the TLB (Translation Lookaside Buffer) for one specific virtual address (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). m points to a logical address, not a physical or virtual one: an offset for your ds segment. Note *m is used, not just m: if you use m here, you invalidate the address of the m variable (not what you want!).
static __inline__ void pgFlushOneTlb(void *m) { asm volatile("invlpg %0"::"m" (*m)); }
