How does BIOS determine the PCI port type during enumeration process? - pci

As in PCI Express a capability register called “pci express capability register” specifies the device/port type field which tells whether its root port, upstream switch port, switch downstream port, end point etc.
What mechanism does BIOS use to determine the port/device type during PCI Bus enumeration ?

I am not sure answering your question but while getting bridge operation in coreboot (get_pci_bridge_ops) https://github.com/coreboot/coreboot/blob/master/src/device/pci_device.c it is done that way :
unsigned int pciexpos;
pciexpos = pci_find_capability(dev, PCI_CAP_ID_PCIE);
if (pciexpos) {
u16 flags;
flags = pci_read_config16(dev, pciexpos + PCI_EXP_FLAGS);
switch ((flags & PCI_EXP_FLAGS_TYPE) >> 4) {
case PCI_EXP_TYPE_ROOT_PORT:
case PCI_EXP_TYPE_UPSTREAM:
case PCI_EXP_TYPE_DOWNSTREAM:
printk(BIOS_DEBUG, "%s subordinate bus PCI Express\n",
dev_path(dev));
return &default_pciexp_ops_bus;
case PCI_EXP_TYPE_PCI_BRIDGE:
printk(BIOS_DEBUG, "%s subordinate PCI\n",
dev_path(dev));
return &default_pci_ops_bus;
default:
break;
}
}

It's fixed - the vendor programs that field based on the design of its product.
The manufacturer always knows if a port is upstream or downstream or if it has just designed a root complex instead of a switch or if it is using PCI instread PCIe.
Simply put this is where the vendor knowledge is essential and why it is its duty to write a firmware.
Specifically, the field value can come from an EEPROM/FlashROM or be programmed by the BIOS/UEFI at early boot using hardwired values.
It doesn't really matter how it is done, what matters is that the field gets initialized as intended in the factory before any dependent software read it.

Related

Does modern operating-systems use the MCFG ACPI table to find the registers of the xHCI?

I'm working on a minimal hobby operating-system. I want to write a driver for xHCI in order to support USB keyboards and mouses. I'm using QEMU for virtualization. I'm passing the -device qemu-xhci parameter to qemu so that it emulates the xHCI. I was wondering if the modern operating-systems use the MCFG ACPI table to find the configuration space of PCI (and the registers of the xHCI)? I was also wondering how to enable PCI-Express in QEMU so that I can find a MCFG table in RAM so that I can support the latest technology in my hobby OS.
To find all xHCI controllers you search PCI configuration space for devices ("functions") with matching "class/subclass/progID" values (see note 2); which means that you have to find a way to access PCI configuration space first.
On 80x86; there are 3 possible ways to access PCI configuration space - 2 that use IO ports ("mechanism #1" and the deprecated "mechanism #2"), and one that maps PCI configuration space into the physical address space (called "Enhanced Configuration Access Mechanism").
If the Enhanced Configuration Access Mechanism is supported; the MCFG ACPI table describes how PCI configuration space is mapped into the physical address space. Primarily; PCI buses are described as "groups of buses", where each group (defined by a "starting bus number" and "total buses in this group" pair) has a base physical address, and the correct physical address for a PCI function is determined by finding information for the relevant group of buses for the requested bus number, then doing a calculation like:
physical_address = base_physical_address_for_group +
(bus_number - starting_bus_number_for_group) << 20 +
device_number << 15 +
function_number << 12 +
offset;
Note 1: because most operating systems use virtual memory it's possible for an OS to create a nice "virtually linear" mapping of the ("possibly physically disjoint") physical memory areas described by MCFG ACPI table (while using the same page full of zeros mapped as read-only to fill any gaps in the "virtually linear mapping"); so that the OS can use a simplified approach (with no need to find information for the relevant group of buses) like:
virtual_address = PCI_config_space_base_virtual_address +
bus_number << 20 +
device_number << 15 +
function_number << 12 +
offset;
Note 2: An OS doesn't/shouldn't literally search PCI configuration space each time it wants to start a device driver for one specific type of device. Instead an OS typically enumerates PCI buses once during boot (and possibly after boot in response to a notification if "hot-plug PCI" is supported) and starts device drivers based on the results of that enumeration. In other words, it's more like "I found an xHCI controller and need to start the appropriate driver" and not like "I want to start an xHCI driver and need to find the appropriate device/s".

