PCI configuration space detection

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
User avatar
eino
Member
Member
Posts: 49
Joined: Fri Sep 16, 2011 10:00 am
Location: Finland

PCI configuration space detection

Post by eino »

Hi again. I'm trying to loop throught all pci busses, devices and functions in order to find USB devices but I'm not quite sure that my code works since no class 0x0C devices with 0x03 subclass can't be found (at least on QEMU)

Code: Select all

uint32_t read_pci ( const uint8_t bus, const uint8_t device, const uint8_t func, const uint8_t port /*, const uint8_t len */ ) {

	uint32_t ret;

	const uint32_t val = 0x80000000 | ( bus << 16 ) | ( device << 11 ) | ( func << 8 ) | ( port & 0xFC );
	outd ( 0x0CF8, val );
	extern uint32_t ind( uint32_t in_port );

/*	ret = ind( 0x0CFC + ( port & 0x3) ); */
/*	ret &= ( 0xFFFFFFFF >> ( ( 4 - len ) * 8 ) ); */

    ret = ( ( ind ( 0xCFC ) >> ( ( port & 2 ) * 8 ) ) & 0xFFFF );

	return ret;
}

/*
Scan all pci busses, devices and functions
*/
void enumerate_pci () {

	uint8_t bus = 0;
	uint8_t device = 0;
	uint8_t func = 0;

	uint32_t vendor_id;
	uint32_t device_id;
	uint32_t sub_class;
	uint32_t class;

	for ( bus = 0; bus < 255; bus++ ) {
		for ( device = 0; device < 32; device++ ) {
			for ( func = 0; func < 8; func++ ) {
				
				/* vendor id at offset 0 */
				vendor_id = read_pci ( bus, device, func, 0 );
				
				/* vendor found check for device id, subclass and class */
				if ( vendor_id != 0xFFFF ) {

					/* vendor id at offset 2 */
					device_id = read_pci ( bus, device, func, 2 );

					/* subclass at offset 0x0A */
					sub_class = read_pci ( bus, device, func, 0x0A );

					/* class at offset 0x0B */
					class = read_pci ( bus, device, func, 0x0B );

					log(DEBUG, "Vend: %p did: %p sub: %p c: %p", vendor_id, device_id, sub_class, class);

				}


			}	
		}		
	}

}
Am I doing something completely wrong here?

Here's the output (on QEMU)

Vend: 00008086 did: 00001237 sub: 00000600 c: 00000600
Vend: 00008086 did: 00007000 sub: 00000601 c: 00000601
Vend: 00008086 did: 00007010 sub: 00000101 c: 00000101
Vend: 00008086 did: 00007113 sub: 00000680 c: 00000680
Vend: 00001013 did: 000000B8 sub: 00000300 c: 00000300
Vend: 00008086 did: 0000100E sub: 00000200 c: 00000200

Seems that the sub class and class are the same.
Last edited by eino on Sun Aug 12, 2012 11:21 am, edited 1 time in total.
I'm Eino Tuominen from Finland, a web software dev learning low level stuff and reading / trying out kernel dev
User avatar
JamesM
Member
Member
Posts: 2935
Joined: Tue Jul 10, 2007 5:27 am
Location: York, United Kingdom
Contact:

Re: PCI configuration space detection

Post by JamesM »

And what does your code actually output?
User avatar
eino
Member
Member
Posts: 49
Joined: Fri Sep 16, 2011 10:00 am
Location: Finland

Re: PCI configuration space detection

Post by eino »

Ah. Sorry that was meant to be in the first post, it's now there. It's been a long day. Again.
I'm Eino Tuominen from Finland, a web software dev learning low level stuff and reading / trying out kernel dev
User avatar
NickJohnson
Member
Member
Posts: 1249
Joined: Tue Mar 24, 2009 8:11 pm
Location: Sunnyvale, California

Re: PCI configuration space detection

Post by NickJohnson »

Your read_pci() function only reads word-aligned values from the configuration space, so offset 0xA and 0xB give you the same thing.

Personally, I usually just declare a PCI configuration space structure in memory and read the entire contents at once. It helps to avoid annoying alignment calculations.
User avatar
eino
Member
Member
Posts: 49
Joined: Fri Sep 16, 2011 10:00 am
Location: Finland

