blob: d05278e64cd27ec435208f894c8d778e2c0c5d8f [file] [log] [blame]
/*
* Copyright (C) 2010 Michael Brown <mbrown@fensystems.co.uk>.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*
* You can also choose to distribute this program under the terms of
* the Unmodified Binary Distribution Licence (as given in the file
* COPYING.UBDL), provided that you have satisfied its requirements.
*
*/
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL )
#define PCIBIOS_READ_CONFIG_WORD 0xb109
#define PCIBIOS_READ_CONFIG_DWORD 0xb10a
#define PCIBIOS_WRITE_CONFIG_WORD 0xb10c
#define PCIBIOS_WRITE_CONFIG_DWORD 0xb10d
#define PCI_COMMAND 0x04
#define PCI_COMMAND_MEM 0x02
#define PCI_BAR_0 0x10
#define PCI_BAR_5 0x24
#define PCI_BAR_EXPROM 0x30
#define PCIR_SIGNATURE ( 'P' + ( 'C' << 8 ) + ( 'I' << 16 ) + ( 'R' << 24 ) )
#define ROMPREFIX_EXCLUDE_PAYLOAD 1
#define ROMPREFIX_MORE_IMAGES 1
#define _pcirom_start _mrom_start
#include "pciromprefix.S"
.section ".note.GNU-stack", "", @progbits
.code16
.arch i386
/* Obtain access to payload by exposing the expansion ROM BAR at the
* address currently used by a suitably large memory BAR on the same
* device. The memory BAR is temporarily disabled. Using a memory
* BAR on the same device means that we don't have to worry about the
* configuration of any intermediate PCI bridges.
*
* Parameters:
* %ds:0000 : Prefix
* %esi : Buffer for copy of image source (or zero if no buffer available)
* %ecx : Expected offset within buffer of first payload block
* Returns:
* %esi : Valid image source address (buffered or unbuffered)
* %ecx : Actual offset within buffer of first payload block
* CF set on error
*/
.section ".text16.early", "awx", @progbits
.globl open_payload
open_payload:
/* Preserve registers */
pushl %eax
pushw %bx
pushl %edx
pushl %edi
pushw %bp
pushw %es
pushw %ds
/* Retrieve bus:dev.fn from .prefix */
movw init_pci_busdevfn, %bx
/* Set up %ds for access to .text16.early */
pushw %cs
popw %ds
/* Set up %es for access to flat address space */
xorw %ax, %ax
movw %ax, %es
/* Store bus:dev.fn to .text16.early */
movw %bx, payload_pci_busdevfn
/* Get expansion ROM BAR current value */
movw $PCI_BAR_EXPROM, %di
call pci_read_bar
movl %eax, rom_bar_orig_value
/* Get expansion ROM BAR size */
call pci_size_mem_bar_low
movl %ecx, rom_bar_size
/* Find a suitable memory BAR to use */
movw $PCI_BAR_0, %di /* %di is PCI BAR register */
xorw %bp, %bp /* %bp is increment */
find_mem_bar:
/* Move to next BAR */
addw %bp, %di
cmpw $PCI_BAR_5, %di
jle 1f
stc
movl $0xbabababa, %esi /* Report "No suitable BAR" */
movl rom_bar_size, %ecx
jmp 99f
1: movw $4, %bp
/* Get BAR current value */
call pci_read_bar
/* Skip non-existent BARs */
notl %eax
testl %eax, %eax
notl %eax
jz find_mem_bar
/* Skip I/O BARs */
testb $0x01, %al
jnz find_mem_bar
/* Set increment to 8 for 64-bit BARs */
testb $0x04, %al
jz 1f
movw $8, %bp
1:
/* Skip 64-bit BARs with high dword set; we couldn't use this
* address for the (32-bit) expansion ROM BAR anyway
*/
testl %edx, %edx
jnz find_mem_bar
/* Get low dword of BAR size */
call pci_size_mem_bar_low
/* Skip BARs smaller than the expansion ROM BAR */
cmpl %ecx, rom_bar_size
ja find_mem_bar
/* We have a memory BAR with a 32-bit address that is large
* enough to use. Store BAR number and original value.
*/
movw %di, stolen_bar_register
movl %eax, stolen_bar_orig_value
/* Remove flags from BAR address */
xorb %al, %al
/* Write zero to our stolen BAR. This doesn't technically
* disable it, but it's a pretty safe bet that the PCI bridge
* won't pass through accesses to this region anyway. Note
* that the high dword (if any) must already be zero.
*/
xorl %ecx, %ecx
call pci_write_config_dword
/* Enable expansion ROM BAR at stolen BAR's address */
movl %eax, %ecx
orb $0x1, %cl
movw $PCI_BAR_EXPROM, %di
call pci_write_config_dword
/* Locate our ROM image */
1: movl $0xaa55, %ecx /* 55aa signature */
addr32 es cmpw %cx, (%eax)
jne 2f
movl $PCIR_SIGNATURE, %ecx /* PCIR signature */
addr32 es movzwl 0x18(%eax), %edx
addr32 es cmpl %ecx, (%eax,%edx)
jne 2f
addr32 es cmpl $_build_id, build_id(%eax) /* iPXE build ID */
je 3f
movl $0x80, %ecx /* Last image */
addr32 es testb %cl, 0x15(%eax,%edx)
jnz 2f
addr32 es movzwl 0x10(%eax,%edx), %ecx /* PCIR image length */
shll $9, %ecx
addl %ecx, %eax
jmp 1b
2: /* Failure */
stc
movl %eax, %esi /* Report failure address */
jmp 99f
3:
/* Copy payload to buffer, or set buffer address to BAR address */
testl %esi, %esi
jz 1f
/* We have a buffer; copy payload to it. Since .mrom is
* designed specifically for real hardware, we assume that
* flat real mode is working properly. (In the unlikely event
* that this code is run inside a hypervisor that doesn't
* properly support flat real mode, it will die horribly.)
*/
pushl %esi
movl %esi, %edi
movl %eax, %esi
addr32 es movzbl 2(%esi), %ecx
shll $7, %ecx
addr32 es movzwl mpciheader_image_length(%esi,%ecx,4), %edx
shll $7, %edx
addl %edx, %ecx
addr32 es rep movsl
popl %esi
jmp 2f
1: /* We have no buffer; set %esi to the BAR address */
movl %eax, %esi
2:
/* Locate first payload block (after the dummy ROM header) */
addr32 es movzbl 2(%esi), %ecx
shll $9, %ecx
addl $_pprefix_skip, %ecx
clc
/* Restore registers and return */
99: popw %ds
popw %es
popw %bp
popl %edi
popl %edx
popw %bx
popl %eax
lret
.size open_payload, . - open_payload
.section ".text16.early.data", "aw", @progbits
payload_pci_busdevfn:
.word 0
.size payload_pci_busdevfn, . - payload_pci_busdevfn
.section ".text16.early.data", "aw", @progbits
rom_bar_orig_value:
.long 0
.size rom_bar_orig_value, . - rom_bar_orig_value
.section ".text16.early.data", "aw", @progbits
rom_bar_size:
.long 0
.size rom_bar_size, . - rom_bar_size
.section ".text16.early.data", "aw", @progbits
stolen_bar_register:
.word 0
.size stolen_bar_register, . - stolen_bar_register
.section ".text16.early.data", "aw", @progbits
stolen_bar_orig_value:
.long 0
.size stolen_bar_orig_value, . - stolen_bar_orig_value
/* Restore original BAR values
*
* Parameters:
* none
* Returns:
* none
*/
.section ".text16.early", "awx", @progbits
.globl close_payload
close_payload:
/* Preserve registers */
pushw %bx
pushw %di
pushl %ecx
pushw %ds
/* Set up %ds for access to .text16.early */
pushw %cs
popw %ds
/* Retrieve stored bus:dev.fn */
movw payload_pci_busdevfn, %bx
/* Restore expansion ROM BAR original value */
movw $PCI_BAR_EXPROM, %di
movl rom_bar_orig_value, %ecx
call pci_write_config_dword
/* Restore stolen BAR original value */
movw stolen_bar_register, %di
movl stolen_bar_orig_value, %ecx
call pci_write_config_dword
/* Restore registers and return */
popw %ds
popl %ecx
popw %di
popw %bx
lret
.size close_payload, . - close_payload
/* Get PCI BAR value
*
* Parameters:
* %bx : PCI bus:dev.fn
* %di : PCI BAR register number
* Returns:
* %edx:%eax : PCI BAR value
*/
.section ".text16.early", "awx", @progbits
pci_read_bar:
/* Preserve registers */
pushl %ecx
pushw %di
/* Read low dword value */
call pci_read_config_dword
movl %ecx, %eax
/* Read high dword value, if applicable */
xorl %edx, %edx
andb $0x07, %cl
cmpb $0x04, %cl
jne 1f
addw $4, %di
call pci_read_config_dword
movl %ecx, %edx
1:
/* Restore registers and return */
popw %di
popl %ecx
ret
.size pci_read_bar, . - pci_read_bar
/* Get low dword of PCI memory BAR size
*
* Parameters:
* %bx : PCI bus:dev.fn
* %di : PCI BAR register number
* %eax : Low dword of current PCI BAR value
* Returns:
* %ecx : PCI BAR size
*/
.section ".text16.early", "awx", @progbits
pci_size_mem_bar_low:
/* Preserve registers */
pushw %dx
/* Disable memory accesses */
xorw %dx, %dx
call pci_set_mem_access
/* Write all ones to BAR */
xorl %ecx, %ecx
decl %ecx
call pci_write_config_dword
/* Read back BAR */
call pci_read_config_dword
/* Calculate size */
notl %ecx
orb $0x0f, %cl
incl %ecx
/* Restore original value */
pushl %ecx
movl %eax, %ecx
call pci_write_config_dword
popl %ecx
/* Enable memory accesses */
movw $PCI_COMMAND_MEM, %dx
call pci_set_mem_access
/* Restore registers and return */
popw %dx
ret
.size pci_size_mem_bar_low, . - pci_size_mem_bar_low
/* Read PCI config dword
*
* Parameters:
* %bx : PCI bus:dev.fn
* %di : PCI register number
* Returns:
* %ecx : Dword value
*/
.section ".text16.early", "awx", @progbits
pci_read_config_dword:
/* Preserve registers */
pushl %eax
pushl %ebx
pushl %edx
/* Issue INT 0x1a,b10a */
movw $PCIBIOS_READ_CONFIG_DWORD, %ax
int $0x1a
/* Restore registers and return */
popl %edx
popl %ebx
popl %eax
ret
.size pci_read_config_dword, . - pci_read_config_dword
/* Write PCI config dword
*
* Parameters:
* %bx : PCI bus:dev.fn
* %di : PCI register number
* %ecx : PCI BAR value
* Returns:
* none
*/
.section ".text16.early", "awx", @progbits
pci_write_config_dword:
/* Preserve registers */
pushal
/* Issue INT 0x1a,b10d */
movw $PCIBIOS_WRITE_CONFIG_DWORD, %ax
int $0x1a
/* Restore registers and return */
popal
ret
.size pci_write_config_dword, . - pci_write_config_dword
/* Enable/disable memory access response in PCI command word
*
* Parameters:
* %bx : PCI bus:dev.fn
* %dx : PCI_COMMAND_MEM, or zero
* Returns:
* none
*/
.section ".text16.early", "awx", @progbits
pci_set_mem_access:
/* Preserve registers */
pushal
/* Read current value of command register */
pushw %bx
pushw %dx
movw $PCI_COMMAND, %di
movw $PCIBIOS_READ_CONFIG_WORD, %ax
int $0x1a
popw %dx
popw %bx
/* Set memory access enable as appropriate */
andw $~PCI_COMMAND_MEM, %cx
orw %dx, %cx
/* Write new value of command register */
movw $PCIBIOS_WRITE_CONFIG_WORD, %ax
int $0x1a
/* Restore registers and return */
popal
ret
.size pci_set_mem_access, . - pci_set_mem_access
/* Update image source address for UNDI loader
*
* Parameters:
* %esi : Image source address
* Returns:
* %esi : Image source address
*/
.section ".prefix", "ax", @progbits
.globl undiloader_source
undiloader_source:
/* Always use expansion ROM BAR directly when installing via
* the UNDI loader entry point, since the PMM-allocated block
* may collide with whatever is calling the UNDI loader entry
* point.
*/
xorl %esi, %esi
ret
/* Payload prefix
*
* We include a dummy ROM header to cover the "hidden" portion of the
* overall ROM image.
*/
.globl _payload_align
.equ _payload_align, 512
.section ".pprefix", "ax", @progbits
.org 0x00
mromheader:
.word 0xaa55 /* BIOS extension signature */
.byte 0x01 /* Dummy size (BIOS bug workaround) */
.org 0x18
.word mpciheader
.org 0x1a
.word 0
.size mromheader, . - mromheader
.balign 4
mpciheader:
.ascii "PCIR" /* Signature */
.word pci_vendor_id /* Vendor identification */
.word pci_device_id /* Device identification */
.word 0x0000 /* Device list pointer */
.word mpciheader_len /* PCI data structure length */
.byte 0x03 /* PCI data structure revision */
.byte 0x00, 0x00, 0x02 /* Class code */
mpciheader_image_length:
.word 0 /* Image length */
.word 0x0001 /* Revision level */
.byte 0xff /* Code type */
.byte 0x80 /* Last image indicator */
mpciheader_runtime_length:
.word 0 /* Maximum run-time image length */
.word 0x0000 /* Configuration utility code header */
.word 0x0000 /* DMTF CLP entry point */
.equ mpciheader_len, . - mpciheader
.size mpciheader, . - mpciheader
.section ".zinfo.fixup", "a", @progbits /* Compressor fixups */
.ascii "APPW"
.long mpciheader_image_length
.long 512
.long 0
.ascii "APPW"
.long mpciheader_runtime_length
.long 512
.long 0
.previous
/* Fix up additional image source size
*
*/
.section ".zinfo.fixup", "a", @progbits /* Compressor fixups */
.ascii "ADPW"
.long extra_size
.long 512
.long 0
.previous