Hi,
onlyonemac wrote:If you use CPL3 for drivers, how do they perform the necessary hardware IO? Or does the kernel provide routines to do this for them?
Bus mastering and DMA is where the big problem is. For ISA devices I provide a "setup DMA transfer" syscall in the kernel (which includes checking if the process should/shouldn't be able to use the DMA channel and the RAM area being transferred), so that isn't a problem. For PCI devices; if you have an IOMMU then it can be used to ensure a driver can't use the device to access something it shouldn't. Otherwise (for PCI devices when there's no IOMMU) there's no good solution - I just rely on "network effects" (if nobody that has an IOMMU has reported security problems with the driver, then assume the driver is safe on systems that don't have an IOMMU).
For IRQs there's no problem - typically the kernel has IRQ handlers which send "IRQ occurred" messages to device drivers, plus a syscall that drivers use to say "I've handled that IRQ" (so kernel can do EOI, etc). This has the benefit that device drivers don't need to know or care what the interrupt controller is (e.g. PIC, IO APIC) or if it's being shared by other devices, or which IRQ number it is; which also means that kernel can reconfigure IRQs without telling the driver. For e.g. if the device is using "interrupt 0x33" and that interrupt occurs the kernel can send a "Your device's IRQ #1 occured" message to driver, and then if the kernel reconfigures IRQs/interrupts so that the device ends up using "interrupt 0x44" instead and that interrupt occurs kernel can still send a "Your device's IRQ #1 occured" message to driver.
For memory mapped IO areas, you can map the areas that the driver should be able to access (as determined by PCI configuration space BARs, etc) into its virtual address space. Fortunately, for PCI the minimum size of a memory mapped IO area is 4 KiB (page size) and they have to be "power of 2", so there's never 2 or more memory mapped IO areas in the same physical page.
For IO ports there's 3 options:
- Use IO permission bitmap in TSS
- Emulate instructions that use IO ports in the general protection fault handler
- Provide syscalls for IO port accesses (so a driver asks kernel to access the IO port)
Setting IOPL to 3 doesn't quite work, as you'd be giving a driver access to all IO ports and it'd be able to interfere with things it shouldn't be able to (e.g. disable A20 gate and crash the OS, etc).
The IO permission bitmap is probably fastest; but means you need to juggle IO permission bitmaps during task switching. Both the emulation and syscall methods are slower, but allow you to give "insanely fine grained" permissions (e.g. let a driver access some bits of an IO port but not others, read from an IO port but not write, etc); and allows you to re-arrange IO ports without telling the driver (e.g. the driver always thinks it's using "IO port 0x0000" regardless of which IO port the device actually uses when).
Note that because kernel can control access, kernel could provide some powerful advanced features. For example, kernel could generate a log of everything (e.g. when the device generates an IRQ, when a device's IO port is accessed and what data was involved, etc) to make it easier for device driver developers to debug their device drivers (without resorting to normal debugging tools like single-stepping, which tend to ruin timing). For a more advanced example, kernel could support "virtual devices" - a device driver writes to an IO port and kernel forwards it to "virtual device" to handle (and device driver doesn't know it's not driving a real device).
Cheers,
Brendan