PCI Configuration reads strange address for BAR0-BAR5

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
skyesp
Posts: 15
Joined: Sat Oct 16, 2021 11:57 am

PCI Configuration reads strange address for BAR0-BAR5

Post by skyesp »

Hiya all. Sorry for the frequent questions to this forum, but I have another problem. I've recently been trying to implement PCI.I've been going off the PCI page on the wiki for the configuration reading and interpretation. All seemed well, and I read a fairly rational looking list of devices, with notably one mass storage controller of type 0x6 (SATA, hopefully). However, once I tried reading the BAR* addresses for function 2 (or function 1, if you start counting at 0), I ran into a perplexing set of data. Here is the struct that my function produced (note that here, I only scanned for the base addresses of function 2 for now); Note that all numbers are in base 10:

Code: Select all

PCIDevice {
    bus: 0,
    device: 1,
    vendor_id: 32902,
    device_id: 28672,
    class: MassStorageController,
    subclass_id: 6,
    header_type: 0,
    base_addresses_f1: Some(
        [
            0,
            0,
            0,
            0,
            49249,
            0,
        ],
    ),
}
This is strange, because the other devices read like this:

Code: Select all

PCIDevice {
    bus: 0,
    device: 2,
    vendor_id: 4660,
    device_id: 4369,
    class: Unclassified,
    subclass_id: 3,
    header_type: 0,
    base_addresses_f1: Some(
        [
            4294967295,
            4294967295,
            4294967295,
            4294967295,
            4294967295,
            4294967295,
        ],
    ),
}
Granted, it may be nonsensical to scan for function 1 addresses for most devices, so I can understand why they would return 2³²-1. But why is the SATA device's address a (base 10) part of this number? This seems odd. Has anyone got an explanation for this?

As an aside, does anyone know any good resources for actual PCI communication? Do I just use the numbers returned as port numbers or memory addresses? Sorry if this is a stupid question or just something I overlooked on the wiki article?

Many thanks.
User avatar
iansjack
Member
Member
Posts: 4703
Joined: Sat Mar 31, 2012 3:07 am
Location: Chichester, UK

Re: PCI Configuration reads strange address for BAR0-BAR5

Post by iansjack »

I’m not quite clear what your problem is. Is it the port number (0xC060)? That seems reasonable to me.
skyesp
Posts: 15
Joined: Sat Oct 16, 2021 11:57 am

Re: PCI Configuration reads strange address for BAR0-BAR5

Post by skyesp »

iansjack wrote:I’m not quite clear what your problem is. Is it the port number (0xC060)? That seems reasonable to me.
First, thank you for confirming (?) that it is a port I am looking at. Second, perhaps it is just a coincidence that the port number resembles the other numbers in decimal. Maybe I'm just being a bit paranoid :?
User avatar
iansjack
Member
Member
Posts: 4703
Joined: Sat Mar 31, 2012 3:07 am
Location: Chichester, UK

Re: PCI Configuration reads strange address for BAR0-BAR5

Post by iansjack »

Looks like it’s just a coincidence. The fact that it’s a multiple of 16 makes it look like a valid port number to me.

The real answer is to try reading/writing to that port range to see if you get the expected results.
Octocontrabass
Member
Member
Posts: 5563
Joined: Mon Mar 25, 2013 7:01 pm

Re: PCI Configuration reads strange address for BAR0-BAR5

Post by Octocontrabass »

skyesp wrote:Granted, it may be nonsensical to scan for function 1 addresses for most devices, so I can understand why they would return 2³²-1.
Indeed. If function 0 says the device isn't multifunction, then scanning any of the other 7 functions could return all kinds of garbage.
skyesp wrote:But why is the SATA device's address a (base 10) part of this number? This seems odd. Has anyone got an explanation for this?
It is purely a coincidence. You should print your numbers in hexadecimal so it's easier to read the individual bits.

The address itself makes no sense for an AHCI controller. Are you sure you're looking at one?
skyesp wrote:As an aside, does anyone know any good resources for actual PCI communication? Do I just use the numbers returned as port numbers or memory addresses? Sorry if this is a stupid question or just something I overlooked on the wiki article?
The low bits of each BAR tell you whether it's a memory or I/O address, as well as some other important information. For example, a BAR containing 0xC061 indicates an I/O address of 0xC060. All of this information comes from the PCI Local Bus Specification, which I'm sure you can "borrow" from someone.
skyesp
Posts: 15
Joined: Sat Oct 16, 2021 11:57 am

Re: PCI Configuration reads strange address for BAR0-BAR5

Post by skyesp »

