It's been a very long time since I devloped operating system. This means that we need to re-organize our "grand" plan...
Aside from developing task management system, I also want to focus on developing device drivers(because they're kinda cool.)
So... for today, I tried implementing the PCI driver that simply detects all the PCI devices in the system.
There're distinct two parts of PCI driver. One is about reading configuration space, and other is about probing the device. Probing the device will be the core kernel's feature, and fetching the configuration space will be arch-dependent part. So, I created both pci.cpp and pci.hpp to x86_64 arch folder, and also kernel folder.
This is the arch-dependent PCI driver part :
#include <kernel/driver/pci.hpp>
#include <x86_64/pci.hpp>
#include <kernel/io_port.hpp>
dword pci::read_config_space_dword(byte bus , byte device , byte function , byte offset) {
/* To-do : Support more than one configuration space access methods */
dword config_addr = (offset & 0b11111100)
|(((dword)function & 0b111) << 8)
|(((dword)device & 0x1F) << 11)
|(((dword)bus & 0xFF) << 16)
|(1 << 31);
io_write_dword(x86_64_PCI_CONFIG_ADDR , config_addr);
return io_read_dword(x86_64_PCI_CONFIG_DATA);
}
For Intel architecture, there's several mechanisms to accessing the configuration space, but I chose the easiest method.
The configuration space is a 256-byte memory space that has all the information needed to drive the device. The structure of the configuration space is described in the PCI specification in detail. Basically, there's a "common header field" describing the vendor id, device id, type of the header, and miscellaneous informations. The structure of the configuration space changes by the provided type of the header. This article from osdev wiki explains the overall PCI system quite well. I wrote my kernel code based on it, and you also might want to check it out!
That function(read_config_space_dword) reads the bytes from the configuration space of specific device given by bus, function, and device number. So, the way I'm currently probing the devices in the systems is to search all the possible devices. Since bus, device, and function can range from only 0~256, there's theoretically 16,777,216 devices there, and we can just brute-force out all the devices, check them if they're available and skip those that are invalid devices. Yes, there's more efficient way to search the devices, but I'm sticking with this simple way, for now.
static void probe_device(byte bus , byte device) {
struct pci::common_header_field header;
get_common_header_field(header , bus , device , 0);
if(header.vendor_id == 0xffff) return; // the device does not exist
probe_function(bus , device , 0);
if((header.header_type & 0x80) == 0x80) { // multi-function device
for(byte function = 1; function < 8; function++) {
probe_function(bus , device , function);
}
}
}
static void probe_bus(byte bus) {
for(byte device = 0; device < 32; device++) {
probe_device(bus , device);
}
}
void pci::probe_all_pci_devices(void) {
// brute force way of probing all the pci devices
for(int bus = 0; bus < 256; bus++) {
probe_bus(bus);
}
}
Now, theoretically if we make a kernel interface that allows device drivers to search the PCI devices, we can make a driver that can use the PCI interface to drive the devices! That'll be the future goal for me.