| /* |
| * 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 |