Re: PCI configuration space detection

Post by eino »

Ach! Ofcourse. I changed the code a bit:

Code: Select all

uint32_t read_pci ( const uint8_t bus, const uint8_t device, const uint8_t func, const uint8_t port, const uint8_t len ) {

	uint32_t ret;

	const uint32_t val = 0x80000000 | ( bus << 16 ) | ( device << 11 ) | ( func << 8 ) | ( port & 0xFC );
	outd ( 0x0CF8, val );
	extern uint32_t ind( uint32_t in_port );

	ret = ind( 0x0CFC + ( port & 0x3) );
	ret &= ( 0xFFFFFFFF >> ( ( 4 - len ) * 8 ) );
/*  ret = ( ( ind ( 0xCFC ) >> ( ( port & 2 ) * 8 ) ) & 0xFFFF ); */

	return ret;
}

/*
Scan all pci busses, devices and functions
*/
void enumerate_pci () {

	uint8_t bus = 0;
	uint8_t device = 0;
	uint8_t func = 0;

	uint32_t vendor_id;
	uint32_t device_id;
	uint32_t sub_class;
	uint32_t class;

	for ( bus = 0; bus < 255; bus++ ) {
		for ( device = 0; device < 32; device++ ) {
			
			for ( func = 0; func < 8; func++ ) {
				
				/* vendor id at offset 0 */
				vendor_id = read_pci ( bus, device, func, 0, 2 );
				
				/* vendor found check for device id, subclass and class */
				if ( vendor_id != 0xFFFF ) {

					/* vendor id at offset 2 */
					device_id = read_pci ( bus, device, func, 2, 2 );

					/* subclass at offset 0x0A */
					sub_class = read_pci ( bus, device, func, 0x0A, 1 );

					/* class at offset 0x0B */
					class = read_pci ( bus, device, func, 0x0B, 1 );

					log(DEBUG, "B:%d V: %p D: %p SC: %p C: %p", bus, vendor_id, device_id, sub_class, class);

				}


			}	
		}		
	}

}
The output is now

Vend: 00008086 did: 00001237 sub: 00000000 c: 00000006
Vend: 00008086 did: 00007000 sub: 00000001 c: 00000000
Vend: 00008086 did: 00007010 sub: 00000001 c: 00000001
Vend: 00008086 did: 00007113 sub: 00000080 c: 00000006
Vend: 00001013 did: 000000B8 sub: 00000000 c: 00000003
Vend: 00008086 did: 0000100E sub: 00000000 c: 00000002

So there is three class 6 devices which are which are bridge devices. A host bridge, isa bridge and other bridge device. Sounds reasonable? Then there is a vga compatible device and ethernet controller all from Intel. And a vga compatible controller by Cirrus logic...

So... I guess there are more devices to be found beyond those bridge devices... This is something I haven't read about yet. Any links or a direct suggestions? Or maybe some pseudo code?

Btw Nick, I'm writing this on top of Pinion.
I'm Eino Tuominen from Finland, a web software dev learning low level stuff and reading / trying out kernel dev
User avatar
JamesM
Member
Member
Posts: 2935
Joined: Tue Jul 10, 2007 5:27 am
Location: York, United Kingdom
Contact:

Re: PCI configuration space detection

Post by JamesM »

You're missing multi-function devices.

The bridge devices are multi-function (I forget what indicates that - google it :) ) - each function is a different base device.

That's where your missing devices are.
User avatar
eino
Member
Member
Posts: 49
Joined: Fri Sep 16, 2011 10:00 am
Location: Finland

Re: PCI configuration space detection

Post by eino »

Well okay I tuned up the code a bit. Made a couple of structures and functions that handle them. Thou I will need to define more in detail what they will be, but they will suffice for now.

I tried this on real hardware and I think I'm getting results I want. For example there were three class 0x0C subclass 0x03 devices that I tracked down to be:

0x28308086 (ICH8 Family) USB UHCI Controller #1
0x28318086 (ICH8 Family) USB UHCI Controller #2
0x28328086 (ICH8 Family) USB UHCI Controller #3

The complete list was quite a bit longer on real hardware than on QEMU.

