There are several ThingsYouCannotDoWithC - CPUID, handling control registers etc. You can either write these things in Assembler and link them to your C code, or you can use InlineAssembly.
What follows is a collection of InlineAssembly functions so common that they should be useful to most OS developers.
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));
}
Is the above even run-tested? I'm pretty sure the structure will be allocated on the stack, and therefore ruin the whole thing. -mkr
Iirc, it was initially posted by someone who actually used it. Afaik, having the structure for LIDT stored on the stack is not an issue since its own purpose is to set up the IDTR register on the CPU. Once the register is loaded, what matters is that the IDT itself is valid and not stored on a stack (for instance), but you could erase the IDTR structure without a tear. We indeed better prefer a check than a reasoning... -- PypeClicker
It does work, the CPU couldn't give a damn about this struct once it's been loaded into IDTR, only the IDT itself is important at that point (Just like the GDTR struct, you don't bother keeping that either [unless you've got it statically built in, but anyway...]) -- AR
It makes this error undefined reference to `$i', when i is static, and when it's not invalid asm
Cpu-related functions
- CPUID
- request for CPU identification. See the CPUID page for more code snippets.
/** 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));
}
Category: HardWareCpu
