Page 1 of 1

Handling PCI BAR assignment in BIOS

Posted: Wed Nov 16, 2022 3:28 pm
by dc1010
I'm trying to write a simple x86 BIOS and I'm not able to initialize the PCI bus. Specifically, I'm trying to figure out how my BIOS would actually assign an address to BAR0 of a PCI device.

After following the various tutorials on OSDev and elsewhere, I am able to successfully enumerate the PCI bus, but I cannot seem to actually perform any CONFIG updates.

I have forked qemu and created my own custom PCI device called mydummypcidevice. This device has one BAR (BAR0) in memory address space, and does nothing other than log messages when read from or written to. Its vendor ID is 0x10E8 and its device ID is 0x7777.

When starting qemu with the default BIOS, BAR0 for the device is successfully registered:

Code: Select all

./qemu-system-i386.exe -monitor stdio -device mydummypcidevice

info pci
...
  Bus  0, device   4, function 0:
    Class 2432: PCI device 10e8:7777
      PCI subsystem 1af4:1100
      BAR0: 32 bit memory at 0xfebf1000 [0xfebf100f].
      id ""
Additionally, if I write a simple kernel which reads from the hard-coded BAR value (0xfebf1000), I get the expected log message
DUMMYPCIDEVICE :: mem_readfn :: memory read!
So it seems that the device implementation is working as expected.

I run qemu with my custom BIOS using this command:

Code: Select all

./qemu-system-i386.exe -monitor stdio -device mydummypcidevice -bios out/pci_bar_setup.rom -nographic
Reading the memory at 0x0000EEC6 yields 0x10e8 0x7777, which matches my device's vendor ID and device ID, as expected. However, the BAR is never updated, and remains unchanged from the initial:

Code: Select all

  Bus  0, device   4, function 0:
    Class 2432: PCI device 10e8:7777
      PCI subsystem 1af4:1100
      BAR0: 32 bit memory at 0xffffffffffffffff [0x0000000e].
      id ""
After setting CONFIG_ADDRESS, I'm trying to set BAR0 to 0x0001ABCD with this code (see source for full context):

Code: Select all

	mov dx, 0xCFC

	/* we want to assign 0x0001ABCD to BAR0 */
	mov eax, 0x0001ABCD
	out dx, eax
I'm running on MSYS2 MINGW64 on Windows 10.

Any help would be greatly appreciated!