Octocontrabass, I am not 100 percent sure I am reading correctly. If you feel like it, you take a quick peek at my source code. The pci_config_read_word function is translated from the wiki page, but my offsets might be wrong.

Code: Select all

fn pci_config_read_dword(bus: u8, device: u8, function: u8, offset: u8) -> u32{
    let long_bus = bus as u32;
    let long_slot = device as u32;
    let long_func = function as u32;
    let mut tmp = 0u32;
    //Create the address as per figure 1 at the link above.
    let address = ((long_bus << 16) | (long_slot << 11)
        | (long_func << 8) | (offset & 0xFC) as u32 | (0x80000000 as u32)) as u32;
    //Port to be used for the address.
    let mut port1:PortGeneric<u32, ReadWriteAccess> = Port::new(0xCF8);
    unsafe {
        //Write the address
        port1.write(address);
    }
    //Port to be used for the data.
    let mut port2: PortGeneric<u32, ReadWriteAccess> = Port::new(0xCFC);
    unsafe { port2.read() }
}
^^ this is the function to read a dword from the configuration register. This might perhaps be a naive approach, I am not sure.

Code: Select all

///Read the header type of a pci device from its configuration space
fn get_pci_header_type(bus: u8, device: u8)->u8{
    pci_config_read_word(bus, device, 0, 0xC+0x2).to_le_bytes()[1]
}

///Get the vendor ID of a PCI device. Header type independent.
fn get_vendor_id(bus: u8, device: u8, function: u8) -> u16{
    pci_config_read_word(bus, device, function, 0)
}

///Get the device ID of a PCI device. Header type independent.
fn get_device_id(bus: u8, device: u8, function: u8) -> u16{
    pci_config_read_word(bus, device, function, 0x2)
}

///Get the PCI Device Class of a PCI device. Header type independent.
fn check_pci_device_class(bus: u8, device: u8, function: u8) -> u8{
    pci_config_read_word(bus, device, function, 0xA).to_le_bytes()[0]
}

///Get the PCI device subclass of a PCI device. Header type independent.
fn check_pci_device_subclass(bus: u8, device: u8, function: u8) -> u8{
    pci_config_read_word(bus, device, function, 0xA).to_le_bytes()[1]
}
Finally, this is the function to check the device.

Code: Select all

///Check a PCI device and return None if the device does not exist or Some<PCIDevice> if it does.
fn check_device(bus: u8, device: u8) -> Option<PCIDevice>{
    //FIXME: multiple function devices
    let header_type = get_pci_header_type(bus, device);
    let vendor_id = get_vendor_id(bus, device, 0);
    if vendor_id == 0xFFFF{
        return None;
    }
    //Device vendor is valid
    let device_id = get_device_id(bus, device, 0);
    let class_id = check_pci_device_class(bus, device, 0);
    let subclass_id = check_pci_device_subclass(bus, device, 0);
    let mut base_addresses_f1 = None;
    if header_type == 0x0{
        base_addresses_f1 = Some([
            pci_config_read_dword(bus, device, 1, 0x10),
            pci_config_read_dword(bus, device, 1, 0x14),
            pci_config_read_dword(bus, device, 1, 0x18),
            pci_config_read_dword(bus, device, 1, 0x1C),
            pci_config_read_dword(bus, device, 1, 0x20),
            pci_config_read_dword(bus, device, 1, 0x24),
        ])
    }
    //println!("PCI: header type: {:#x}, vendor = {:#x}, device = {:#x}, class = {:#x}, subclass = {:#x}", header_type, vendor_id, device_id, class_id, subclass_id);
    Some(PCIDevice{
        bus,
        device,
        vendor_id,
        device_id,
        subclass_id,
        class: class_id.into(),
        base_addresses_f1,
        header_type
    })
}
nullplan
Member
Member
Posts: 1790
Joined: Wed Aug 30, 2017 8:24 am

Re: PCI Configuration reads strange address for BAR0-BAR5

Post by nullplan »

skyesp wrote:Octocontrabass, I am not 100 percent sure I am reading correctly.
Well, what is not clear about what he wrote? You enumerate a device by enumerating its function 0, and if that worked and function 0 said the device is multifunction, then enumerate functions 1-7. So your function check_device is missing a parameter and your enumerator must be changed to account for MF devices.
Carpe diem!
skyesp
Posts: 15
Joined: Sat Oct 16, 2021 11:57 am

Re: PCI Configuration reads strange address for BAR0-BAR5

Post by skyesp »

