PCI BIOS call

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
CyberP1708

PCI BIOS call

Post by CyberP1708 »

Is it absolutly required to call the PCI BIOS (the 'BSD') in order to detect PCI devices using port 0xCF8 ?

The problem is that I don't detect any PCI device if I don't call the BSD (and the call is still buggy because the cpu tells me "wrong opcode" when doing it)
User avatar
Brendan
Member
Member
Posts: 8561
Joined: Sat Jan 15, 2005 12:00 am
Location: At his keyboard!
Contact:

Re:PCI BIOS call

Post by Brendan »

Hi,
CyberP1708 wrote:Is it absolutly required to call the PCI BIOS (the 'BSD') in order to detect PCI devices using port 0xCF8 ?
No - you can manually detect which mechanism is supported and use it without ever using the PCI BIOS. IIRC manual detection won't tell you if the PCI bus supports "special cycles" though, but most OS's don't use special cycles anyway (and half the chipsets don't support it either).
CyberP1708 wrote:The problem is that I don't detect any PCI device if I don't call the BSD (and the call is still buggy because the cpu tells me "wrong opcode" when doing it)
Your code to call the PCI BIOS is buggy and your code for manual detection is buggy?


Cheers,

Brendan
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.
CyberP1708

Re:PCI BIOS call

Post by CyberP1708 »

Your code to call the PCI BIOS is buggy and your code for manual detection is buggy?
Well I think so ;D

Here is my code because I think I am going to need some help:

Code: Select all

#define pci_vendor_id         0x0
#define pci_device_id         0x2
#define pci_class            0x9
#define pci_header_type      0xE

unsigned int pci_list_count;
void pci_detect() {
   unsigned short bus, device;
   unsigned int functions_count;
   u8 header_type;
   u16 vendor_id;

   pci_list_count = 0;
   for (bus = 0; bus < 0x10; ++bus) {
      for (device = 0; device < 0x40; ++device) {
         header_type = pci_read(bus, device, 0, pci_header_type);
         vendor_id =pci_read(bus, device, 0, pci_vendor_id);
         if (!vendor_id || vendor_id == 0xFFFF) continue;
         functions_count = (header_type & 0x80) ? 8 : 1;
         ++pci_list_count;
         // here is not finished but pci_list_count remains null at the end of the function
      }
   }
}

unsigned int pci_read(unsigned short bus, unsigned short device, unsigned short function, unsigned short reg) {
   u32 toret;
   u32 msg = 0x80000000 | ((bus & 0xFF) << 16) | ((device & 0x1F) << 11) | ((function & 0x07) << 8) | (reg & 0xFC);
   u32 org = ind(0xCF8);
   outd(0xCF8, msg);
   toret = ind(0xCFC);
   outd(0xCF8, org);
   return toret;
}
Of course I call the function pci_detect (I'm sure about that)

edit: it is the code to detect the devices
I am going to get rid of the pci bios
CyberP1708

Re:PCI BIOS call

Post by CyberP1708 »

Well I had the intelligence to test it on real hardware and it is working (18 devices detected)
So my last question is : why is it not working with bochs ?
In the configuration file I put this line :

Code: Select all

ne2k: ioaddr=0x240, irq=9, mac=b0:c4:20:00:00:00, ethmod=linux, ethdev=eth0
So it should at least detect the ne2k
I'm not able to test it with VMWare because it freezes while GRUB is loading
bluecode

Re:PCI BIOS call

Post by bluecode »

hi,

you have to activate PCI in bochs at compile time, that means you have to ./configure it with --enable-pci. Quoting from bochs documentation: "Enable limited i440FX PCI support. This is still incomplete, but usable."
CyberP1708

Re:PCI BIOS call

Post by CyberP1708 »

Ok thank you very much
I'll try this
User avatar
Brendan
Member
Member
Posts: 8561
Joined: Sat Jan 15, 2005 12:00 am
Location: At his keyboard!
Contact:

Re:PCI BIOS call

Post by Brendan »

Hi,

The first problem is that "pci_read()" will always return a 32 bit value that is always aligned to the nearest 32 bits. When you try to read the "pci_device_id" you will actually get a 32 bit value where the Device ID is in bits 16 to 31.

The PCI BIOS solves this by providing functions to return a dword, word or byte (and doing the necessary bit shifting to make it work). You could do the same, but to be honest this is lame - the first thing you're doing is getting the Device ID and the Vendor ID which are in the same dword. Rather than reading the same dword from configuration space twice, it's better to read it once and do the bit shifting stuff afterwards.

For example:

Code: Select all

   temp = pci_read(bus, device, 0, 0);
   vendor_id = temp & 0xFFFF;
   device_id = (temp >> 16) & 0xFFFF;
Secondly, I'm not sure why your "pci_read()" function saves and restores the original value (I can't see why you'd need to). If you're worried about re-entrancy then use a re-entrancy lock (or for single CPU only, just disable interrupts).

None of this explains why "if (!vendor_id || vendor_id == 0xFFFF) continue;" doesn't work correctly, as vendor_id should still be correct (as it's truncated to 16 bit). Also, theoretically "vendor ID = 0x0000" is possible (but hasn't been assigned to a vendor yet). In any case you should be able to simplify this test.

This leaves something like:

Code: Select all

unsigned int pci_list_count;

void pci_detect() {
   unsigned short bus, device;
   unsigned int functions_count;
   u32 temp;
   u8 header_type;
   u16 vendor_id;

   pci_list_count = 0;
   for (bus = 0; bus < 0x10; ++bus) {
      for (device = 0; device < 0x40; ++device) {
         temp = pci_read_dword(bus, device, 0, 0);
         vendor_id = temp & 0xFFFF;
         device_id = (temp >> 16) & 0xFFFF;
         vendor_id =pci_read_dword(bus, device, 0, pci_vendor_id);
         if (vendor_id != 0xFFFF) {
            ++pci_list_count;
            // here is not finished...
         }
      }
   }
}

unsigned int pci_read_dword(unsigned short bus, unsigned short device, unsigned short function, unsigned short reg) {
   U32 result;

   u32 msg = 0x80000000 | ((bus & 0xFF) << 16) | ((device & 0x1F) << 11) | ((function & 0x07) << 8) | (reg & 0xFC);

   // Re-entrancy lock or "CLI" needed here

   outd(0xCF8, msg);
   result = ind(0xCFC);

   // Unlock re-entrancy lock or "STI" needed here

   return result;
}

This is a start. The next step is to see if works (fingers crossed), and then implement more.

[continued]
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.
User avatar
Brendan
Member
Member
Posts: 8561
Joined: Sat Jan 15, 2005 12:00 am
Location: At his keyboard!
Contact:

Re:PCI BIOS call

Post by Brendan »

[continued]

Rather than scanning every possible bus number, you can detect if a "PCI to PCI Bridge" has been found and get the new bus number (for the bus on the other side of the bridge) from the PCI bridge's header.

Also, the first device (bus=0, device=0) should always be the "PCI host controller". This is normally a single function device (one PCI host controller), but systems with more than one PCI host controller are becoming more common. In this case the first device can be a multi-function device. For example, if the first device has 2 functions then you've got a system with dual PCI host controllers.

Taking this into account, I'd go something like:

Code: Select all

#define PCI_TYPE_BRIDGE   0x01

unsigned int pci_list_count;

void pci_detect() {
   scan_PCI_bus(0);
}

void scan_PCI_bus(u8 bus);
   u8 device;
   u8 function;
   u16 vendor_id;
   u16 device_id;
   u8 header_type;
   u32 temp;

   for (device = 0; device < 0x40; device++) {
      temp = pci_read_dword(bus, device, 0, 0);
      vendor_id = temp & 0xFFFF;
      device_id = (temp >> 16) & 0xFFFF;

      // Check if this device is valid

      if(vendor_id != 0xFFFF) {
         temp = pci_read_dword(bus, device, 0, 0x0C);
         header_type = (temp >> 16) & 0x7F;
         register_PCI(bus, device, 0, device_ID, vendor_id);

         // Check if this device is a multi-function device

         if( (temp & 0x00800000) != 0) {
            for(function = 1; function < 0x08; function++) {
               temp = pci_read_dword(bus, device, function, 0);
               vendor_id = temp & 0xFFFF;
               device_id = (temp >> 16) & 0xFFFF;
               register_PCI(bus, device, function, device_ID, vendor_id);
            }
         }
      }
   }
}

