OSDev.org

The Place to Start for Operating System Developers
It is currently Thu Mar 28, 2024 5:23 pm

All times are UTC - 6 hours




Post new topic Reply to topic  [ 4 posts ] 
Author Message
 Post subject: Handling PCI BAR assignment in BIOS
PostPosted: Wed Nov 16, 2022 3:28 pm 
Offline

Joined: Wed Nov 16, 2022 2:54 pm
Posts: 1
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:
./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
Quote:
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:
./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:
  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:
   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-bios-from-scratch.html):

pci_bar_setup.s
Code:
/********************************************************************************/
/*                         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:
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:
.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



Top
 Profile  
 
 Post subject: Re: Handling PCI BAR assignment in BIOS
PostPosted: Sun Nov 27, 2022 6:59 pm 
Offline
Member
Member

Joined: Mon Mar 25, 2013 7:01 pm
Posts: 5100
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.


Top
 Profile  
 
 Post subject: Re: Handling PCI BAR assignment in BIOS
PostPosted: Mon Nov 28, 2022 1:54 am 
Offline
Member
Member

Joined: Wed Oct 01, 2008 1:55 pm
Posts: 3192
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.


Top
 Profile  
 
 Post subject: Re: Handling PCI BAR assignment in BIOS
PostPosted: Mon Nov 28, 2022 11:18 am 
Offline
Member
Member

Joined: Mon Mar 25, 2013 7:01 pm
Posts: 5100
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.


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 4 posts ] 

All times are UTC - 6 hours


Who is online

Users browsing this forum: Google [Bot] and 77 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group