nullplan wrote:
skyesp wrote:Octocontrabass, I am not 100 percent sure I am reading correctly.
Well, what is not clear about what he wrote? You enumerate a device by enumerating its function 0, and if that worked and function 0 said the device is multifunction, then enumerate functions 1-7. So your function check_device is missing a parameter and your enumerator must be changed to account for MF devices.
Sorry, I meant I am not sure whether I am reading the configuration register correctly.
Octocontrabass
Member
Member
Posts: 5563
Joined: Mon Mar 25, 2013 7:01 pm

Re: PCI Configuration reads strange address for BAR0-BAR5

Post by Octocontrabass »

skyesp wrote:The pci_config_read_word function is translated from the wiki page, but my offsets might be wrong.

[...]

^^ this is the function to read a dword from the configuration register. This might perhaps be a naive approach, I am not sure.
The wiki sets a bad example. You don't need separate functions to read different sizes - it's perfectly valid to read an entire dword every time. But if you are going to read only a word, why would you use the IN instruction to read an entire dword? Port 0xCFC allows partial reads and writes!
skyesp wrote:

Code: Select all

///Read the header type of a pci device from its configuration space
fn get_pci_header_type(bus: u8, device: u8)->u8{
    pci_config_read_word(bus, device, 0, 0xC+0x2).to_le_bytes()[1]
}
You're reading the byte at offset 0xF, not 0xE, so you have no idea what the actual header type is or whether a device actually is multifunction. Also, the header type can be different for each function, but you've hardcoded function 0.
skyesp wrote:

Code: Select all

///Get the PCI Device Class of a PCI device. Header type independent.
fn check_pci_device_class(bus: u8, device: u8, function: u8) -> u8{
    pci_config_read_word(bus, device, function, 0xA).to_le_bytes()[0]
}

///Get the PCI device subclass of a PCI device. Header type independent.
fn check_pci_device_subclass(bus: u8, device: u8, function: u8) -> u8{
    pci_config_read_word(bus, device, function, 0xA).to_le_bytes()[1]
}
The class code is three bytes. You need all three bytes to identify a device.
skyesp wrote:Finally, this is the function to check the device.
Well, it's no wonder the results are nonsense. You're only reading the BARs from function 1 and nothing else. You can't interpret the BARs - or even know if they exist - without reading the rest of that function's configuration space.
skyesp
Posts: 15
Joined: Sat Oct 16, 2021 11:57 am

Re: PCI Configuration reads strange address for BAR0-BAR5

Post by skyesp »

@Octocontrabass, thank you very much for all your help. I will implement the changes you suggested and see if the results make more sense.
skyesp
Posts: 15
Joined: Sat Oct 16, 2021 11:57 am

Re: PCI Configuration reads strange address for BAR0-BAR5

Post by skyesp »

Octocontrabass wrote: The class code is three bytes. You need all three bytes to identify a device.
I don't quite understand this. The wiki says the class code is from bits 31-24 at the offset. Is the wiki incorrect?
Ethin
Member
Member
Posts: 625
Joined: Sun Jun 23, 2019 5:36 pm
Location: North Dakota, United States

Re: PCI Configuration reads strange address for BAR0-BAR5

Post by Ethin »

The class code consists of the class, subclass, and program interface codes. You need all three to know what kind of device your working with. In some cases, devices do not have a particular program interface, making the precise device unclear; for that, you should also include the vendor and device IDs.
Octocontrabass
Member
Member
Posts: 5563
Joined: Mon Mar 25, 2013 7:01 pm

Re: PCI Configuration reads strange address for BAR0-BAR5

Post by Octocontrabass »

skyesp wrote:The wiki says the class code is from bits 31-24 at the offset. Is the wiki incorrect?
The wiki should say "base class". The class code is three bytes taken together: the base class, the subclass, and the programming interface.
nlg
Member
Member
Posts: 40
Joined: Mon Mar 14, 2016 5:34 am

Re: PCI Configuration reads strange address for BAR0-BAR5

Post by nlg »

for the ahci I'm not sure but for the ide controller I observed that sometimes when the controller is in compatibility mode (see https://wiki.osdev.org/PCI_IDE_Controll ... Controller), BAR0 to BAR4 return 0 or strange values

as the bar0 to bar4 of the ahci are reserved for a compatibility operating mode, the operation may be identical
skyesp
Posts: 15
Joined: Sat Oct 16, 2021 11:57 am

Re: PCI Configuration reads strange address for BAR0-BAR5

Post by skyesp »

Octocontrabass wrote:
skyesp wrote:The wiki says the class code is from bits 31-24 at the offset. Is the wiki incorrect?
The wiki should say "base class". The class code is three bytes taken together: the base class, the subclass, and the programming interface.
Alright, that clears it up. Thank you!
Post Reply