void register_PCI(u8 bus, u8 device, u8 function, u16 device_ID, u16 vendor_id) {
   u8 header_type;
   u8 new_bus;
   u32 temp;

   printf("Found PCI device at %x:%x:%x, device ID: %x, vendor ID: %x\n",
      bus, device, function,
      device_id, vendor_id);

   pci_list_count++;

   // Remember this device exists(?)

   // Check if it's a PCI bridge

   if( (bus != 0) || (device != 0) || (function != 0) ) {
      temp = pci_read_dword(bus, device, 0, 0x0C);
      header_type = (temp >> 16) & 0x7F;
      if(header_type == PCI_TYPE_BRIDGE) {
         temp = pci_read_dword(bus, device, 0, 0x18);
         new_bus = (temp >> 8) & 0xFF;
         scan_PCI_bus(new_bus);                   // Recursion!!!
      }
   }
}
The code above is an example only. It might actually work, but it hasn't been tested (I made it up form memory)...


Cheers,

Brendan


[Edit: Just a silly typo..]
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.
User avatar
Pype.Clicker
Member
Member
Posts: 5964
Joined: Wed Oct 18, 2006 2:31 am
Location: In a galaxy, far, far away
Contact:

Re:PCI BIOS call

Post by Pype.Clicker »

CyberP1708 wrote: Well I had the intelligence to test it on real hardware and it is working (18 devices detected)
So my last question is : why is it not working with bochs ?
In the configuration file I put this line :

Code: Select all

ne2k: ioaddr=0x240, irq=9, mac=b0:c4:20:00:00:00, ethmod=linux, ethdev=eth0
So it should at least detect the ne2k
No. The implementation of NE2000 in bochs is the ISA card, not the PCI clones. QEMU has PCI ne2000 though.
CyberP1708

Re:PCI BIOS call

Post by CyberP1708 »

Thank you very much for this code. It answers many questions I was asking myself.
Secondly, I'm not sure why your "pci_read()" function saves and restores the original value (I can't see why you'd need to). If you're worried about re-entrancy then use a re-entrancy lock (or for single CPU only, just disable interrupts).
I did this because they do the same here: http://www.osdev.org/osfaq2/index.php/Where%20can%20I%20find%20programming%20info%20on%20PCI%3F
I was not knowing why it was not working so I tried everything I saw

With Bochs on Windows it detects 4 pci devices
I'll try to figure out why it doesn't work on Linux
Post Reply