Inline Assembly/Examples: Difference between revisions

From OSDev.wiki
Jump to navigation Jump to search
[unchecked revision][unchecked revision]
Content added Content deleted
No edit summary
Line 202: Line 202:
{
{
asm volatile( "invlpg %0"
asm volatile( "invlpg %0"
: : "m"(*m) );
: // no output
: "m"(*m) //
: "memory" // clobber memory to avoid optimizer re-ordering access before invlpg, which may cause nasty bug.
);
}
}
</source>
</source>

Revision as of 17:38, 29 April 2012

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).

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. Unfortunately there is no constraint for manipulating segment registers directly, so issuing the 'mov <reg>, <segmentreg>' manually is required.

static inline
dword farpeekl( word sel, void * off )
{
    dword ret;
    asm( "push %%fs\n\t"
         "mov  %1, %%fs\n\t"
         "mov  %%fs:(%2), %0\n\t"
         "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\n\t"
         "mov  %0, %%fs\n\t"
         "movb %2, %%fs:(%1)\n\t"
         "pop %%fs"
         : : "g"(sel), "r"(off), "r"(v) );
}

I/O access

OUTx

Sends a byte (or word or dword) 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.

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. Traditional 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\n\t"
                  "1:jmp 1f\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):

static inline
void io_wait( void )
{
    // port 0x80 is used for 'checkpoints' during POST.
    // The Linux kernel seems to think it is free for use :-/
    asm volatile( "outb %%al, $0x80"
                  : : "a"(0) );
}

Interrupt-related functions

Enabled?

Returns a 'true' boolean value if irq are enabled for the CPU.

static inline
int irqEnabled()
{
    int f;
    asm volatile ( "pushf\n\t"
                   "popl %0"
                   : "=g"(f) );
    return f & ( 1 << 9 );
}

LIDT

Define a new interrupt table.

static inline
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:

static inline
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.

static inline
void cpuid( int code, dword * a, dword * d )
{
    asm volatile( "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.

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, 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( void )
{
    unsigned val;
    asm volatile( "mov %%cr0, %0"
                  : "=r"(val) );
    return val;
}

PGFLUSHTLB

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. 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"
                  :          // no output
                  : "m"(*m)  // 
                  : "memory" // clobber memory to avoid optimizer re-ordering access before invlpg, which may cause nasty bug.
     );
}

WRMSR

Write a 64 bits value to a MSR. The 'A' constraint stands for concatenation of registers EAX and EDX.

__inline__ void		wrmsr(uint32_t	msr_id,
			      uint64_t	msr_value)
{
  __asm__ __volatile__ ("	wrmsr"
			:
			: "c" (msr_id), "A" (msr_value)
			);
}

RDMSR

Read a 64 bits value from a MSR. The 'A' constraint stands for concatenation of registers EAX and EDX.

__inline__ uint64_t	rdmsr(uint32_t	msr_id)
{
  uint64_t		msr_value;

  __asm__ __volatile__ ("	rdmsr"
			: "=A" (msr_value)
			: "c" (msr_id)
			);

  return msr_value;
}

References

Visual C++ Compiler Intrinsics