Project files for my BIOS (adapted from https://pete.akeo.ie/2011/06/crafting-b ... ratch.html):

pci_bar_setup.s

Code: Select all

/********************************************************************************/
/*                         VMware BIOS ROM example                              */
/*       Copyright (c) 2011 Pete Batard ([email protected]) -  Public Domain         */
/********************************************************************************/


/********************************************************************************/
/* GNU Assembler Settings:                                                      */
/********************************************************************************/
.intel_syntax noprefix
.code16
/********************************************************************************/

/********************************************************************************/
/* Constants:                                                                   */
/********************************************************************************/
PCI_ENUMERATOR_OFFSET				= 0 /* also tried with 0x4 and 0x10, but BAR0 is still not updated */
PCI_ENUMERATOR_BUS					= 0
PCI_ENUMERATOR_DEVICE				= 4
PCI_ENUMERATOR_FUNC					= 0
TMP_ADDR_1							= 0x0000EEAA
ENUMERATED_PCI_ADDR					= 0x0000EEC6
SAVE_ADDR_1							= 0x0000EFCA
/********************************************************************************/


/********************************************************************************/
/* begin : Dummy section marking the very start of the BIOS.                    */
/* This allows the .rom binary to be filled to the right size with objcopy.     */
/********************************************************************************/
.section begin, "a"		/* The 'ALLOC' flag is needed for objcopy       		*/
	.ascii "PCIBARSETUP v1.00"	/* Dummy ID string                              */
	.align 16
/********************************************************************************/


/********************************************************************************/
/* main:                                                                        */
/* This section will be relocated according to the pci_bar_setup.ld script.              */
/********************************************************************************/
.section main, "ax"
.globl _pci_bar_setup
_pci_bar_setup:
	cli
	mov  ds, ax
	mov  ss, ax
	
	/* write a value here so that we can verify that this point was reached */
	movw [SAVE_ADDR_1], 0x9999
	
	/* begin dummy enumeration */
	
	mov eax, PCI_ENUMERATOR_BUS
	shl eax, 16
	mov edx, PCI_ENUMERATOR_DEVICE
	shl edx, 11
	or eax, edx
	mov edx, PCI_ENUMERATOR_FUNC
	shl edx, 8
	or eax, edx
	mov edx, TMP_ADDR_1
	mov edx, [edx]
	add edx, PCI_ENUMERATOR_OFFSET
	and edx, 0xFC
	or eax, edx
	or eax, 0x80000000

	mov dx, 0xCF8
	out dx, eax

	mov dx, 0xCFC
	in eax, dx
	mov DWORD PTR [ENUMERATED_PCI_ADDR], eax

	/* end of dummy enumeration */
	
	/* begin dummy write */
	
	mov eax, PCI_ENUMERATOR_BUS
	shl eax, 16
	mov edx, PCI_ENUMERATOR_DEVICE
	shl edx, 11
	or eax, edx
	mov edx, PCI_ENUMERATOR_FUNC
	shl edx, 8
	or eax, edx
	mov edx, TMP_ADDR_1
	mov edx, [edx]
	add edx, PCI_ENUMERATOR_OFFSET
	and edx, 0xFC
	or eax, edx
	or eax, 0x80000000

	mov dx, 0xCF8
	out dx, eax

	mov dx, 0xCFC
	in eax, dx
	
	/* we want to assign 0x0001ABCD to BAR0 */
	mov eax, 0x0001ABCD
	out dx, eax
	
	/* end of dummy write */
	
	/* write a value here so that we can verify that this point was reached */
	movw [SAVE_ADDR_1], 0x8888
	hlt

/********************************************************************************/

/********************************************************************************/
/* reset: this section must reside at 0xfffffff0, and be exactly 16 bytes       */
/********************************************************************************/
.section reset, "ax"
	/* Issue a manual jmp to work around a binutils bug.                    */
	/* See coreboot's src/cpu/x86/16bit/reset16.inc                         */
	.byte  0xe9
	.int   _pci_bar_setup - ( . + 2 )
	.align 16, 0xff	/* fills section to end of ROM (with 0xFF)              */
/********************************************************************************/
pci_bar_setup.ld

Code: Select all

OUTPUT_ARCH(i386)			/* i386 for 32 bit, i8086 for 16 bit       */

/* Set the variable below to the address you want the "main" section, from bios.S, */
/* to be located. The BIOS should be located at the area just below 4GB (4096 MB). */
main_address = 4096M - 4K;		/* Use the last 4K block                   */

/* Set the BIOS size below (both locations) according to your target flash size    */
MEMORY {
	ROM (rx) : org = 4096M - 512K, len = 512K
}

/* You shouldn't have to modify anything below this                                */
SECTIONS {
	ENTRY(_pci_bar_setup)			/* To avoid antivirus false positives      */
	/* Sanity check on the _pci_bar_setup entrypoint                                     */
	_assert = ASSERT(_pci_bar_setup >= 4096M - 64K, 
		"'_pci_bar_setup' entrypoint too low - it needs to reside in the last 64K.");
	.begin : {	/* NB: ld section labels MUST be 6 letters or less         */
		*(begin)
	} >ROM		/* Places this first section at the beginning of the ROM   */
	/* the --gap-fill option of objcopy will be used to fill the gap to .main  */
	.main main_address : {
		*(main)
	}
	.reset 4096M - 0x10 : {	 	/* First instruction executed after reset  */
		*(reset)
	}
	.igot 0 : {			/* Required on Linux                       */
		*(.igot.plt)
	}
}
Makefile

Code: Select all

.PHONY: default
default: generate_romfile ;

prepare :
	mkdir out -p
	
clean : 
	rm -rf out

compile : clean prepare
	gcc -c -o out/pci_bar_setup.o -m32 -mtune=generic -march=i386 -nostartfiles -no-pie -fno-pie -z -g -ggdb -static -m32 pci_bar_setup.s

to_binary : compile
	objcopy -O binary -j .main --set-section-flags .main=alloc,load,readonly,code out/pci_bar_setup.o out/main.bin

link : to_binary
	ld -A elf32-i386 -Tpci_bar_setup.ld -o out/pci_bar_setup.out out/pci_bar_setup.o -Map out/xMemLayout.map

generate_romfile : link
	objcopy -Felf32-i386 -O binary -j .begin -j .main -j .reset --gap-fill=0x0ff out/pci_bar_setup.out out/pci_bar_setup.rom


Re: Handling PCI BAR assignment in BIOS

Posted: Sun Nov 27, 2022 6:59 pm
by Octocontrabass
dc1010 wrote:After following the various tutorials on OSDev and elsewhere, I am able to successfully enumerate the PCI bus, but I cannot seem to actually perform any CONFIG updates.
How are you verifying your updates to the configuration space? Are you reading back the values you've written, or are you trying to access MMIO at the address you've set in the BAR?
dc1010 wrote:After setting CONFIG_ADDRESS, I'm trying to set BAR0 to 0x0001ABCD with this code (see source for full context):
This address is not valid for a MMIO BAR: it overlaps RAM. You must choose an address that doesn't overlap RAM.

You may need to initialize some other registers in the PCI configuration space to enable MMIO, and you may need to initialize some other devices such as bridges.

Re: Handling PCI BAR assignment in BIOS

Posted: Mon Nov 28, 2022 1:54 am
by rdos
Octocontrabass wrote:
dc1010 wrote:After following the various tutorials on OSDev and elsewhere, I am able to successfully enumerate the PCI bus, but I cannot seem to actually perform any CONFIG updates.
How are you verifying your updates to the configuration space? Are you reading back the values you've written, or are you trying to access MMIO at the address you've set in the BAR?
dc1010 wrote:After setting CONFIG_ADDRESS, I'm trying to set BAR0 to 0x0001ABCD with this code (see source for full context):
This address is not valid for a MMIO BAR: it overlaps RAM. You must choose an address that doesn't overlap RAM.

You may need to initialize some other registers in the PCI configuration space to enable MMIO, and you may need to initialize some other devices such as bridges.
Another problem is that the lower bits of BARs is used for other information than the physical address. In addition to that, BIOS must detect the size of the BAR and must allocate an address with proper alignment. I think this is typically done by writing all ones in the physical address bits and reading it back. Zeros read back indicate alignment requirements.

I'm not sure about the overlap with RAM. My impression is that this is valid, and that PCI devices will take precedence over RAM. However, assigning MMIO in the middle of low memory seems like a very bad idea.

Another thing to note is the type of address the device use. MMIO can be both memory and an IO port. The lower bits in the BAR will give you that information.

Re: Handling PCI BAR assignment in BIOS

Posted: Mon Nov 28, 2022 11:18 am
by Octocontrabass
rdos wrote:I'm not sure about the overlap with RAM. My impression is that this is valid, and that PCI devices will take precedence over RAM. However, assigning MMIO in the middle of low memory seems like a very bad idea.
The behavior is chipset-specific, so you can't rely on it. I expect RAM will take precedence on most chipsets, though.
rdos wrote:Another thing to note is the type of address the device use. MMIO can be both memory and an IO port. The lower bits in the BAR will give you that information.
MMIO is memory-mapped I/O. If you access it through ports, it's not memory-mapped.