Question about Message Signaled Interrupts (MSI) on x86 LAPIC system

Hi I'm writing a kernel and plan to use MSI interrupt for PCI devices.
However, I'm also quite confused by the documentations.
My understanding about MSI are as follow:
From PCI device point of view:
Documentations indicate that I
need to find Capabillty ID = 0x05 to locate 3 registers: Message control (MCR), Message Address (MAR) and Message Data (MDR) registers
MCR provide control functionality for MSI interrupt,
MAR provide the physical address the PCI device
will write once interrupt occurs
MDR forms out the actual data it will write into the physical address
From CPU point of view:
Documentation shows that Message Address register contains fixed top of 0xFEE, and following by destination ID (LAPIC ID) and other controlling bits as follow:
The Message Data register will contain the following information, including the interrupt vector:
After reading all of these, I am thinking if the APIC_ID is 0x0h would the Message Address conflict with the Local APIC memory mapping? Although the address of FEE00000~FEE00010 are reserved.
In addition, is it true that the vector number in MDR is corresponding to the IDT vector number. In other words, if I put MAR = 0xFEE0000C (Destination ID = 0, Using logical APIC ID) and MDR = 0x0032 (edge trigger, Vector = 50) and enable the MSI interrupt, then once the device issues an interrupt CPU would correspondingly run the function pointed by IDT[50]? After that I write 0h to EOI register to end it?
Finally, according to the documentation, the upper 32 bit of MAR is not used? Can anyone help on this?
Thanks a lot!
Your understanding of how to detect and program MSI in a PCI (or PCIe) device is correct.*
The message address controls the destination (which CPU the interrupt is sent to), while the message data contains the vector number. For normal interrupts, all bits of the message data should be 0 except for the low 8 bits, which contain the vector.
The vector is an index into the IDT, so if the message data is 0x0032, the interrupt is delivered through entry 50 of the IDT.**
If the Destination ID in an interrupt message is 0, the Message Address of the MSI does match the default address of the local APIC, but they do not conflict, because the APIC can only be written by the CPU and MSIs can only be written by devices.
On x86 platforms, the upper 32 bits of the message address must be 0. This can be done by setting the upper part of the message address to 0 or by programming the device to use a 32-bit message address (in which case the upper message address register is not used). The PCI spec was designed to work with systems where 64-bit MSI addresses are used, but x86 systems never use the upper 32 bits of the message address.
Reprogramming the APIC base address by writing to the APIC_BASE MSR does not affect the address range used for MSI; it is always 0xFEExxxxx.
* You should also look at the MSI-X capability, because some devices support MSI-X but not MSI. MSI-X is a bit more flexible, which inevitably makes it a bit more complicated.
** When using the MSI capability, the message data isn't exactly the value in the Message Data Register (MDR). The MSI capability allows the device to use several contiguous vectors. When the device sends an interrupt message, it replaces the low bits of the MDR with a different value depending on the interrupt cause within the device.

How to boot STM32 from user flash?

