Hi,
Random notes....
To figure out what CPL currently is, you'd need to look at the RPL field of CS:
Code: Select all
push cs
pop ax
and eax,3 ;<- eax = CPL
AFAIK this always works (regardless of what CPL the code is running at, or if conforming code segments are used).
Normally this isn't necessary because you can guess what CPL should be depending on how the code is used.
For a simple check (for the purpose of making sure your multitasking is working), you could try doing "CLI; STI". At CPL=0 this should work fine but you'll get a general protection fault at CPL=3 (assuming your IOPL is set correctly).
Interrupts can cause CPL transitions, but for IRQ handlers and exceptions the CPU ignores the IDT entry's DPL. In this case you'd need to use a conforming code segment (where the IRQ handler or exception handler runs at the same CPL that was interrupted), or use a normal CPL=0 IRQ or exception handler.
Conforming code segments aren't very useful because if CPL=3 code is interrupted then the IRQ or exception handler will also run at CPL=3 (everyone uses normal CPL=0 interrupt handlers).
Because of this you can't have device drivers at CPL=1 containing IRQ handlers that are also at CPL=1 (it would work until CPL=0 code is interrupted and you get a general protection exception). To get around this you'd need IRQ handlers in the kernel at CPL=0 which pass control to the CPL=1 device drivers.
For software interrupts it's a little different - the CPU doesn't allow control to be passed to less privileged code. This means you can have a CPL=0 software interrupt that can be used by any code, but you can't have a CPL=3 software interrupt that is used by CPL=0 code. The same applies to call gates.
It's also possible to have conforming software interrupts, where the interrupt handler runs at the same CPL as the code that is using the software interrupt. In this case CPL must be less than or equal to the interrupt handler's DPL. For e.g. if DPL=2 then code at CPL= 2 or CPL=3 could use the conforming software interrupt, but if code at CPL=1 or CPL=0 tried to use it then it'd cause a general protection fault.
For all privilege level transitions (regardless of the reason), if the CPU is changing to more privileged code then the CPU will get SS:ESP from the TSS (e.g. the SS0:ESP0 field) and the old less privileged SS:ESP will be pushed on the stack. If the CPU is changing to less privileged code, then the less privileged SS:ESP will be popped from stack and the previous more privileged values for SS:ESP will be discarded.
All privilege levels for the stack must match the CPL. For example, if CPL=2 then then descriptor for SS must use DPL=2. This means you can't use the same segment descriptor for all stacks.
Data segment descriptors are different - in general DPL must be numerically equal to or higher than CPL. For example, if the descriptor's DPL=3 then the segment can be used by all code, regardless of what CPL is, or if the descriptor's DPL=1 then code running at CPL=0 and CPL=1 can use it.
All of this was originally intended for a system that uses up to 4 privilege levels to protect different types of code from each other. Most OS's use paging instead (rather than using segmentation only or using segmentation and paging).
For a "flat" paging OS, you'd only need 1 data segment descriptor (DPL=3) that's used by all code. This same data segment descriptor would also be used for CPL=3 SS. Then you'd need a descriptor for CPL=0 SS, a CPL=3 code segment and a CPL=0 code segment. All IRQ and exception handlers should use DPL=0 to prevent CPL=3 code trying to use them with a software interrupt (e.g. trying to pretend a page fault occured with "int 0x0E"). All software interrupts (if any) would have DPL=3 to allow CPL=3 code to use them. For software task switching, you'd need a single TSS and a TSS descriptor with DPL=0 (you do not need a TSS gate descriptor).
This is the "minimum requirements" to get a "flat" paging OS to work. You may or may not want other segments for other things (like using the protected mode interfaces for VBE or APM, or using FS or GS to point to a specific kernel data structure).
Cheers,
Brendan