Page 1 of 1

reading pci id is always 0x1111

Posted: Mon Apr 12, 2021 12:06 pm
by monarrk
Hello there,

I am attempting to enumerate the pci configuration space (on qemu, if that matters, I haven't had a chance to try on real hardware), but the ID always comes out as 0x1111, and the vendor always comes out as 0.

my pci code looks like this

Code: Select all

#define PCI_ADDR 0x0CF8
#define PCI_DATA 0x0CFC

// port is the byte offset from 0x00
unsigned char pci_read_byte(const int bus, const int dev, const int func, const int port) {
	const int shift = ((port & 3) * 8);
	const unsigned int val = 0x80000000 | (bus << 16) | (dev << 11) | (func << 8) | (port & 0xFC);
	outl(PCI_ADDR, val);
	return (inl(PCI_DATA) >> shift) & 0xFF;
}

// port is the byte offset from 0x00
unsigned short pci_read_word(const int bus, const int dev, const int func, const int port) {
	if ((port & 3) <= 2) {
		const int shift = ((port & 3) * 8);
		const unsigned int val = 0x80000000 | (bus << 16) | (dev << 11) | (func << 8) | (port & 0xFC);
		outl(PCI_ADDR, val);
		return (inl(PCI_DATA) >> shift) & 0xFFFF;
	} else
		return (pci_read_byte(bus, dev, func, port + 1) << 8) | pci_read_byte(bus, dev, func, port);
}

// port is the byte offset from 0x00
unsigned int pci_read_dword(const int bus, const int dev, const int func, const int port) {
	if ((port & 3) == 0) {
		const unsigned int val = 0x80000000 | (bus << 16) | (dev << 11) | (func << 8) | (port & 0xFC);
		outl(PCI_ADDR, val);
		return inl(PCI_DATA);
	} else
		return (pci_read_word(bus, dev, func, port + 2) << 16) | pci_read_word(bus, dev, func, port);
}
and I'm calling it like this (please excuse my debug code)

Code: Select all

/// max pci buses to enumerate
pub const PCI_MAX_BUS: i32 = 256;
/// max number of devices to enumerate in a bus
pub const PCI_MAX_DEV: i32 = 32;
/// max number of functions to enumerate in a device
pub const PCI_MAX_FUNC: i32 = 8;

pub unsafe fn probe() {
    let mut found = false;
    let mut nonzero = false;

    // enumerate pci buses
    for bus in 0..PCI_MAX_BUS {
        for dev in 0..PCI_MAX_DEV {
            for func in 0..PCI_MAX_FUNC {
                // read the first 16 bytes of the function
                // if it is not 0xFFFF, then it's a valid function
                let id = pci::pci_read_dword(bus, dev, func, 0x00);

                // vendor is bits 31:16
                let vendor = (id & 0xFFFF0000) << 15;
                dbgln!(
                    "[{}][{}][{}] -> id: {i:b} ({i:x}), vendor: {v:b} ({v:x})",
                    bus,
                    dev,
                    func,
                    i = id,
                    v = vendor
                );

                if vendor != 0xFFFF {
                    // 0x09 is CC - Class Code
                    // see 2.1.5 of the intel serial ata spec
                    let class_code = pci::pci_read_dword(bus, dev, func, 0x09);

                    // base class code, bits 23:16
                    let bcc = (class_code & 0x0000FF00) << 15;
                    // sub class code, bits 15:08
                    let scc = (class_code & 0x00FF0000) << 7;

                    dbgln!("[{}][{}][{}] -> class code: {:b}, base class code: {:b}, sub class code: {:b}", bus, dev, func, class_code, bcc, scc);

                    if bcc == 0x01 && scc == 0x06 {
                        found = true;
                    }

                    if bcc != 0 || scc != 0 {
                        nonzero = true;
                    }
                }
            }
        }
    }

    dbgln!(
        "found a suitable drive: {}\nany values nonzero? : {}",
        found,
        nonzero
    );
}
a snippet of the output

Code: Select all

[1][12][0] -> class code: 10001000000000000000000010001, base class code: 0, sub class code: 0
[1][12][1] -> id: 1000100010001 (1111), vendor: 0 (0)
[1][12][1] -> class code: 10001000000000000000000010001, base class code: 0, sub class code: 0
[1][12][2] -> id: 1000100010001 (1111), vendor: 0 (0)
[1][12][2] -> class code: 10001000000000000000000010001, base class code: 0, sub class code: 0
[1][12][3] -> id: 1000100010001 (1111), vendor: 0 (0)
[1][12][3] -> class code: 10001000000000000000000010001, base class code: 0, sub class code: 0
[1][12][4] -> id: 1000100010001 (1111), vendor: 0 (0)
[1][12][4] -> class code: 10001000000000000000000010001, base class code: 0, sub class code: 0
the issue could be from the fact that I'm using ffi to call from rust, but I sort of doubt it. I'm pretty bad at math so I could possibly be doing shifting wrong or something, or it could be a deeper issue.

thank you,
skylar alexandra bleed

Re: reading pci id is always 0x1111

Posted: Mon Apr 12, 2021 1:42 pm
by Octocontrabass
monarrk wrote:

Code: Select all

// port is the byte offset from 0x00
They're called registers, not ports.
monarrk wrote:

Code: Select all

	return (inl(PCI_DATA) >> shift) & 0xFF;
PCI_DATA allows byte and word accesses, you don't have to access the whole dword every time. Add the lowest two bits of the register address to PCI_DATA. (But you still can't cross a dword boundary.)
monarrk wrote:

Code: Select all

                let vendor = (id & 0xFFFF0000) << 15;
You're shifting the wrong way.

There may be other issues, I'm not very familiar with Rust.

Re: reading pci id is always 0x1111

Posted: Tue Apr 13, 2021 12:08 pm
by Ethin
First, your code you provided at the top is not Rust. Its C. I don't know if your trying to link Rust and C together or what, but I would hesitate in doing that in a low-level project like this, if only because the compiler might not compile your code correctly. (Rust obviously can't read C code, so it has to go to Clang or another C compiler.)
Next, I don't think your reading code is correct. The PCI article explicitly states:

Code: Select all

uint16_t pciConfigReadWord (uint8_t bus, uint8_t slot, uint8_t func, uint8_t offset) {
    uint32_t address;
    uint32_t lbus  = (uint32_t)bus;
    uint32_t lslot = (uint32_t)slot;
    uint32_t lfunc = (uint32_t)func;
    uint16_t tmp = 0;
 
    /* create configuration address as per Figure 1 */
    address = (uint32_t)((lbus << 16) | (lslot << 11) |
              (lfunc << 8) | (offset & 0xfc) | ((uint32_t)0x80000000));
 
    /* write out the address */
    outl(0xCF8, address);
    /* read in the data */
    /* (offset & 2) * 8) = 0 will choose the first word of the 32 bits register */
    tmp = (uint16_t)((inl(0xCFC) >> ((offset & 2) * 8)) & 0xffff);
    return (tmp);
}
Therefore, the equivalent Rust translation would be something like (note, untested):

Code: Select all

