PCI configuration space registers - write values

2k views Asked by At

I am developing a network driver (RTL8139) for a selfmade operating system and have problems in writing values to the PCI configuration space registers.

I want to change the value of the interrupt line (offset 0x3c) to get another IRQ number and enable bus master (set bit 2) of the command register (offset 0x04).
When I read back the values I see that my values are correctly written. But instead of using the new IRQ number (in my case 6) it uses the old value (11).
Also bus master for DMA is not working (my packets I would like to send have the correct size (set by IO-Port) but they have no content (all values just 0, transceive buffer is on physical memory and has non zero values). This always worked with my code as expected after I double checked the physical address. I need this to let the network controller access to my physical memory where my buffers for receive/transceive are. (Bus mastering needed for RTL8139)

Do I have to do something else to confirm my changes to the PCI-device?

As emulator I use qemu.

For reading/writing I wrote the following functions:

uint16_t pci_read_word(uint16_t bus, uint16_t slot, uint16_t func, uint16_t offset)
{
 uint64_t address;
 uint64_t lbus = (uint64_t)bus;
 uint64_t lslot = (uint64_t)slot;
 uint64_t lfunc = (uint64_t)func;
 uint16_t tmp = 0;
 address = (uint64_t)((lbus << 16) | (lslot << 11) |
                   (lfunc << 8) | (offset & 0xfc) | ((uint32_t)0x80000000));
 outportl (0xCF8, address);
 tmp = (uint16_t)((inportl (0xCFC) >> ((offset & 2) * 8)) & 0xffff);
 return (tmp);
}

uint16_t pci_write_word(uint16_t bus, uint16_t slot, uint16_t func, uint16_t offset, uint16_t data)
{
 uint64_t address;
 uint64_t lbus = (uint64_t)bus;
 uint64_t lslot = (uint64_t)slot;
 uint64_t lfunc = (uint64_t)func;
 uint32_t tmp = 0;
 address = (uint64_t)((lbus << 16) | (lslot << 11) |
                   (lfunc << 8) | (offset & 0xfc) | ((uint32_t)0x80000000));
 outportl (0xCF8, address);
 tmp = (inportl (0xCFC));
 tmp &= ~(0xFFFF << ((offset & 0x2)*8)); // reset the word at the offset
 tmp |= data << ((offset & 0x2)*8); // write the data at the offset
 outportl (0xCF8, address); // set address again just to be sure
 outportl(0xCFC,tmp); // write data
 return pci_read_word(bus,slot,func,offset); // read back data;
}

I hope somebody can help me.

1

There are 1 answers

2
Brendan On BEST ANSWER

The "interrupt line" field (at offset 0x03C in PCI configuration space) literally does nothing.

The Full Story

PCI cards could use up to 4 "PCI IRQs" at the PCI slot; and use them in order (so if you have ten PCI cards that all have one IRQ, then they'll all use the first PCI IRQ at the slot).

There's a tricky "barber pole" arrangement to connect "PCI IRQ at the slot" to "PCI IRQ at the host controller" that is designed to reduce IRQ sharing. If you have ten PCI cards that all have one IRQ, then they'll all use the first PCI IRQ at the slot, but the first IRQ at each PCI slot will be connected to a different PCI IRQ at the host controller. All of this is hard-wired and can not be changed by software.

To complicate things more; to connect PCI IRQs (from the PCI host controller) to the legacy PIC chips, a special "PCI IRQ router" was added. In theory the configuration of the "PCI IRQ router" can be changed by software (if you can find the documentation that describes a table that describes the location, capabilities and restrictions of the "PCI IRQ router").

Without the firmware's help it'd be impossible for an OS to figure out which PIC chip input a PCI device actually uses. For that reason, the firmware figures it out during boot and then stores "PIC chip input number" somewhere for the OS to find. That is what the "interrupt line" register is - it's just an 8-bit register that can store anything you like (that the BIOS/firmware uses to store "PIC chip input number").