STM32 microcontrollers are capable to boot from different sources such as:
User-Flash
System-memory
Embedded-SRAM.
On the firmware side, does "boot from user Flash" means executing a custom bootloader?
No.
The "Boot from User Flash" mode means that the application code that will be run after reset is located in user flash memory. The user flash memory in that mode is aliased to start at address 0x00000000 in boot memory space. Upon reset, the top-of-stack value is fetched from address 0x00000000, and code then begins execution at address 0x00000004.
In contrast, the "Boot from System Memory" mode simply means that the system memory (not the user flash) is now aliased to start at address 0x00000000. The application code in this case must have already been loaded into system memory.
The "Boot from Embedded SRAM" mode does not alias the SRAM address. When this mode is selected, the device expects the vector table to have been relocated using the NVIC exception table and offset register, and execution begins at the start of embedded SRAM. The application code in this case must have already been loaded into embedded SRAM.
For more details, refer to the Reference Manual for the specific family of STM32 devices you are using, in the section titled "Boot Configuration".
It depends.
The option "Boot from user Flash" will run whatever is in the user flash. This code could be anything you want. It could be:
a custom bootloader or
it could be application code.
The distinction is a logical one based on what you write your code to do. i.e. it is even possible to have an app that rewrites part or all of itself (what you call it is up to you).
If you do use a custom bootloader then you will normally have two projects.
The custom bootloader will have its own projects with its own generated binary image loaded into the start of flash.
The application code also having its own project and generated .bin file.
The application is loaded into a specific address that the bootloader knows about when it was built (in some free statically allocated memory block - normally on page boundaries to allow for reflashing while keeping the bootloader unchanged). These addresses can be configured in linker files.
Custom bootloaders are a great way of allowing features like:
A custom coms stack, to reflash your application via some other coms protocol.
Dual redundancy of the application code.
Note the STM32 micros "System Memory" already contains ST's own bootloader which supports a range of serial links to reflash the micro without needing a fully custom bootloader.
ST's documentation is stupid. They mention this, but they don't also tell that you need to only configure BOOT0 & BOOT1 pins in order to select the boot source.
So these two pins binary speaking give you three options that are:
(A) BOOT0 = 0, BOOT1 = whatever,
(B) BOOT0 = 1, BOOT1 = 0 and
(C) BOOT0 = 1, BOOT1 = 1.
Option A boots in FLASH, option B boots in bootloader and option C boots in SRAM.
So how to use this. First choose configuration B to start communicating with the bootloader to whom you instruct (in bootloader's own commands) to erase FLASH and upload your program inside FLASH. Then you change the configuration to A and reboot using MCU's RESET pin.
Note:
I perfer the NXP's documentation which has everything black on white.
As a developper I value my time and good documentation is a way to go.
Ditch ST and go for NXP if documentation is what you value. Here is NXP's sample documentation:
https://www.nxp.com/docs/en/user-guide/UM10562.pdf
I am really sorry ST, but you have to do better than what you are currently doing...
STM MCUs are a bit stupid in this regard. You need to configure the Option bytes in Flash so that your MCU will always run code from the user Flash after reset.
Run this code at the beginning of your main:
if ((FLASH->OPTR & FLASH_OPTR_nSWBOOT0_Msk) != 0x0 || ((FLASH->OPTR & FLASH_OPTR_nBOOT0_Msk) == 0x0))
{
while ((FLASH->SR & FLASH_SR_BSY_Msk) != 0x0) { ; }
FLASH->KEYR = 0x45670123;
while ((FLASH->SR & FLASH_SR_BSY_Msk) != 0x0) { ; }
FLASH->KEYR = 0xCDEF89AB;
while ((FLASH->SR & FLASH_SR_BSY_Msk) != 0x0) { ; }
FLASH->OPTKEYR = 0x08192A3B;
while ((FLASH->SR & FLASH_SR_BSY_Msk) != 0x0) { ; }
FLASH->OPTKEYR = 0x4C5D6E7F;
while ((FLASH->SR & FLASH_SR_BSY_Msk) != 0x0) { ; }
FLASH->OPTR = (FLASH->OPTR & ~(FLASH_OPTR_nSWBOOT0_Msk)) | FLASH_OPTR_nBOOT0_Msk;
while ((FLASH->SR & FLASH_SR_BSY_Msk) != 0x0) { ; }
FLASH->CR = FLASH->CR | FLASH_CR_OPTSTRT;
while ((FLASH->SR & FLASH_SR_BSY_Msk) != 0x0) { ; }
}

C++/Windows: Get unique machine id: mac address, volume serial number,

My platform is Windows 7 (or later), and I am using Visual Studio 2010.
In an attempt to get a unique machine identifier, I was trying to retrieve the mac address and I encountered the following problem.
I am having difficulty identifying which is the primary ethernet network adapter from the list of adapters returned by GetAdapatersInfo method.
I can get a list of ethernet adapters by checking their type (it should be MIB_IF_TYPE_ETHERNET).
However, there are multiple ethernet adapters on my machine: Actual LAN adapter, Cisco created software adapter, bluetooth ethernet adapater, etc.
Depending upon how I am connected to the internet, this list keeps changing.
So, how do I know which one is the actual ethernet adapter (the one that will connect using LAN cable).
Well,
After experimenting for some time (about one month), the volume number seems to be a reliable metric for generating unique id which is persistent across reboots and which cannot be changed by the user. This id will not change unless the disk is reformatted.
The following code fetches the volume number.
int getVolumeNumber(char *volumeNumber, int capacity) {
for(int i=0; i<capacity; i++) {
volumeNumber[i] = '\0';
}
TCHAR volumeName[MAX_PATH + 1] = { 0 };
TCHAR fileSystemName[MAX_PATH + 1] = { 0 };
DWORD serialNumber = 0;
DWORD maxComponentLen = 0;
DWORD fileSystemFlags = 0;
if(GetVolumeInformation(_T("C:\\"), volumeName, ARRAYSIZE(volumeName), &serialNumber, &maxComponentLen, &fileSystemFlags, fileSystemName, ARRAYSIZE(fileSystemName)) != 0) {
sprintf(volumeNumber,"%lu",serialNumber);
return 0;
} else {
return 1;
}
}
In the above code, I am fetching the volume number of the C: drive.
"the volume number seems to be a reliable metric for generating unique id which is persistent across reboots and which cannot be changed by the user."
The last part of this statement ("cannot be changed by the user.") is not true. There are several utilities which either change or spoof the volume serial number.
See for example https://www.raymond.cc/blog/changing-or-spoofing-hard-disk-hardware-serial-number-and-volume-id/ .
Depending on your use case, you might be slightly better off using the hard disk serial number, which is provided by the HD manufacturer and cannot be changed by the user (but CAN be spoofed). It can be retrieved using the Win32_PhysicalMedia class (https://msdn.microsoft.com/en-us/library/windows/desktop/aa394346%28v=vs.85%29.aspx).
Another option might be to enumerate all ethernet adapters, sort them and compare the result - but it seems you have investigated this road.
In general, anything that could be used as a unique ID of a PC could be used for "software protection" (i.e. preventing unauthorised use of software), and it is therefore highly probable that people have tried to find ways to circumvent it.

Can you access PCI cards(32 bits) in "real mode"?

Can you access PCI cards(32 bits) in "real mode" ? Isn't "real mode" 16 bits? I have a developer claiming he can only access hardware in Real mode. But PCI is 32bits...
Yes, you can.
IO ports 0xCF8 and 0xCF9 act as index and data registers for accessing PCI Config space. The address to be written to index register (i.e. 0xCF8) has a fixed predefined format (refer PCI spec). To access pci config data, one writes to index register and then reads from data register.
The Index register is a DWORD (32-bit) register and the format is:
Byte-3 = 0x80
Byte-2 = Bus No
Byte-1 = Upper 5 bits as DEVICE no, and lower 3 bits as FUNCTION no.
Byte-0 = Register no. to read from config space
So to read from Bus:0 Device:0 Func:0 register:0 in real mode, you would say:
IoPortWrite32(0xCF8, 0x80000000);
ValueRead = IoPortRead32(0xCFC);
Hope this helps!
Thanks,
Rohit