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));
}