pub fn pciConfigReadWord (bus: u8, slot: u8, func: u8, offset: u8) -> u16 {
    /* create configuration address as per Figure 1 */
    let address = (((bus as u32) << 16) | ((slot as u32) << 11) | ((func as u32) << 8) | ((offset as u32) & 0xfc) | 0x80000000;
    /* write out the address */
    unsafe {
        outl(0xCF8, address);
    }
    /* read in the data */
    /* (offset & 2) * 8) = 0 will choose the first word of the 32 bits register */
    let res = unsafe {
        inw(0xcfc)
        };
((res >> (((offset as u16) & 2) * 8)) & 0xffff)
}
One implementation that I found that worked was this:

Code: Select all

    let lbus = bus as u32;
    let lslot = slot as u32;
    let lfunc = func as u32;
    unsafe {
        outl(
            ((((lbus as u32) << 16) as u32)
                | (((lslot as u32) << 11) as u32)
                | (((lfunc as u32) << 8) as u32)
                | ((offset as u32) & 0xfc)
                | (0x80000000)) as u32,
            0xCF8,
        );
        inw(0xCFC)
    }
This can be trivially adapted for byte and dword accesses. If you tweak it a bit you can make a universal PCI read/write function in one.

Re: reading pci id is always 0x1111

Posted: Tue Apr 13, 2021 12:22 pm
by Octocontrabass
Ethin wrote:The PCI article explicitly states:
That code is an awful example. It does work, but there are easier ways.
Ethin wrote:One implementation that I found that worked was this:
It only works when the low two bits of the offset are zero. If you want to read the word at offset 1 or 2, for example, you would need to add those two bits to 0xCFC so that the final inw call reads from 0xCFD or 0xCFE. (What about offset 3? That crosses a dword boundary, so you have to split it into two byte accesses.)

Re: reading pci id is always 0x1111

Posted: Tue Apr 13, 2021 6:14 pm
by Ethin
Octocontrabass wrote:
Ethin wrote:The PCI article explicitly states:
That code is an awful example. It does work, but there are easier ways.
Ethin wrote:One implementation that I found that worked was this:
It only works when the low two bits of the offset are zero. If you want to read the word at offset 1 or 2, for example, you would need to add those two bits to 0xCFC so that the final inw call reads from 0xCFD or 0xCFE. (What about offset 3? That crosses a dword boundary, so you have to split it into two byte accesses.)
And the PCI article conveniently leaves something like that, which is very important, out? Wow. Talk about deliberately making people's lives hard. Thanks for that -- no one ever said anything like that when I wrote that code. My kernel doesn't use that code anymore -- I've switched completely over to PCIe now -- but still, that's useful to know.

Re: reading pci id is always 0x1111

Posted: Wed Apr 14, 2021 6:22 am
by monarrk
First, your code you provided at the top is not Rust. Its C. I don't know if your trying to link Rust and C together or what, but I would hesitate in doing that in a low-level project like this, if only because the compiler might not compile your code correctly. (Rust obviously can't read C code, so it has to go to Clang or another C compiler.)
I am aware. I am using ffi, like I said in the original post

Re: reading pci id is always 0x1111

Posted: Wed Apr 14, 2021 8:12 am
by nullplan
Ethin wrote:And the PCI article conveniently leaves something like that, which is very important, out?
Why is this in any way important? My PCI stack uses only aligned DWORD reads and writes, and somehow I make do with that. I cannot think of a single field anywhere in PCI that crosses a DWORD boundary and is misaligned. Even for the MMIO method, I only use aligned DWORD accesses. It can be done.

Re: reading pci id is always 0x1111

Posted: Thu Apr 15, 2021 8:00 am
by Klakap
My favorite PCI read method in C:

Code: Select all

//this method can read everything from PCI
uint32_t read_pci(uint32_t bus, uint32_t dev, uint32_t func, uint32_t offset) {
    outl(0xCF8, (0x80000000 | (bus<<16) | (dev<<11) | (func<<8) | (offset & 0xFC));
    return inl(0xCFC);
}

//and how read what you want
#define read_pci_vendorid(bus, dev, func) (read_pci(bus, dev, func, 0x00) & 0xFFFF)
#define read_pci_deviceid(bus, dev, func) (read_pci(bus, dev, func, 0x00) >> 16)
#define read_pci_class(bus, dev, func) (read_pci(bus, dev, func, 0x08) >> 24)
#define read_pci_subclass(bus, dev, func) ((read_pci(bus, dev, func, 0x08) >> 16) & 0xFF)
#define read_pci_progif(bus, dev, func) ((read_pci(bus, dev, func, 0x08) >> 8) & 0xFF)
//...
Hope that this will help.