JamesM: I'm actually doing the function loop from 0 to 3 on each device... Or have I understood something wrong about multifunctions maybe...

Anyways here's the code... Maybe you can have one more look on it? =) (anyways hopefully this will be usefull for someone else too)

Code: Select all

#define MAX_PCI_DEVICES 100 /* 100 PCI devices should be enough? */

/*
Structure with all variables needed for a pci address
*/
struct pci_address {
	uint8_t enable_reserved; /* 10000000b - 0x80h */
	uint8_t bus_number;
	uint8_t device_and_function; /* & with 0xF8 to get device and & with 0x7 to get the function */
	uint8_t register_number; /* and with 0xFC to get the register number */
};

/*
Structure to hold a complete pci device information in 16 different 32 bit registers
*/
struct pci_device_registers {
	uint32_t reg[16];
};

/*
List that holds all pci devices raw register data
*/
struct pci_device_registers pci_devices[MAX_PCI_DEVICES];

/*
Structure that holds all variables needed to generate an pci port address (on register 0 (vendor/device))
*/
struct pci_address create_pci_address ( uint8_t bus, uint8_t device, uint8_t func ) {

	struct pci_address address;

	address.enable_reserved = 0x80;
	address.bus_number = bus;
	address.device_and_function = ( device << 3 ) | func;
	address.register_number = 0;
	
	return address;

}

/*
Gets an 32 bit pci address with specified register (0 = vendor/device, 08 = class, sub, prog if, revision id, ...)
*/
uint32_t get_pci_address_for_register ( struct pci_address address, uint8_t reg ) {
	
	uint32_t addr =	( address.enable_reserved << 24 ) |
					( address.bus_number << 16 ) |
					( address.device_and_function << 8 ) |
					( reg & 0xFC );


	return addr;

}

/*
Reads an 32 bit addres/register from the hardware
*/
uint32_t read_pci_register ( uint32_t address ) {

	outd ( 0x0CF8, address );
	extern uint32_t ind( uint32_t in_port );

	uint32_t result = ind ( 0xCFC );

	return result; 

}

/*
Scan all pci busses, devices and functions
*/
void enumerate_pci () {

	struct pci_address address;
	uint8_t bus		= 0;
	uint8_t device	= 0;
	uint8_t func	= 0;
	uint8_t reg		= 0;
	uint32_t vendor_and_device;

	int counter		= 0;
	short i			= 0;

	for ( bus = 0; bus < 255; bus++ ) {

		for ( device = 0; device < 32; device++ ) {
			
			for ( func = 0; func < 3; func++ ) {

				address = create_pci_address ( bus, device, func );

				vendor_and_device = read_pci_register ( get_pci_address_for_register ( address, reg ) );
				
				if ( vendor_and_device != 0xFFFFFFFF ) {

					for ( i = 0; i < 16; i++ ) {

						if ( i == 0 ) {
							pci_devices[counter].reg[i] = vendor_and_device;
						}
						else {
							pci_devices[counter].reg[i] = read_pci_register ( get_pci_address_for_register ( address, ( i * 4 ) ) );
						}
								

					}

					log(DEBUG, "V & D: %p S & C: %p", pci_devices[counter].reg[0], ( pci_devices[counter].reg[2] >> 16 ) );

					counter++;
				}
				
			}
		}
	}
}
I'm Eino Tuominen from Finland, a web software dev learning low level stuff and reading / trying out kernel dev
User avatar
JamesM
Member
Member
Posts: 2935
Joined: Tue Jul 10, 2007 5:27 am
Location: York, United Kingdom
Contact:

Re: PCI configuration space detection

Post by JamesM »

Ah bugger, sorry I misread your code.

In which case, what's the problem. Qemu won't put any USB devices on the bus unless you've added one with a command line parameter.
User avatar
eino
Member
Member
Posts: 49
Joined: Fri Sep 16, 2011 10:00 am
Location: Finland

Re: PCI configuration space detection

Post by eino »

Ah. Should be more careful and add the qemu options to the right place... Anyways

Code: Select all

-device piix3-usb-uhci 
did it.

Thanks!
I'm Eino Tuominen from Finland, a web software dev learning low level stuff and reading / trying out kernel dev
Post Reply