How does an OS find a peripheral's assigned address(es)?

jj1984

Member
Hello,

Last year some time, I asked a question about getting a peripheral's assigned memory address(es) from the BIOS.

I was told that the BIOS puts this info in some predefined memory location for the OS to query.

But, where is this location exactly?

How do I find it?

More specifically, let's say you want to write your own bootable code.

Further, your code is going to be really simple.

So simple, in fact, that it only consists of a single instruction.

Your bootable code is going to write a byte or word or double word or whatever to a register or RAM location on a peripheral device, not main RAM or a CPU register.

How do you find out what address(es) have been assigned to that peripheral memory location by the BIOS / UEFI?

Here's a more concrete example:

My bootable code's first and only instruction will write the number 11H to a register located on the sound card.

If the BIOS / UEFI initialization code did its job properly, that sound card register should be mapped into the CPU's memory space and/or IO space.

I need to find that address to accomplish that write.

How do I find it?

This is what real operating systems must do at some point.

When you open control panel / device manager in Windows, you see all the memory ranges for peripherals listed there.

At some point, Windows must have queried the BIOS /UEFI to find this data.

Again, how is this done?
 

jj1984

Member
The bios polls the PCI slots for devices and reads the pci configuration space. It's standardized in the PCI spec. This is the area where the device vendor and hardware id I mentioned in your other thread come from.

Thanks, but this sounds more like an explanation of how the BIOS interacts with peripherals in an effort to assign memory addresses to those peripherals. My question is once those addresses have been assigned, how is that information conveyed to the OS post boot.

Have a read through the enumeration section for a general overview https://en.wikipedia.org/wiki/PCI_configuration_space#Bus_enumeration

OK, I copied this from the article:

At this point, the BIOS or operating system will program the memory-mapped and I/O port addresses into the device's BAR configuration register.

Assuming my motherboard works in such a way that the BIOS is responsible for address assignment and not the OS, again, how is this information passed on to the OS?
 

Cromewell

Administrator
Staff member
Base address Registers (or BARs) can be used to hold memory addresses used by the device, or offsets for port addresses. Typically, memory address BARs need to be located in physical ram while I/O space BARs can reside at any memory address (even beyond physical memory). To distinguish between them, you can check the value of the lowest bit. The following tables describe the two types of BARs:

Memory Space BAR Layout
Code:
31 - 4                          3             2 - 1  0
16-Byte Aligned Base Address    Prefetchable  Type   Always 0
I/O Space BAR Layout
Code:
31 - 2                        1          0
4-Byte Aligned Base Address   Reserved   Always 1
The Type field of the Memory Space BAR Layout specifies the size of the base register and where in memory it can be mapped. If it has a value of 0x00 then the base register is 32-bits wide and can be mapped anywhere in the 32-bit Memory Space. A value of 0x02 means the base register is 64-bits wide and can be mapped anywhere in the 64-bit Memory Space (A 64-bit base address register consumes 2 of the base address registers available). A value of 0x01 is reserved as of revision 3.0 of the PCI Local Bus Specification. In earlier versions it was used to support memory space below 1MB (16-bit wide base register that can be mapped anywhere in the 16-bit Memory Space).

When you want to retrieve the actual base address of a BAR, be sure to mask the lower bits. For 16-Bit Memory Space BARs, you calculate (BAR[x] & 0xFFF0). For 32-Bit Memory Space BARs, you calculate (BAR[x] & 0xFFFFFFF0). For 64-Bit Memory Space BARs, you calculate ((BAR[x] & 0xFFFFFFF0) + ((BAR[x+1] & 0xFFFFFFFF) << 32)) For I/O Space BARs, you calculate (BAR[x] & 0xFFFFFFFC).

To determine the amount of address space needed by a PCI device, you must save the original value of the BAR, write a value of all 1's to the register, then read it back. The amount of memory can then be determined by masking the information bits, performing a bitwise NOT ('~' in C), and incrementing the value by 1. The original value of the BAR should then be restored. The BAR register is naturally aligned and as such you can only modify the bits that are set. For example, if a device utilizes 16 MB it will have BAR0 filled with 0xFF000000 (0x01000000 after decoding) and you can only modify the upper 8-bits.
How's that real time os going?
 

jj1984

Member
How's that real time os going?

I'm not writing an OS of any kind; I'm just trying to learn the low level stuff because as of right now it interests me much more than the upper level stuff.

Base address Registers (or BARs) can be used to hold memory addresses used by the device, or offsets for port addresses. Typically, memory address BARs need to be located in physical ram while I/O space BARs can reside at any memory address (even beyond physical memory).

OK, so the BARs hold the memory location(s) of the peripheral devices.

But, the BARs themselves are mapped into RAM.

When you want to retrieve the actual base address of a BAR, be sure to mask the lower bits. For 16-Bit Memory Space BARs, you calculate (BAR[x] & 0xFFF0). For 32-Bit Memory Space BARs, you calculate (BAR[x] & 0xFFFFFFF0). For 64-Bit Memory Space BARs, you calculate ((BAR[x] & 0xFFFFFFF0) + ((BAR[x+1] & 0xFFFFFFFF) << 32)) For I/O Space BARs, you calculate (BAR[x] & 0xFFFFFFFC).

OK, so this is how you actually determine what addresses are stored within the BARs, correct?

But, then how do you find what address in RAM the BARs are aliased to?

OK, let's make it real simple:

I'm going to write some bootable code that will run after the BIOS has initialized everything, and that bootable code will consist of only one instruction - move the number 10H into a register on the sound card.

That's it; nothing more.

So, my entire code will be: MOV X, 10H.

What do I put in for X? What's the actual physical address for this peripheral register? How do I find it?
 
Top