diff options
Diffstat (limited to 'qemu/roms/ipxe/src/drivers/bus')
-rw-r--r-- | qemu/roms/ipxe/src/drivers/bus/eisa.c | 182 | ||||
-rw-r--r-- | qemu/roms/ipxe/src/drivers/bus/isa.c | 173 | ||||
-rw-r--r-- | qemu/roms/ipxe/src/drivers/bus/isa_ids.c | 26 | ||||
-rw-r--r-- | qemu/roms/ipxe/src/drivers/bus/isapnp.c | 756 | ||||
-rw-r--r-- | qemu/roms/ipxe/src/drivers/bus/mca.c | 177 | ||||
-rw-r--r-- | qemu/roms/ipxe/src/drivers/bus/pci.c | 392 | ||||
-rw-r--r-- | qemu/roms/ipxe/src/drivers/bus/pci_settings.c | 124 | ||||
-rw-r--r-- | qemu/roms/ipxe/src/drivers/bus/pcibackup.c | 91 | ||||
-rw-r--r-- | qemu/roms/ipxe/src/drivers/bus/pciextra.c | 86 | ||||
-rw-r--r-- | qemu/roms/ipxe/src/drivers/bus/pcivpd.c | 556 | ||||
-rw-r--r-- | qemu/roms/ipxe/src/drivers/bus/virtio-pci.c | 64 | ||||
-rw-r--r-- | qemu/roms/ipxe/src/drivers/bus/virtio-ring.c | 136 |
12 files changed, 2763 insertions, 0 deletions
diff --git a/qemu/roms/ipxe/src/drivers/bus/eisa.c b/qemu/roms/ipxe/src/drivers/bus/eisa.c new file mode 100644 index 000000000..a4efe2621 --- /dev/null +++ b/qemu/roms/ipxe/src/drivers/bus/eisa.c @@ -0,0 +1,182 @@ +#include <stdint.h> +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <ipxe/io.h> +#include <unistd.h> +#include <ipxe/eisa.h> + +FILE_LICENCE ( GPL2_OR_LATER ); + +static void eisabus_remove ( struct root_device *rootdev ); + +/** + * Reset and enable/disable an EISA device + * + * @v eisa EISA device + * @v enabled 1=enable, 0=disable + */ +void eisa_device_enabled ( struct eisa_device *eisa, int enabled ) { + /* Set reset line high for 1000 µs. Spec says 500 µs, but + * this doesn't work for all cards, so we are conservative. + */ + outb ( EISA_CMD_RESET, eisa->ioaddr + EISA_GLOBAL_CONFIG ); + udelay ( 1000 ); /* Must wait 800 */ + + /* Set reset low and write a 1 to ENABLE. Delay again, in + * case the card takes a while to wake up. + */ + outb ( enabled ? EISA_CMD_ENABLE : 0, + eisa->ioaddr + EISA_GLOBAL_CONFIG ); + udelay ( 1000 ); /* Must wait 800 */ + + DBG ( "EISA %s device %02x\n", ( enabled ? "enabled" : "disabled" ), + eisa->slot ); +} + +/** + * Probe an EISA device + * + * @v eisa EISA device + * @ret rc Return status code + * + * Searches for a driver for the EISA device. If a driver is found, + * its probe() routine is called. + */ +static int eisa_probe ( struct eisa_device *eisa ) { + struct eisa_driver *driver; + struct eisa_device_id *id; + unsigned int i; + int rc; + + DBG ( "Adding EISA device %02x (%04x:%04x (\"%s\") io %x)\n", + eisa->slot, eisa->vendor_id, eisa->prod_id, + isa_id_string ( eisa->vendor_id, eisa->prod_id ), eisa->ioaddr ); + + for_each_table_entry ( driver, EISA_DRIVERS ) { + for ( i = 0 ; i < driver->id_count ; i++ ) { + id = &driver->ids[i]; + if ( id->vendor_id != eisa->vendor_id ) + continue; + if ( ISA_PROD_ID ( id->prod_id ) != + ISA_PROD_ID ( eisa->prod_id ) ) + continue; + eisa->driver = driver; + eisa->dev.driver_name = id->name; + DBG ( "...using driver %s\n", eisa->dev.driver_name ); + if ( ( rc = driver->probe ( eisa, id ) ) != 0 ) { + DBG ( "......probe failed\n" ); + continue; + } + return 0; + } + } + + DBG ( "...no driver found\n" ); + return -ENOTTY; +} + +/** + * Remove an EISA device + * + * @v eisa EISA device + */ +static void eisa_remove ( struct eisa_device *eisa ) { + eisa->driver->remove ( eisa ); + DBG ( "Removed EISA device %02x\n", eisa->slot ); +} + +/** + * Probe EISA root bus + * + * @v rootdev EISA bus root device + * + * Scans the EISA bus for devices and registers all devices it can + * find. + */ +static int eisabus_probe ( struct root_device *rootdev ) { + struct eisa_device *eisa = NULL; + unsigned int slot; + int rc; + + for ( slot = EISA_MIN_SLOT ; slot <= EISA_MAX_SLOT ; slot++ ) { + /* Allocate struct eisa_device */ + if ( ! eisa ) + eisa = malloc ( sizeof ( *eisa ) ); + if ( ! eisa ) { + rc = -ENOMEM; + goto err; + } + memset ( eisa, 0, sizeof ( *eisa ) ); + eisa->slot = slot; + eisa->ioaddr = EISA_SLOT_BASE ( eisa->slot ); + + /* Test for board present */ + outb ( 0xff, eisa->ioaddr + EISA_VENDOR_ID ); + eisa->vendor_id = + le16_to_cpu ( inw ( eisa->ioaddr + EISA_VENDOR_ID ) ); + eisa->prod_id = + le16_to_cpu ( inw ( eisa->ioaddr + EISA_PROD_ID ) ); + if ( eisa->vendor_id & 0x80 ) { + /* No board present */ + continue; + } + + /* Add to device hierarchy */ + snprintf ( eisa->dev.name, sizeof ( eisa->dev.name ), + "EISA%02x", slot ); + eisa->dev.desc.bus_type = BUS_TYPE_EISA; + eisa->dev.desc.vendor = eisa->vendor_id; + eisa->dev.desc.device = eisa->prod_id; + eisa->dev.parent = &rootdev->dev; + list_add ( &eisa->dev.siblings, &rootdev->dev.children ); + INIT_LIST_HEAD ( &eisa->dev.children ); + + /* Look for a driver */ + if ( eisa_probe ( eisa ) == 0 ) { + /* eisadev registered, we can drop our ref */ + eisa = NULL; + } else { + /* Not registered; re-use struct */ + list_del ( &eisa->dev.siblings ); + } + } + + free ( eisa ); + return 0; + + err: + free ( eisa ); + eisabus_remove ( rootdev ); + return rc; +} + +/** + * Remove EISA root bus + * + * @v rootdev EISA bus root device + */ +static void eisabus_remove ( struct root_device *rootdev ) { + struct eisa_device *eisa; + struct eisa_device *tmp; + + list_for_each_entry_safe ( eisa, tmp, &rootdev->dev.children, + dev.siblings ) { + eisa_remove ( eisa ); + list_del ( &eisa->dev.siblings ); + free ( eisa ); + } +} + +/** EISA bus root device driver */ +static struct root_driver eisa_root_driver = { + .probe = eisabus_probe, + .remove = eisabus_remove, +}; + +/** EISA bus root device */ +struct root_device eisa_root_device __root_device = { + .dev = { .name = "EISA" }, + .driver = &eisa_root_driver, +}; diff --git a/qemu/roms/ipxe/src/drivers/bus/isa.c b/qemu/roms/ipxe/src/drivers/bus/isa.c new file mode 100644 index 000000000..da0c43c60 --- /dev/null +++ b/qemu/roms/ipxe/src/drivers/bus/isa.c @@ -0,0 +1,173 @@ +#include <stdint.h> +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <ipxe/io.h> +#include <ipxe/isa.h> + +FILE_LICENCE ( GPL2_OR_LATER ); + +/* + * isa.c implements a "classical" port-scanning method of ISA device + * detection. The driver must provide a list of probe addresses + * (probe_addrs), together with a function (probe_addr) that can be + * used to test for the physical presence of a device at any given + * address. + * + * Note that this should probably be considered the "last resort" for + * device probing. If the card supports ISAPnP or EISA, use that + * instead. Some cards (e.g. the 3c509) implement a proprietary + * ISAPnP-like mechanism. + * + * The ISA probe address list can be overridden by config.h; if the + * user specifies ISA_PROBE_ADDRS then that list will be used first. + * (If ISA_PROBE_ONLY is defined, the driver's own list will never be + * used). + */ + +/* + * User-supplied probe address list + * + */ +static isa_probe_addr_t isa_extra_probe_addrs[] = { +#ifdef ISA_PROBE_ADDRS + ISA_PROBE_ADDRS +#endif +}; +#define ISA_EXTRA_PROBE_ADDR_COUNT \ + ( sizeof ( isa_extra_probe_addrs ) / sizeof ( isa_extra_probe_addrs[0] ) ) + +#define ISA_IOIDX_MIN( driver ) ( -ISA_EXTRA_PROBE_ADDR_COUNT ) +#ifdef ISA_PROBE_ONLY +#define ISA_IOIDX_MAX( driver ) ( -1 ) +#else +#define ISA_IOIDX_MAX( driver ) ( (int) (driver)->addr_count - 1 ) +#endif + +#define ISA_IOADDR( driver, ioidx ) \ + ( ( (ioidx) >= 0 ) ? \ + (driver)->probe_addrs[(ioidx)] : \ + *( isa_extra_probe_addrs + (ioidx) + ISA_EXTRA_PROBE_ADDR_COUNT ) ) + +static void isabus_remove ( struct root_device *rootdev ); + +/** + * Probe an ISA device + * + * @v isa ISA device + * @ret rc Return status code + */ +static int isa_probe ( struct isa_device *isa ) { + int rc; + + DBG ( "Trying ISA driver %s at I/O %04x\n", + isa->driver->name, isa->ioaddr ); + + if ( ( rc = isa->driver->probe ( isa ) ) != 0 ) { + DBG ( "...probe failed\n" ); + return rc; + } + + DBG ( "...device found\n" ); + return 0; +} + +/** + * Remove an ISA device + * + * @v isa ISA device + */ +static void isa_remove ( struct isa_device *isa ) { + isa->driver->remove ( isa ); + DBG ( "Removed ISA%04x\n", isa->ioaddr ); +} + +/** + * Probe ISA root bus + * + * @v rootdev ISA bus root device + * + * Scans the ISA bus for devices and registers all devices it can + * find. + */ +static int isabus_probe ( struct root_device *rootdev ) { + struct isa_device *isa = NULL; + struct isa_driver *driver; + int ioidx; + int rc; + + for_each_table_entry ( driver, ISA_DRIVERS ) { + for ( ioidx = ISA_IOIDX_MIN ( driver ) ; + ioidx <= ISA_IOIDX_MAX ( driver ) ; ioidx++ ) { + /* Allocate struct isa_device */ + if ( ! isa ) + isa = malloc ( sizeof ( *isa ) ); + if ( ! isa ) { + rc = -ENOMEM; + goto err; + } + memset ( isa, 0, sizeof ( *isa ) ); + isa->driver = driver; + isa->ioaddr = ISA_IOADDR ( driver, ioidx ); + + /* Add to device hierarchy */ + snprintf ( isa->dev.name, sizeof ( isa->dev.name ), + "ISA%04x", isa->ioaddr ); + isa->dev.driver_name = driver->name; + isa->dev.desc.bus_type = BUS_TYPE_ISA; + isa->dev.desc.vendor = driver->vendor_id; + isa->dev.desc.device = driver->prod_id; + isa->dev.parent = &rootdev->dev; + list_add ( &isa->dev.siblings, + &rootdev->dev.children ); + INIT_LIST_HEAD ( &isa->dev.children ); + + /* Try probing at this I/O address */ + if ( isa_probe ( isa ) == 0 ) { + /* isadev registered, we can drop our ref */ + isa = NULL; + } else { + /* Not registered; re-use struct */ + list_del ( &isa->dev.siblings ); + } + } + } + + free ( isa ); + return 0; + + err: + free ( isa ); + isabus_remove ( rootdev ); + return rc; +} + +/** + * Remove ISA root bus + * + * @v rootdev ISA bus root device + */ +static void isabus_remove ( struct root_device *rootdev ) { + struct isa_device *isa; + struct isa_device *tmp; + + list_for_each_entry_safe ( isa, tmp, &rootdev->dev.children, + dev.siblings ) { + isa_remove ( isa ); + list_del ( &isa->dev.siblings ); + free ( isa ); + } +} + +/** ISA bus root device driver */ +static struct root_driver isa_root_driver = { + .probe = isabus_probe, + .remove = isabus_remove, +}; + +/** ISA bus root device */ +struct root_device isa_root_device __root_device = { + .dev = { .name = "ISA" }, + .driver = &isa_root_driver, +}; diff --git a/qemu/roms/ipxe/src/drivers/bus/isa_ids.c b/qemu/roms/ipxe/src/drivers/bus/isa_ids.c new file mode 100644 index 000000000..e72b233f3 --- /dev/null +++ b/qemu/roms/ipxe/src/drivers/bus/isa_ids.c @@ -0,0 +1,26 @@ +#include <stdint.h> +#include <stdio.h> +#include <byteswap.h> +#include <ipxe/isa_ids.h> + +/* + * EISA and ISAPnP IDs are actually mildly human readable, though in a + * somewhat brain-damaged way. + * + */ +char * isa_id_string ( unsigned int vendor, unsigned int product ) { + static char buf[7]; + int i; + + /* Vendor ID is a compressed ASCII string */ + vendor = bswap_16 ( vendor ); + for ( i = 2 ; i >= 0 ; i-- ) { + buf[i] = ( 'A' - 1 + ( vendor & 0x1f ) ); + vendor >>= 5; + } + + /* Product ID is a 4-digit hex string */ + sprintf ( &buf[3], "%04x", bswap_16 ( product ) ); + + return buf; +} diff --git a/qemu/roms/ipxe/src/drivers/bus/isapnp.c b/qemu/roms/ipxe/src/drivers/bus/isapnp.c new file mode 100644 index 000000000..6417c74a6 --- /dev/null +++ b/qemu/roms/ipxe/src/drivers/bus/isapnp.c @@ -0,0 +1,756 @@ +/************************************************************************** +* +* isapnp.c -- Etherboot isapnp support for the 3Com 3c515 +* Written 2002-2003 by Timothy Legge <tlegge@rogers.com> +* +* 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 +* (at your option) 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. +* +* Portions of this code: +* Copyright (C) 2001 P.J.H.Fox (fox@roestock.demon.co.uk) +* +* +* REVISION HISTORY: +* ================ +* Version 0.1 April 26, 2002 TJL +* Version 0.2 01/08/2003 TJL Moved outside the 3c515.c driver file +* Version 0.3 Sept 23, 2003 timlegge Change delay to currticks +* +* +* Generalised into an ISAPnP bus that can be used by more than just +* the 3c515 by Michael Brown <mbrown@fensystems.co.uk> +* +***************************************************************************/ + +/** @file + * + * ISAPnP bus support + * + * Etherboot orignally gained ISAPnP support in a very limited way for + * the 3c515 NIC. The current implementation is almost a complete + * rewrite based on the ISAPnP specification, with passing reference + * to the Linux ISAPnP code. + * + * There can be only one ISAPnP bus in a system. Once the read port + * is known and all cards have been allocated CSNs, there's nothing to + * be gained by re-scanning for cards. + * + * External code (e.g. the ISAPnP ROM prefix) may already know the + * read port address, in which case it can store it in + * #isapnp_read_port. Note that setting the read port address in this + * way will prevent further isolation from taking place; you should + * set the read port address only if you know that devices have + * already been allocated CSNs. + * + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <errno.h> +#include <ipxe/io.h> +#include <unistd.h> +#include <ipxe/isapnp.h> + +/** + * ISAPnP Read Port address. + * + * ROM prefix may be able to set this address, which is why this is + * non-static. + */ +uint16_t isapnp_read_port; + +static void isapnpbus_remove ( struct root_device *rootdev ); + +/* + * ISAPnP utility functions + * + */ + +#define ISAPNP_CARD_ID_FMT "ID %04x:%04x (\"%s\") serial %x" +#define ISAPNP_CARD_ID_DATA(identifier) \ + (identifier)->vendor_id, (identifier)->prod_id, \ + isa_id_string ( (identifier)->vendor_id, (identifier)->prod_id ), \ + (identifier)->serial +#define ISAPNP_DEV_ID_FMT "ID %04x:%04x (\"%s\")" +#define ISAPNP_DEV_ID_DATA(isapnp) \ + (isapnp)->vendor_id, (isapnp)->prod_id, \ + isa_id_string ( (isapnp)->vendor_id, (isapnp)->prod_id ) + +static inline void isapnp_write_address ( unsigned int address ) { + outb ( address, ISAPNP_ADDRESS ); +} + +static inline void isapnp_write_data ( unsigned int data ) { + outb ( data, ISAPNP_WRITE_DATA ); +} + +static inline unsigned int isapnp_read_data ( void ) { + return inb ( isapnp_read_port ); +} + +static inline void isapnp_write_byte ( unsigned int address, + unsigned int value ) { + isapnp_write_address ( address ); + isapnp_write_data ( value ); +} + +static inline unsigned int isapnp_read_byte ( unsigned int address ) { + isapnp_write_address ( address ); + return isapnp_read_data (); +} + +static inline unsigned int isapnp_read_word ( unsigned int address ) { + /* Yes, they're in big-endian order */ + return ( ( isapnp_read_byte ( address ) << 8 ) + | isapnp_read_byte ( address + 1 ) ); +} + +/** Inform cards of a new read port address */ +static inline void isapnp_set_read_port ( void ) { + isapnp_write_byte ( ISAPNP_READPORT, ( isapnp_read_port >> 2 ) ); +} + +/** + * Enter the Isolation state. + * + * Only cards currently in the Sleep state will respond to this + * command. + */ +static inline void isapnp_serialisolation ( void ) { + isapnp_write_address ( ISAPNP_SERIALISOLATION ); +} + +/** + * Enter the Wait for Key state. + * + * All cards will respond to this command, regardless of their current + * state. + */ +static inline void isapnp_wait_for_key ( void ) { + isapnp_write_byte ( ISAPNP_CONFIGCONTROL, ISAPNP_CONFIG_WAIT_FOR_KEY ); +} + +/** + * Reset (i.e. remove) Card Select Number. + * + * Only cards currently in the Sleep state will respond to this + * command. + */ +static inline void isapnp_reset_csn ( void ) { + isapnp_write_byte ( ISAPNP_CONFIGCONTROL, ISAPNP_CONFIG_RESET_CSN ); +} + +/** + * Place a specified card into the Config state. + * + * @v csn Card Select Number + * @ret None - + * @err None - + * + * Only cards currently in the Sleep, Isolation, or Config states will + * respond to this command. The card that has the specified CSN will + * enter the Config state, all other cards will enter the Sleep state. + */ +static inline void isapnp_wake ( uint8_t csn ) { + isapnp_write_byte ( ISAPNP_WAKE, csn ); +} + +static inline unsigned int isapnp_read_resourcedata ( void ) { + return isapnp_read_byte ( ISAPNP_RESOURCEDATA ); +} + +static inline unsigned int isapnp_read_status ( void ) { + return isapnp_read_byte ( ISAPNP_STATUS ); +} + +/** + * Assign a Card Select Number to a card, and enter the Config state. + * + * @v csn Card Select Number + * + * Only cards in the Isolation state will respond to this command. + * The isolation protocol is designed so that only one card will + * remain in the Isolation state by the time the isolation protocol + * completes. + */ +static inline void isapnp_write_csn ( unsigned int csn ) { + isapnp_write_byte ( ISAPNP_CARDSELECTNUMBER, csn ); +} + +static inline void isapnp_logicaldevice ( unsigned int logdev ) { + isapnp_write_byte ( ISAPNP_LOGICALDEVICENUMBER, logdev ); +} + +static inline void isapnp_activate ( unsigned int logdev ) { + isapnp_logicaldevice ( logdev ); + isapnp_write_byte ( ISAPNP_ACTIVATE, 1 ); +} + +static inline void isapnp_deactivate ( unsigned int logdev ) { + isapnp_logicaldevice ( logdev ); + isapnp_write_byte ( ISAPNP_ACTIVATE, 0 ); +} + +static inline unsigned int isapnp_read_iobase ( unsigned int index ) { + return isapnp_read_word ( ISAPNP_IOBASE ( index ) ); +} + +static inline unsigned int isapnp_read_irqno ( unsigned int index ) { + return isapnp_read_byte ( ISAPNP_IRQNO ( index ) ); +} + +static void isapnp_delay ( void ) { + udelay ( 1000 ); +} + +/** + * Linear feedback shift register. + * + * @v lfsr Current value of the LFSR + * @v input_bit Current input bit to the LFSR + * @ret lfsr Next value of the LFSR + * + * This routine implements the linear feedback shift register as + * described in Appendix B of the PnP ISA spec. The hardware + * implementation uses eight D-type latches and two XOR gates. I + * think this is probably the smallest possible implementation in + * software. Six instructions when input_bit is a constant 0 (for + * isapnp_send_key). :) + */ +static inline unsigned int isapnp_lfsr_next ( unsigned int lfsr, + unsigned int input_bit ) { + register uint8_t lfsr_next; + + lfsr_next = lfsr >> 1; + lfsr_next |= ( ( ( lfsr ^ lfsr_next ) ^ input_bit ) ) << 7; + return lfsr_next; +} + +/** + * Send the ISAPnP initiation key. + * + * Sending the key causes all ISAPnP cards that are currently in the + * Wait for Key state to transition into the Sleep state. + */ +static void isapnp_send_key ( void ) { + unsigned int i; + unsigned int lfsr; + + isapnp_delay(); + isapnp_write_address ( 0x00 ); + isapnp_write_address ( 0x00 ); + + lfsr = ISAPNP_LFSR_SEED; + for ( i = 0 ; i < 32 ; i++ ) { + isapnp_write_address ( lfsr ); + lfsr = isapnp_lfsr_next ( lfsr, 0 ); + } +} + +/** + * Compute ISAPnP identifier checksum + * + * @v identifier ISAPnP identifier + * @ret checksum Expected checksum value + */ +static unsigned int isapnp_checksum ( struct isapnp_identifier *identifier ) { + unsigned int i, j; + unsigned int lfsr; + unsigned int byte; + + lfsr = ISAPNP_LFSR_SEED; + for ( i = 0 ; i < 8 ; i++ ) { + byte = * ( ( ( uint8_t * ) identifier ) + i ); + for ( j = 0 ; j < 8 ; j++ ) { + lfsr = isapnp_lfsr_next ( lfsr, byte ); + byte >>= 1; + } + } + return lfsr; +} + +/* + * Read a byte of resource data from the current location + * + * @ret byte Byte of resource data + */ +static inline unsigned int isapnp_peek_byte ( void ) { + unsigned int i; + + /* Wait for data to be ready */ + for ( i = 0 ; i < 20 ; i++ ) { + if ( isapnp_read_status() & 0x01 ) { + /* Byte ready - read it */ + return isapnp_read_resourcedata(); + } + isapnp_delay(); + } + /* Data never became ready - return 0xff */ + return 0xff; +} + +/** + * Read resource data. + * + * @v buf Buffer in which to store data, or NULL + * @v bytes Number of bytes to read + * + * Resource data is read from the current location. If #buf is NULL, + * the data is discarded. + */ +static void isapnp_peek ( void *buf, size_t len ) { + unsigned int i; + unsigned int byte; + + for ( i = 0 ; i < len ; i++) { + byte = isapnp_peek_byte(); + if ( buf ) + * ( ( uint8_t * ) buf + i ) = byte; + } +} + +/** + * Find a tag within the resource data. + * + * @v wanted_tag The tag that we're looking for + * @v buf Buffer in which to store the tag's contents + * @v len Length of buffer + * @ret rc Return status code + * + * Scan through the resource data until we find a particular tag, and + * read its contents into a buffer. + */ +static int isapnp_find_tag ( unsigned int wanted_tag, void *buf, size_t len ) { + unsigned int tag; + unsigned int tag_len; + + DBG2 ( "ISAPnP read tag" ); + do { + tag = isapnp_peek_byte(); + if ( ISAPNP_IS_SMALL_TAG ( tag ) ) { + tag_len = ISAPNP_SMALL_TAG_LEN ( tag ); + tag = ISAPNP_SMALL_TAG_NAME ( tag ); + } else { + tag_len = ( isapnp_peek_byte() + + ( isapnp_peek_byte() << 8 ) ); + tag = ISAPNP_LARGE_TAG_NAME ( tag ); + } + DBG2 ( " %02x (%02x)", tag, tag_len ); + if ( tag == wanted_tag ) { + if ( len > tag_len ) + len = tag_len; + isapnp_peek ( buf, len ); + DBG2 ( "\n" ); + return 0; + } else { + isapnp_peek ( NULL, tag_len ); + } + } while ( tag != ISAPNP_TAG_END ); + DBG2 ( "\n" ); + return -ENOENT; +} + +/** + * Find specified Logical Device ID tag + * + * @v logdev Logical device ID + * @v logdevid Logical device ID structure to fill in + * @ret rc Return status code + */ +static int isapnp_find_logdevid ( unsigned int logdev, + struct isapnp_logdevid *logdevid ) { + unsigned int i; + int rc; + + for ( i = 0 ; i <= logdev ; i++ ) { + if ( ( rc = isapnp_find_tag ( ISAPNP_TAG_LOGDEVID, logdevid, + sizeof ( *logdevid ) ) ) != 0 ) + return rc; + } + return 0; +} + +/** + * Try isolating ISAPnP cards at the current read port. + * + * @ret \>0 Number of ISAPnP cards found + * @ret 0 There are no ISAPnP cards in the system + * @ret \<0 A conflict was detected; try a new read port + * @err None - + * + * The state diagram on page 18 (PDF page 24) of the PnP ISA spec + * gives the best overview of what happens here. + */ +static int isapnp_try_isolate ( void ) { + struct isapnp_identifier identifier; + unsigned int i, j; + unsigned int seen_55aa, seen_life; + unsigned int csn = 0; + unsigned int data; + unsigned int byte; + + DBG ( "ISAPnP attempting isolation at read port %04x\n", + isapnp_read_port ); + + /* Place all cards into the Sleep state, whatever state + * they're currently in. + */ + isapnp_wait_for_key(); + isapnp_send_key(); + + /* Reset all assigned CSNs */ + isapnp_reset_csn(); + isapnp_delay(); + isapnp_delay(); + + /* Place all cards into the Isolation state */ + isapnp_wait_for_key (); + isapnp_send_key(); + isapnp_wake ( 0x00 ); + + /* Set the read port */ + isapnp_set_read_port(); + isapnp_delay(); + + while ( 1 ) { + + /* All cards that do not have assigned CSNs are + * currently in the Isolation state, each time we go + * through this loop. + */ + + /* Initiate serial isolation */ + isapnp_serialisolation(); + isapnp_delay(); + + /* Read identifier serially via the ISAPnP read port. */ + memset ( &identifier, 0, sizeof ( identifier ) ); + seen_55aa = seen_life = 0; + for ( i = 0 ; i < 9 ; i++ ) { + byte = 0; + for ( j = 0 ; j < 8 ; j++ ) { + data = isapnp_read_data(); + isapnp_delay(); + data = ( ( data << 8 ) | isapnp_read_data() ); + isapnp_delay(); + byte >>= 1; + if ( data != 0xffff ) { + seen_life++; + if ( data == 0x55aa ) { + byte |= 0x80; + seen_55aa++; + } + } + } + *( ( ( uint8_t * ) &identifier ) + i ) = byte; + } + + /* If we didn't see any 55aa patterns, stop here */ + if ( ! seen_55aa ) { + if ( csn ) { + DBG ( "ISAPnP found no more cards\n" ); + } else { + if ( seen_life ) { + DBG ( "ISAPnP saw life but no cards, " + "trying new read port\n" ); + csn = -1; + } else { + DBG ( "ISAPnP saw no signs of life, " + "abandoning isolation\n" ); + } + } + break; + } + + /* If the checksum was invalid stop here */ + if ( identifier.checksum != isapnp_checksum ( &identifier) ) { + DBG ( "ISAPnP found malformed card " + ISAPNP_CARD_ID_FMT "\n with checksum %02x " + "(should be %02x), trying new read port\n", + ISAPNP_CARD_ID_DATA ( &identifier ), + identifier.checksum, + isapnp_checksum ( &identifier) ); + csn = -1; + break; + } + + /* Give the device a CSN */ + csn++; + DBG ( "ISAPnP found card " ISAPNP_CARD_ID_FMT + ", assigning CSN %02x\n", + ISAPNP_CARD_ID_DATA ( &identifier ), csn ); + + isapnp_write_csn ( csn ); + isapnp_delay(); + + /* Send this card back to Sleep and force all cards + * without a CSN into Isolation state + */ + isapnp_wake ( 0x00 ); + isapnp_delay(); + } + + /* Place all cards in Wait for Key state */ + isapnp_wait_for_key(); + + /* Return number of cards found */ + if ( csn > 0 ) { + DBG ( "ISAPnP found %d cards at read port %04x\n", + csn, isapnp_read_port ); + } + return csn; +} + +/** + * Find a valid read port and isolate all ISAPnP cards. + * + */ +static void isapnp_isolate ( void ) { + for ( isapnp_read_port = ISAPNP_READ_PORT_START ; + isapnp_read_port <= ISAPNP_READ_PORT_MAX ; + isapnp_read_port += ISAPNP_READ_PORT_STEP ) { + /* Avoid problematic locations such as the NE2000 + * probe space + */ + if ( ( isapnp_read_port >= 0x280 ) && + ( isapnp_read_port <= 0x380 ) ) + continue; + + /* If we detect any ISAPnP cards at this location, stop */ + if ( isapnp_try_isolate() >= 0 ) + return; + } +} + +/** + * Activate or deactivate an ISAPnP device. + * + * @v isapnp ISAPnP device + * @v activation True to enable, False to disable the device + * @ret None - + * @err None - + * + * This routine simply activates the device in its current + * configuration, or deactivates the device. It does not attempt any + * kind of resource arbitration. + * + */ +void isapnp_device_activation ( struct isapnp_device *isapnp, + int activation ) { + /* Wake the card and select the logical device */ + isapnp_wait_for_key (); + isapnp_send_key (); + isapnp_wake ( isapnp->csn ); + isapnp_logicaldevice ( isapnp->logdev ); + + /* Activate/deactivate the logical device */ + isapnp_activate ( activation ); + isapnp_delay(); + + /* Return all cards to Wait for Key state */ + isapnp_wait_for_key (); + + DBG ( "ISAPnP %s device %02x:%02x\n", + ( activation ? "activated" : "deactivated" ), + isapnp->csn, isapnp->logdev ); +} + +/** + * Probe an ISAPnP device + * + * @v isapnp ISAPnP device + * @ret rc Return status code + * + * Searches for a driver for the ISAPnP device. If a driver is found, + * its probe() routine is called. + */ +static int isapnp_probe ( struct isapnp_device *isapnp ) { + struct isapnp_driver *driver; + struct isapnp_device_id *id; + unsigned int i; + int rc; + + DBG ( "Adding ISAPnP device %02x:%02x (%04x:%04x (\"%s\") " + "io %x irq %d)\n", isapnp->csn, isapnp->logdev, + isapnp->vendor_id, isapnp->prod_id, + isa_id_string ( isapnp->vendor_id, isapnp->prod_id ), + isapnp->ioaddr, isapnp->irqno ); + + for_each_table_entry ( driver, ISAPNP_DRIVERS ) { + for ( i = 0 ; i < driver->id_count ; i++ ) { + id = &driver->ids[i]; + if ( id->vendor_id != isapnp->vendor_id ) + continue; + if ( ISA_PROD_ID ( id->prod_id ) != + ISA_PROD_ID ( isapnp->prod_id ) ) + continue; + isapnp->driver = driver; + isapnp->dev.driver_name = id->name; + DBG ( "...using driver %s\n", isapnp->dev.driver_name ); + if ( ( rc = driver->probe ( isapnp, id ) ) != 0 ) { + DBG ( "......probe failed\n" ); + continue; + } + return 0; + } + } + + DBG ( "...no driver found\n" ); + return -ENOTTY; +} + +/** + * Remove an ISAPnP device + * + * @v isapnp ISAPnP device + */ +static void isapnp_remove ( struct isapnp_device *isapnp ) { + isapnp->driver->remove ( isapnp ); + DBG ( "Removed ISAPnP device %02x:%02x\n", + isapnp->csn, isapnp->logdev ); +} + +/** + * Probe ISAPnP root bus + * + * @v rootdev ISAPnP bus root device + * + * Scans the ISAPnP bus for devices and registers all devices it can + * find. + */ +static int isapnpbus_probe ( struct root_device *rootdev ) { + struct isapnp_device *isapnp = NULL; + struct isapnp_identifier identifier; + struct isapnp_logdevid logdevid; + unsigned int csn; + unsigned int logdev; + int rc; + + /* Perform isolation if it hasn't yet been done */ + if ( ! isapnp_read_port ) + isapnp_isolate(); + + for ( csn = 1 ; csn <= 0xff ; csn++ ) { + for ( logdev = 0 ; logdev <= 0xff ; logdev++ ) { + + /* Allocate struct isapnp_device */ + if ( ! isapnp ) + isapnp = malloc ( sizeof ( *isapnp ) ); + if ( ! isapnp ) { + rc = -ENOMEM; + goto err; + } + memset ( isapnp, 0, sizeof ( *isapnp ) ); + isapnp->csn = csn; + isapnp->logdev = logdev; + + /* Wake the card */ + isapnp_wait_for_key(); + isapnp_send_key(); + isapnp_wake ( csn ); + + /* Read the card identifier */ + isapnp_peek ( &identifier, sizeof ( identifier ) ); + + /* No card with this CSN; stop here */ + if ( identifier.vendor_id & 0x80 ) + goto done; + + /* Find the Logical Device ID tag */ + if ( ( rc = isapnp_find_logdevid ( logdev, + &logdevid ) ) != 0){ + /* No more logical devices; go to next CSN */ + break; + } + + /* Select the logical device */ + isapnp_logicaldevice ( logdev ); + + /* Populate struct isapnp_device */ + isapnp->vendor_id = logdevid.vendor_id; + isapnp->prod_id = logdevid.prod_id; + isapnp->ioaddr = isapnp_read_iobase ( 0 ); + isapnp->irqno = isapnp_read_irqno ( 0 ); + + /* Return all cards to Wait for Key state */ + isapnp_wait_for_key(); + + /* Add to device hierarchy */ + snprintf ( isapnp->dev.name, + sizeof ( isapnp->dev.name ), + "ISAPnP%02x:%02x", csn, logdev ); + isapnp->dev.desc.bus_type = BUS_TYPE_ISAPNP; + isapnp->dev.desc.vendor = isapnp->vendor_id; + isapnp->dev.desc.device = isapnp->prod_id; + isapnp->dev.desc.ioaddr = isapnp->ioaddr; + isapnp->dev.desc.irq = isapnp->irqno; + isapnp->dev.parent = &rootdev->dev; + list_add ( &isapnp->dev.siblings, + &rootdev->dev.children ); + INIT_LIST_HEAD ( &isapnp->dev.children ); + + /* Look for a driver */ + if ( isapnp_probe ( isapnp ) == 0 ) { + /* isapnpdev registered, we can drop our ref */ + isapnp = NULL; + } else { + /* Not registered; re-use struct */ + list_del ( &isapnp->dev.siblings ); + } + } + } + + done: + free ( isapnp ); + return 0; + + err: + free ( isapnp ); + isapnpbus_remove ( rootdev ); + return rc; +} + +/** + * Remove ISAPnP root bus + * + * @v rootdev ISAPnP bus root device + */ +static void isapnpbus_remove ( struct root_device *rootdev ) { + struct isapnp_device *isapnp; + struct isapnp_device *tmp; + + list_for_each_entry_safe ( isapnp, tmp, &rootdev->dev.children, + dev.siblings ) { + isapnp_remove ( isapnp ); + list_del ( &isapnp->dev.siblings ); + free ( isapnp ); + } +} + +/** ISAPnP bus root device driver */ +static struct root_driver isapnp_root_driver = { + .probe = isapnpbus_probe, + .remove = isapnpbus_remove, +}; + +/** ISAPnP bus root device */ +struct root_device isapnp_root_device __root_device = { + .dev = { .name = "ISAPnP" }, + .driver = &isapnp_root_driver, +}; diff --git a/qemu/roms/ipxe/src/drivers/bus/mca.c b/qemu/roms/ipxe/src/drivers/bus/mca.c new file mode 100644 index 000000000..0405c3b88 --- /dev/null +++ b/qemu/roms/ipxe/src/drivers/bus/mca.c @@ -0,0 +1,177 @@ +/* + * MCA bus driver code + * + * Abstracted from 3c509.c. + * + */ + +FILE_LICENCE ( BSD2 ); + +#include <stdint.h> +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <ipxe/io.h> +#include <ipxe/mca.h> + +static void mcabus_remove ( struct root_device *rootdev ); + +/** + * Probe an MCA device + * + * @v mca MCA device + * @ret rc Return status code + * + * Searches for a driver for the MCA device. If a driver is found, + * its probe() routine is called. + */ +static int mca_probe ( struct mca_device *mca ) { + struct mca_driver *driver; + struct mca_device_id *id; + unsigned int i; + int rc; + + DBG ( "Adding MCA slot %02x (ID %04x POS " + "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x)\n", + mca->slot, MCA_ID ( mca ), + mca->pos[0], mca->pos[1], mca->pos[2], mca->pos[3], + mca->pos[4], mca->pos[5], mca->pos[6], mca->pos[7] ); + + for_each_table_entry ( driver, MCA_DRIVERS ) { + for ( i = 0 ; i < driver->id_count ; i++ ) { + id = &driver->ids[i]; + if ( id->id != MCA_ID ( mca ) ) + continue; + mca->driver = driver; + mca->dev.driver_name = id->name; + DBG ( "...using driver %s\n", mca->dev.driver_name ); + if ( ( rc = driver->probe ( mca, id ) ) != 0 ) { + DBG ( "......probe failed\n" ); + continue; + } + return 0; + } + } + + DBG ( "...no driver found\n" ); + return -ENOTTY; +} + +/** + * Remove an MCA device + * + * @v mca MCA device + */ +static void mca_remove ( struct mca_device *mca ) { + mca->driver->remove ( mca ); + DBG ( "Removed MCA device %02x\n", mca->slot ); +} + +/** + * Probe MCA root bus + * + * @v rootdev MCA bus root device + * + * Scans the MCA bus for devices and registers all devices it can + * find. + */ +static int mcabus_probe ( struct root_device *rootdev ) { + struct mca_device *mca = NULL; + unsigned int slot; + int seen_non_ff; + unsigned int i; + int rc; + + for ( slot = 0 ; slot <= MCA_MAX_SLOT_NR ; slot++ ) { + /* Allocate struct mca_device */ + if ( ! mca ) + mca = malloc ( sizeof ( *mca ) ); + if ( ! mca ) { + rc = -ENOMEM; + goto err; + } + memset ( mca, 0, sizeof ( *mca ) ); + mca->slot = slot; + + /* Make sure motherboard setup is off */ + outb_p ( 0xff, MCA_MOTHERBOARD_SETUP_REG ); + + /* Select the slot */ + outb_p ( 0x8 | ( mca->slot & 0xf ), MCA_ADAPTER_SETUP_REG ); + + /* Read the POS registers */ + seen_non_ff = 0; + for ( i = 0 ; i < ( sizeof ( mca->pos ) / + sizeof ( mca->pos[0] ) ) ; i++ ) { + mca->pos[i] = inb_p ( MCA_POS_REG ( i ) ); + if ( mca->pos[i] != 0xff ) + seen_non_ff = 1; + } + + /* Kill all setup modes */ + outb_p ( 0, MCA_ADAPTER_SETUP_REG ); + + /* If all POS registers are 0xff, this means there's no device + * present + */ + if ( ! seen_non_ff ) + continue; + + /* Add to device hierarchy */ + snprintf ( mca->dev.name, sizeof ( mca->dev.name ), + "MCA%02x", slot ); + mca->dev.desc.bus_type = BUS_TYPE_MCA; + mca->dev.desc.vendor = GENERIC_MCA_VENDOR; + mca->dev.desc.device = MCA_ID ( mca ); + mca->dev.parent = &rootdev->dev; + list_add ( &mca->dev.siblings, &rootdev->dev.children ); + INIT_LIST_HEAD ( &mca->dev.children ); + + /* Look for a driver */ + if ( mca_probe ( mca ) == 0 ) { + /* mcadev registered, we can drop our ref */ + mca = NULL; + } else { + /* Not registered; re-use struct */ + list_del ( &mca->dev.siblings ); + } + } + + free ( mca ); + return 0; + + err: + free ( mca ); + mcabus_remove ( rootdev ); + return rc; +} + +/** + * Remove MCA root bus + * + * @v rootdev MCA bus root device + */ +static void mcabus_remove ( struct root_device *rootdev ) { + struct mca_device *mca; + struct mca_device *tmp; + + list_for_each_entry_safe ( mca, tmp, &rootdev->dev.children, + dev.siblings ) { + mca_remove ( mca ); + list_del ( &mca->dev.siblings ); + free ( mca ); + } +} + +/** MCA bus root device driver */ +static struct root_driver mca_root_driver = { + .probe = mcabus_probe, + .remove = mcabus_remove, +}; + +/** MCA bus root device */ +struct root_device mca_root_device __root_device = { + .dev = { .name = "MCA" }, + .driver = &mca_root_driver, +}; diff --git a/qemu/roms/ipxe/src/drivers/bus/pci.c b/qemu/roms/ipxe/src/drivers/bus/pci.c new file mode 100644 index 000000000..4a8d00b54 --- /dev/null +++ b/qemu/roms/ipxe/src/drivers/bus/pci.c @@ -0,0 +1,392 @@ +/* + * Copyright (C) 2006 Michael Brown <mbrown@fensystems.co.uk>. + * + * Based in part on pci.c from Etherboot 5.4, by Ken Yap and David + * Munro, in turn based on the Linux kernel's PCI implementation. + * + * 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. + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +#include <stdint.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <ipxe/tables.h> +#include <ipxe/device.h> +#include <ipxe/pci.h> + +/** @file + * + * PCI bus + * + */ + +static void pcibus_remove ( struct root_device *rootdev ); + +/** + * Read PCI BAR + * + * @v pci PCI device + * @v reg PCI register number + * @ret bar Base address register + * + * Reads the specified PCI base address register, including the flags + * portion. 64-bit BARs will be handled automatically. If the value + * of the 64-bit BAR exceeds the size of an unsigned long (i.e. if the + * high dword is non-zero on a 32-bit platform), then the value + * returned will be zero plus the flags for a 64-bit BAR. Unreachable + * 64-bit BARs are therefore returned as uninitialised 64-bit BARs. + */ +static unsigned long pci_bar ( struct pci_device *pci, unsigned int reg ) { + uint32_t low; + uint32_t high; + + pci_read_config_dword ( pci, reg, &low ); + if ( ( low & (PCI_BASE_ADDRESS_SPACE|PCI_BASE_ADDRESS_MEM_TYPE_MASK) ) + == (PCI_BASE_ADDRESS_SPACE_MEMORY|PCI_BASE_ADDRESS_MEM_TYPE_64) ){ + pci_read_config_dword ( pci, reg + 4, &high ); + if ( high ) { + if ( sizeof ( unsigned long ) > sizeof ( uint32_t ) ) { + return ( ( ( uint64_t ) high << 32 ) | low ); + } else { + DBGC ( pci, PCI_FMT " unhandled 64-bit BAR " + "%08x%08x\n", + PCI_ARGS ( pci ), high, low ); + return PCI_BASE_ADDRESS_MEM_TYPE_64; + } + } + } + return low; +} + +/** + * Find the start of a PCI BAR + * + * @v pci PCI device + * @v reg PCI register number + * @ret start BAR start address + * + * Reads the specified PCI base address register, and returns the + * address portion of the BAR (i.e. without the flags). + * + * If the address exceeds the size of an unsigned long (i.e. if a + * 64-bit BAR has a non-zero high dword on a 32-bit machine), the + * return value will be zero. + */ +unsigned long pci_bar_start ( struct pci_device *pci, unsigned int reg ) { + unsigned long bar; + + bar = pci_bar ( pci, reg ); + if ( (bar & PCI_BASE_ADDRESS_SPACE) == PCI_BASE_ADDRESS_SPACE_MEMORY ){ + return ( bar & PCI_BASE_ADDRESS_MEM_MASK ); + } else { + return ( bar & PCI_BASE_ADDRESS_IO_MASK ); + } +} + +/** + * Read membase and ioaddr for a PCI device + * + * @v pci PCI device + * + * This scans through all PCI BARs on the specified device. The first + * valid memory BAR is recorded as pci_device::membase, and the first + * valid IO BAR is recorded as pci_device::ioaddr. + * + * 64-bit BARs are handled automatically. On a 32-bit platform, if a + * 64-bit BAR has a non-zero high dword, it will be regarded as + * invalid. + */ +static void pci_read_bases ( struct pci_device *pci ) { + unsigned long bar; + int reg; + + for ( reg = PCI_BASE_ADDRESS_0; reg <= PCI_BASE_ADDRESS_5; reg += 4 ) { + bar = pci_bar ( pci, reg ); + if ( bar & PCI_BASE_ADDRESS_SPACE_IO ) { + if ( ! pci->ioaddr ) + pci->ioaddr = + ( bar & PCI_BASE_ADDRESS_IO_MASK ); + } else { + if ( ! pci->membase ) + pci->membase = + ( bar & PCI_BASE_ADDRESS_MEM_MASK ); + /* Skip next BAR if 64-bit */ + if ( bar & PCI_BASE_ADDRESS_MEM_TYPE_64 ) + reg += 4; + } + } +} + +/** + * Enable PCI device + * + * @v pci PCI device + * + * Set device to be a busmaster in case BIOS neglected to do so. Also + * adjust PCI latency timer to a reasonable value, 32. + */ +void adjust_pci_device ( struct pci_device *pci ) { + unsigned short new_command, pci_command; + unsigned char pci_latency; + + pci_read_config_word ( pci, PCI_COMMAND, &pci_command ); + new_command = ( pci_command | PCI_COMMAND_MASTER | + PCI_COMMAND_MEM | PCI_COMMAND_IO ); + if ( pci_command != new_command ) { + DBGC ( pci, PCI_FMT " device not enabled by BIOS! Updating " + "PCI command %04x->%04x\n", + PCI_ARGS ( pci ), pci_command, new_command ); + pci_write_config_word ( pci, PCI_COMMAND, new_command ); + } + + pci_read_config_byte ( pci, PCI_LATENCY_TIMER, &pci_latency); + if ( pci_latency < 32 ) { + DBGC ( pci, PCI_FMT " latency timer is unreasonably low at " + "%d. Setting to 32.\n", PCI_ARGS ( pci ), pci_latency ); + pci_write_config_byte ( pci, PCI_LATENCY_TIMER, 32); + } +} + +/** + * Read PCI device configuration + * + * @v pci PCI device + * @ret rc Return status code + */ +int pci_read_config ( struct pci_device *pci ) { + uint16_t busdevfn; + uint8_t hdrtype; + uint32_t tmp; + + /* Ignore all but the first function on non-multifunction devices */ + if ( PCI_FUNC ( pci->busdevfn ) != 0 ) { + busdevfn = pci->busdevfn; + pci->busdevfn = PCI_FIRST_FUNC ( pci->busdevfn ); + pci_read_config_byte ( pci, PCI_HEADER_TYPE, &hdrtype ); + pci->busdevfn = busdevfn; + if ( ! ( hdrtype & 0x80 ) ) + return -ENODEV; + } + + /* Check for physical device presence */ + pci_read_config_dword ( pci, PCI_VENDOR_ID, &tmp ); + if ( ( tmp == 0xffffffff ) || ( tmp == 0 ) ) + return -ENODEV; + + /* Populate struct pci_device */ + pci->vendor = ( tmp & 0xffff ); + pci->device = ( tmp >> 16 ); + pci_read_config_dword ( pci, PCI_REVISION, &tmp ); + pci->class = ( tmp >> 8 ); + pci_read_config_byte ( pci, PCI_INTERRUPT_LINE, &pci->irq ); + pci_read_bases ( pci ); + + /* Initialise generic device component */ + snprintf ( pci->dev.name, sizeof ( pci->dev.name ), + "PCI%02x:%02x.%x", PCI_BUS ( pci->busdevfn ), + PCI_SLOT ( pci->busdevfn ), PCI_FUNC ( pci->busdevfn ) ); + pci->dev.desc.bus_type = BUS_TYPE_PCI; + pci->dev.desc.location = pci->busdevfn; + pci->dev.desc.vendor = pci->vendor; + pci->dev.desc.device = pci->device; + pci->dev.desc.class = pci->class; + pci->dev.desc.ioaddr = pci->ioaddr; + pci->dev.desc.irq = pci->irq; + INIT_LIST_HEAD ( &pci->dev.siblings ); + INIT_LIST_HEAD ( &pci->dev.children ); + + return 0; +} + +/** + * Find next device on PCI bus + * + * @v pci PCI device to fill in + * @v busdevfn Starting bus:dev.fn address + * @ret busdevfn Bus:dev.fn address of next PCI device, or negative error + */ +int pci_find_next ( struct pci_device *pci, unsigned int busdevfn ) { + static unsigned int end; + int rc; + + /* Determine number of PCI buses */ + if ( ! end ) + end = PCI_BUSDEVFN ( pci_num_bus(), 0, 0 ); + + /* Find next PCI device, if any */ + for ( ; busdevfn < end ; busdevfn++ ) { + memset ( pci, 0, sizeof ( *pci ) ); + pci_init ( pci, busdevfn ); + if ( ( rc = pci_read_config ( pci ) ) == 0 ) + return busdevfn; + } + + return -ENODEV; +} + +/** + * Find driver for PCI device + * + * @v pci PCI device + * @ret rc Return status code + */ +int pci_find_driver ( struct pci_device *pci ) { + struct pci_driver *driver; + struct pci_device_id *id; + unsigned int i; + + for_each_table_entry ( driver, PCI_DRIVERS ) { + for ( i = 0 ; i < driver->id_count ; i++ ) { + id = &driver->ids[i]; + if ( ( id->vendor != PCI_ANY_ID ) && + ( id->vendor != pci->vendor ) ) + continue; + if ( ( id->device != PCI_ANY_ID ) && + ( id->device != pci->device ) ) + continue; + pci_set_driver ( pci, driver, id ); + return 0; + } + } + return -ENOENT; +} + +/** + * Probe a PCI device + * + * @v pci PCI device + * @ret rc Return status code + * + * Searches for a driver for the PCI device. If a driver is found, + * its probe() routine is called. + */ +int pci_probe ( struct pci_device *pci ) { + int rc; + + DBGC ( pci, PCI_FMT " (%04x:%04x) has driver \"%s\"\n", + PCI_ARGS ( pci ), pci->vendor, pci->device, pci->id->name ); + DBGC ( pci, PCI_FMT " has mem %lx io %lx irq %d\n", + PCI_ARGS ( pci ), pci->membase, pci->ioaddr, pci->irq ); + + if ( ( rc = pci->driver->probe ( pci ) ) != 0 ) { + DBGC ( pci, PCI_FMT " probe failed: %s\n", + PCI_ARGS ( pci ), strerror ( rc ) ); + return rc; + } + + return 0; +} + +/** + * Remove a PCI device + * + * @v pci PCI device + */ +void pci_remove ( struct pci_device *pci ) { + pci->driver->remove ( pci ); + DBGC ( pci, PCI_FMT " removed\n", PCI_ARGS ( pci ) ); +} + +/** + * Probe PCI root bus + * + * @v rootdev PCI bus root device + * + * Scans the PCI bus for devices and registers all devices it can + * find. + */ +static int pcibus_probe ( struct root_device *rootdev ) { + struct pci_device *pci = NULL; + int busdevfn = 0; + int rc; + + for ( busdevfn = 0 ; 1 ; busdevfn++ ) { + + /* Allocate struct pci_device */ + if ( ! pci ) + pci = malloc ( sizeof ( *pci ) ); + if ( ! pci ) { + rc = -ENOMEM; + goto err; + } + + /* Find next PCI device, if any */ + busdevfn = pci_find_next ( pci, busdevfn ); + if ( busdevfn < 0 ) + break; + + /* Look for a driver */ + if ( ( rc = pci_find_driver ( pci ) ) != 0 ) { + DBGC ( pci, PCI_FMT " (%04x:%04x) has no driver\n", + PCI_ARGS ( pci ), pci->vendor, pci->device ); + continue; + } + + /* Add to device hierarchy */ + pci->dev.parent = &rootdev->dev; + list_add ( &pci->dev.siblings, &rootdev->dev.children); + + /* Look for a driver */ + if ( ( rc = pci_probe ( pci ) ) == 0 ) { + /* pcidev registered, we can drop our ref */ + pci = NULL; + } else { + /* Not registered; re-use struct pci_device */ + list_del ( &pci->dev.siblings ); + } + } + + free ( pci ); + return 0; + + err: + free ( pci ); + pcibus_remove ( rootdev ); + return rc; +} + +/** + * Remove PCI root bus + * + * @v rootdev PCI bus root device + */ +static void pcibus_remove ( struct root_device *rootdev ) { + struct pci_device *pci; + struct pci_device *tmp; + + list_for_each_entry_safe ( pci, tmp, &rootdev->dev.children, + dev.siblings ) { + pci_remove ( pci ); + list_del ( &pci->dev.siblings ); + free ( pci ); + } +} + +/** PCI bus root device driver */ +static struct root_driver pci_root_driver = { + .probe = pcibus_probe, + .remove = pcibus_remove, +}; + +/** PCI bus root device */ +struct root_device pci_root_device __root_device = { + .dev = { .name = "PCI" }, + .driver = &pci_root_driver, +}; diff --git a/qemu/roms/ipxe/src/drivers/bus/pci_settings.c b/qemu/roms/ipxe/src/drivers/bus/pci_settings.c new file mode 100644 index 000000000..db20452e0 --- /dev/null +++ b/qemu/roms/ipxe/src/drivers/bus/pci_settings.c @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2013 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 (at your option) 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. + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +#include <stdio.h> +#include <errno.h> +#include <ipxe/pci.h> +#include <ipxe/settings.h> +#include <ipxe/init.h> + +/** @file + * + * PCI device settings + * + */ + +/** PCI device settings scope */ +static const struct settings_scope pci_settings_scope; + +/** + * Check applicability of PCI device setting + * + * @v settings Settings block + * @v setting Setting + * @ret applies Setting applies within this settings block + */ +static int pci_settings_applies ( struct settings *settings __unused, + const struct setting *setting ) { + + return ( setting->scope == &pci_settings_scope ); +} + +/** + * Fetch value of PCI device setting + * + * @v settings Settings block + * @v setting Setting to fetch + * @v data Buffer to fill with setting data + * @v len Length of buffer + * @ret len Length of setting data, or negative error + */ +static int pci_settings_fetch ( struct settings *settings __unused, + struct setting *setting, + void *data, size_t len ) { + struct pci_device pci; + unsigned int tag_busdevfn; + unsigned int tag_offset; + unsigned int tag_len; + unsigned int i; + + /* Extract busdevfn, offset, and length from tag */ + tag_busdevfn = ( ( setting->tag >> 16 ) & 0xffff ); + tag_offset = ( ( setting->tag >> 8 ) & 0xff ); + tag_len = ( ( setting->tag >> 0 ) & 0xff ); + + /* Locate PCI device */ + memset ( &pci, 0, sizeof ( pci ) ); + pci_init ( &pci, tag_busdevfn ); + DBG ( PCI_FMT " reading %#02x+%#x\n", PCI_ARGS ( &pci ), + tag_offset, tag_len ); + + /* Read data one byte at a time, in reverse order (since PCI + * is little-endian and iPXE settings are essentially + * big-endian). + */ + tag_offset += tag_len; + for ( i = 0 ; ( ( i < tag_len ) && ( i < len ) ); i++ ) { + pci_read_config_byte ( &pci, --tag_offset, data++ ); + } + + /* Set type to ":hexraw" if not already specified */ + if ( ! setting->type ) + setting->type = &setting_type_hexraw; + + return tag_len; +} + +/** PCI device settings operations */ +static struct settings_operations pci_settings_operations = { + .applies = pci_settings_applies, + .fetch = pci_settings_fetch, +}; + +/** PCI device settings */ +static struct settings pci_settings = { + .refcnt = NULL, + .siblings = LIST_HEAD_INIT ( pci_settings.siblings ), + .children = LIST_HEAD_INIT ( pci_settings.children ), + .op = &pci_settings_operations, + .default_scope = &pci_settings_scope, +}; + +/** Initialise PCI device settings */ +static void pci_settings_init ( void ) { + int rc; + + if ( ( rc = register_settings ( &pci_settings, NULL, "pci" ) ) != 0 ) { + DBG ( "PCI could not register settings: %s\n", + strerror ( rc ) ); + return; + } +} + +/** PCI device settings initialiser */ +struct init_fn pci_settings_init_fn __init_fn ( INIT_NORMAL ) = { + .initialise = pci_settings_init, +}; diff --git a/qemu/roms/ipxe/src/drivers/bus/pcibackup.c b/qemu/roms/ipxe/src/drivers/bus/pcibackup.c new file mode 100644 index 000000000..6b592e893 --- /dev/null +++ b/qemu/roms/ipxe/src/drivers/bus/pcibackup.c @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2009 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. + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +#include <stdint.h> +#include <ipxe/pci.h> +#include <ipxe/pcibackup.h> + +/** @file + * + * PCI configuration space backup and restoration + * + */ + +/** + * Check PCI configuration space offset against exclusion list + * + * @v pci PCI device + * @v offset Offset within PCI configuration space + * @v exclude PCI configuration space backup exclusion list, or NULL + */ +static int +pci_backup_excluded ( struct pci_device *pci, unsigned int offset, + const uint8_t *exclude ) { + + if ( ! exclude ) + return 0; + for ( ; *exclude != PCI_CONFIG_BACKUP_EXCLUDE_END ; exclude++ ) { + if ( offset == *exclude ) { + DBGC ( pci, "PCI %p skipping configuration offset " + "%02x\n", pci, offset ); + return 1; + } + } + return 0; +} + +/** + * Back up PCI configuration space + * + * @v pci PCI device + * @v backup PCI configuration space backup + * @v exclude PCI configuration space backup exclusion list, or NULL + */ +void pci_backup ( struct pci_device *pci, struct pci_config_backup *backup, + const uint8_t *exclude ) { + unsigned int offset; + uint32_t *dword; + + for ( offset = 0, dword = backup->dwords ; offset < 0x100 ; + offset += sizeof ( *dword ) , dword++ ) { + if ( ! pci_backup_excluded ( pci, offset, exclude ) ) + pci_read_config_dword ( pci, offset, dword ); + } +} + +/** + * Restore PCI configuration space + * + * @v pci PCI device + * @v backup PCI configuration space backup + * @v exclude PCI configuration space backup exclusion list, or NULL + */ +void pci_restore ( struct pci_device *pci, struct pci_config_backup *backup, + const uint8_t *exclude ) { + unsigned int offset; + uint32_t *dword; + + for ( offset = 0, dword = backup->dwords ; offset < 0x100 ; + offset += sizeof ( *dword ) , dword++ ) { + if ( ! pci_backup_excluded ( pci, offset, exclude ) ) + pci_write_config_dword ( pci, offset, *dword ); + } +} diff --git a/qemu/roms/ipxe/src/drivers/bus/pciextra.c b/qemu/roms/ipxe/src/drivers/bus/pciextra.c new file mode 100644 index 000000000..c4417e0cb --- /dev/null +++ b/qemu/roms/ipxe/src/drivers/bus/pciextra.c @@ -0,0 +1,86 @@ +FILE_LICENCE ( GPL2_OR_LATER ); + +#include <stdint.h> +#include <ipxe/pci.h> + +/** + * Look for a PCI capability + * + * @v pci PCI device to query + * @v cap Capability code + * @ret address Address of capability, or 0 if not found + * + * Determine whether or not a device supports a given PCI capability. + * Returns the address of the requested capability structure within + * the device's PCI configuration space, or 0 if the device does not + * support it. + */ +int pci_find_capability ( struct pci_device *pci, int cap ) { + uint16_t status; + uint8_t pos, id; + uint8_t hdr_type; + int ttl = 48; + + pci_read_config_word ( pci, PCI_STATUS, &status ); + if ( ! ( status & PCI_STATUS_CAP_LIST ) ) + return 0; + + pci_read_config_byte ( pci, PCI_HEADER_TYPE, &hdr_type ); + switch ( hdr_type & 0x7F ) { + case PCI_HEADER_TYPE_NORMAL: + case PCI_HEADER_TYPE_BRIDGE: + default: + pci_read_config_byte ( pci, PCI_CAPABILITY_LIST, &pos ); + break; + case PCI_HEADER_TYPE_CARDBUS: + pci_read_config_byte ( pci, PCI_CB_CAPABILITY_LIST, &pos ); + break; + } + while ( ttl-- && pos >= 0x40 ) { + pos &= ~3; + pci_read_config_byte ( pci, pos + PCI_CAP_LIST_ID, &id ); + DBG ( "PCI Capability: %d\n", id ); + if ( id == 0xff ) + break; + if ( id == cap ) + return pos; + pci_read_config_byte ( pci, pos + PCI_CAP_LIST_NEXT, &pos ); + } + return 0; +} + +/** + * Find the size of a PCI BAR + * + * @v pci PCI device + * @v reg PCI register number + * @ret size BAR size + * + * It should not be necessary for any Etherboot code to call this + * function. + */ +unsigned long pci_bar_size ( struct pci_device *pci, unsigned int reg ) { + uint16_t cmd; + uint32_t start, size; + + /* Save the original command register */ + pci_read_config_word ( pci, PCI_COMMAND, &cmd ); + /* Save the original bar */ + pci_read_config_dword ( pci, reg, &start ); + /* Compute which bits can be set */ + pci_write_config_dword ( pci, reg, ~0 ); + pci_read_config_dword ( pci, reg, &size ); + /* Restore the original size */ + pci_write_config_dword ( pci, reg, start ); + /* Find the significant bits */ + /* Restore the original command register. This reenables decoding. */ + pci_write_config_word ( pci, PCI_COMMAND, cmd ); + if ( start & PCI_BASE_ADDRESS_SPACE_IO ) { + size &= PCI_BASE_ADDRESS_IO_MASK; + } else { + size &= PCI_BASE_ADDRESS_MEM_MASK; + } + /* Find the lowest bit set */ + size = size & ~( size - 1 ); + return size; +} diff --git a/qemu/roms/ipxe/src/drivers/bus/pcivpd.c b/qemu/roms/ipxe/src/drivers/bus/pcivpd.c new file mode 100644 index 000000000..0b7a879fe --- /dev/null +++ b/qemu/roms/ipxe/src/drivers/bus/pcivpd.c @@ -0,0 +1,556 @@ +/* + * 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. + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +#include <stdint.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <byteswap.h> +#include <ipxe/pci.h> +#include <ipxe/isapnp.h> +#include <ipxe/pcivpd.h> + +/** @file + * + * PCI Vital Product Data + * + */ + +/** + * Initialise PCI Vital Product Data + * + * @v vpd PCI VPD + * @v pci PCI device + * @ret rc Return status code + */ +int pci_vpd_init ( struct pci_vpd *vpd, struct pci_device *pci ) { + + /* Initialise structure */ + vpd->pci = pci; + pci_vpd_invalidate_cache ( vpd ); + + /* Locate VPD capability */ + vpd->cap = pci_find_capability ( pci, PCI_CAP_ID_VPD ); + if ( ! vpd->cap ) { + DBGC ( vpd, PCI_FMT " does not support VPD\n", + PCI_ARGS ( pci ) ); + return -ENOTTY; + } + + DBGC ( vpd, PCI_FMT " VPD is at offset %02x\n", + PCI_ARGS ( pci ), vpd->cap ); + return 0; +} + +/** + * Read one dword of PCI Vital Product Data + * + * @v vpd PCI VPD + * @v address Address to read + * @ret data Read data + * @ret rc Return status code + */ +static int pci_vpd_read_dword ( struct pci_vpd *vpd, int address, + uint32_t *data ) { + struct pci_device *pci = vpd->pci; + unsigned int cap = vpd->cap; + unsigned int retries; + uint16_t flag; + + /* Fail if no VPD present */ + if ( ! cap ) + return -ENOTTY; + + /* Return cached value, if present */ + if ( pci_vpd_cache_is_valid ( vpd ) && + ( vpd->cache.address == address ) ) { + *data = vpd->cache.data; + return 0; + } + + /* Initiate read */ + pci_write_config_word ( pci, ( cap + PCI_VPD_ADDRESS ), address ); + + /* Wait for read to complete */ + for ( retries = 0 ; retries < PCI_VPD_MAX_WAIT_MS ; retries++ ) { + + /* Check if data is ready */ + pci_read_config_word ( pci, ( cap + PCI_VPD_ADDRESS ), &flag ); + if ( flag & PCI_VPD_FLAG ) { + + /* Read data */ + pci_read_config_dword ( pci, ( cap + PCI_VPD_DATA ), + data ); + DBGC2 ( vpd, PCI_FMT " VPD %04x => %08x\n", + PCI_ARGS ( pci ), address, htonl ( *data ) ); + + /* Populate cache */ + vpd->cache.address = address; + vpd->cache.data = *data; + + return 0; + } + + /* Wait 1ms before retrying */ + mdelay ( 1 ); + } + + DBGC ( vpd, PCI_FMT " VPD %04x read via %02x timed out\n", + PCI_ARGS ( pci ), address, cap ); + return -ETIMEDOUT; +} + +/** + * Write one dword of PCI Vital Product Data + * + * @v vpd PCI VPD + * @v address Address to write + * @v data Data to write + * @ret rc Return status code + */ +static int pci_vpd_write_dword ( struct pci_vpd *vpd, int address, + uint32_t data ) { + struct pci_device *pci = vpd->pci; + unsigned int cap = vpd->cap; + unsigned int retries; + uint16_t flag; + + /* Fail if no VPD present */ + if ( ! cap ) + return -ENOTTY; + + /* Invalidate cache */ + pci_vpd_invalidate_cache ( vpd ); + + DBGC2 ( vpd, PCI_FMT " VPD %04x <= %08x\n", + PCI_ARGS ( pci ), address, htonl ( data ) ); + + /* Write data */ + pci_write_config_dword ( pci, ( cap + PCI_VPD_DATA ), data ); + + /* Initiate write */ + pci_write_config_word ( pci, ( cap + PCI_VPD_ADDRESS ), + ( address | PCI_VPD_FLAG ) ); + + /* Wait for write to complete */ + for ( retries = 0 ; retries < PCI_VPD_MAX_WAIT_MS ; retries++ ) { + + /* Check if write has completed */ + pci_read_config_word ( pci, ( cap + PCI_VPD_ADDRESS ), &flag ); + if ( ! ( flag & PCI_VPD_FLAG ) ) + return 0; + + /* Wait 1ms before retrying */ + mdelay ( 1 ); + } + + DBGC ( vpd, PCI_FMT " VPD %04x write via %02x timed out\n", + PCI_ARGS ( pci ), address, cap ); + return -ETIMEDOUT; +} + +/** + * Read PCI VPD + * + * @v vpd PCI VPD + * @v address Starting address + * @v buf Data buffer + * @v len Length of data buffer + * @ret rc Return status code + */ +int pci_vpd_read ( struct pci_vpd *vpd, unsigned int address, void *buf, + size_t len ) { + uint8_t *bytes = buf; + uint32_t data; + size_t skip_len; + unsigned int i; + int rc; + + /* Calculate length to skip at start of data */ + skip_len = ( address & 0x03 ); + + /* Read data, a dword at a time */ + for ( address &= ~0x03 ; len ; address += 4 ) { + + /* Read whole dword */ + if ( ( rc = pci_vpd_read_dword ( vpd, address, &data ) ) != 0 ) + return rc; + + /* Copy data to buffer */ + for ( i = 4 ; i ; i-- ) { + if ( skip_len ) { + skip_len--; + } else if ( len ) { + *(bytes++) = data; + len--; + } + data = ( ( data << 24 ) | ( data >> 8 ) ); + } + } + + return 0; +} + +/** + * Write PCI VPD + * + * @v vpd PCI VPD + * @v address Starting address + * @v buf Data buffer + * @v len Length of data buffer + * @ret rc Return status code + */ +int pci_vpd_write ( struct pci_vpd *vpd, unsigned int address, const void *buf, + size_t len ) { + const uint8_t *bytes = buf; + uint32_t data; + size_t skip_len; + unsigned int i; + int rc; + + /* Calculate length to skip at start of data */ + skip_len = ( address & 0x03 ); + + /* Write data, a dword at a time */ + for ( address &= ~0x03 ; len ; address += 4 ) { + + /* Read existing dword, if necessary */ + if ( skip_len || ( len <= 0x03 ) ) { + if ( ( rc = pci_vpd_read_dword ( vpd, address, + &data ) ) != 0 ) + return rc; + } + + /* Copy data from buffer */ + for ( i = 4 ; i ; i-- ) { + if ( skip_len ) { + skip_len--; + } else if ( len ) { + data = ( ( data & ~0xff ) | *(bytes++) ); + len--; + } + data = ( ( data << 24 ) | ( data >> 8 ) ); + } + + /* Write whole dword */ + if ( ( rc = pci_vpd_write_dword ( vpd, address, data ) ) != 0 ) + return rc; + } + return 0; +} + +/** + * Dump PCI VPD region (for debugging) + * + * @v vpd PCI VPD + * @v address Starting address + * @v len Length of data + */ +static void pci_vpd_dump ( struct pci_vpd *vpd, unsigned int address, + size_t len ) { + int rc; + + /* Do nothing in non-debug builds */ + if ( ! DBG_LOG ) + return; + + /* Read data */ + { + char buf[len]; + if ( ( rc = pci_vpd_read ( vpd, address, buf, + sizeof ( buf ) ) ) != 0 ) + return; + DBGC_HDA ( vpd, address, buf, sizeof ( buf ) ); + } +} + +/** + * Locate PCI VPD tag + * + * @v vpd PCI VPD + * @v tag ISAPnP tag + * @ret address Address of tag body + * @ret len Length of tag body + * @ret rc Return status code + */ +static int pci_vpd_find_tag ( struct pci_vpd *vpd, unsigned int tag, + unsigned int *address, size_t *len ) { + uint8_t read_tag; + uint16_t read_len; + int rc; + + /* Scan through tags looking for a match */ + *address = 0; + do { + /* Read tag byte */ + if ( ( rc = pci_vpd_read ( vpd, (*address)++, &read_tag, + sizeof ( read_tag ) ) ) != 0 ) + return rc; + + /* Extract tag and length */ + if ( ISAPNP_IS_LARGE_TAG ( read_tag ) ) { + if ( ( rc = pci_vpd_read ( vpd, *address, &read_len, + sizeof ( read_len ) ) ) != 0) + return rc; + *address += sizeof ( read_len ); + read_len = le16_to_cpu ( read_len ); + read_tag = ISAPNP_LARGE_TAG_NAME ( read_tag ); + } else { + read_len = ISAPNP_SMALL_TAG_LEN ( read_tag ); + read_tag = ISAPNP_SMALL_TAG_NAME ( read_tag ); + } + + /* Check for tag match */ + if ( tag == read_tag ) { + *len = read_len; + DBGC ( vpd, PCI_FMT " VPD tag %02x is at " + "[%04x,%04zx)\n", PCI_ARGS ( vpd->pci ), tag, + *address, ( *address + *len ) ); + return 0; + } + + /* Move to next tag */ + *address += read_len; + + } while ( read_tag != ISAPNP_TAG_END ); + + DBGC ( vpd, PCI_FMT " VPD tag %02x not found\n", + PCI_ARGS ( vpd->pci ), tag ); + return -ENOENT; +} + +/** + * Locate PCI VPD field + * + * @v vpd PCI VPD + * @v field VPD field descriptor + * @ret address Address of field body + * @ret len Length of field body + * @ret rc Return status code + */ +int pci_vpd_find ( struct pci_vpd *vpd, unsigned int field, + unsigned int *address, size_t *len ) { + struct pci_vpd_field read_field; + int rc; + + /* Locate containing tag */ + if ( ( rc = pci_vpd_find_tag ( vpd, PCI_VPD_TAG ( field ), + address, len ) ) != 0 ) + return rc; + + /* Return immediately if we are searching for a whole-tag field */ + if ( ! PCI_VPD_KEYWORD ( field ) ) { + pci_vpd_dump ( vpd, *address, *len ); + return 0; + } + + /* Scan through fields looking for a match */ + while ( *len >= sizeof ( read_field ) ) { + + /* Read field header */ + if ( ( rc = pci_vpd_read ( vpd, *address, &read_field, + sizeof ( read_field ) ) ) != 0 ) + return rc; + *address += sizeof ( read_field ); + *len -= sizeof ( read_field ); + + /* Check for keyword match */ + if ( read_field.keyword == PCI_VPD_KEYWORD ( field ) ) { + *len = read_field.len; + DBGC ( vpd, PCI_FMT " VPD field " PCI_VPD_FIELD_FMT + " is at [%04x,%04zx)\n", PCI_ARGS ( vpd->pci ), + PCI_VPD_FIELD_ARGS ( field ), + *address, ( *address + *len ) ); + pci_vpd_dump ( vpd, *address, *len ); + return 0; + } + + /* Move to next field */ + if ( read_field.len > *len ) + break; + *address += read_field.len; + *len -= read_field.len; + } + + DBGC ( vpd, PCI_FMT " VPD field " PCI_VPD_FIELD_FMT " not found\n", + PCI_ARGS ( vpd->pci ), PCI_VPD_FIELD_ARGS ( field ) ); + return -ENOENT; +} + +/** + * Resize VPD field + * + * @v vpd PCI VPD + * @v field VPD field descriptor + * @v len New length of field body + * @ret address Address of field body + * @ret rc Return status code + */ +int pci_vpd_resize ( struct pci_vpd *vpd, unsigned int field, size_t len, + unsigned int *address ) { + struct pci_vpd_field rw_field; + struct pci_vpd_field old_field; + struct pci_vpd_field new_field; + unsigned int rw_address; + unsigned int old_address; + unsigned int copy_address; + unsigned int dst_address; + unsigned int dump_address; + size_t rw_len; + size_t old_len; + size_t available_len; + size_t copy_len; + size_t dump_len; + void *copy; + int rc; + + /* Sanity checks */ + assert ( PCI_VPD_TAG ( field ) == PCI_VPD_TAG_RW ); + assert ( PCI_VPD_KEYWORD ( field ) != 0 ); + assert ( field != PCI_VPD_FIELD_RW ); + + /* Locate 'RW' field */ + if ( ( rc = pci_vpd_find ( vpd, PCI_VPD_FIELD_RW, &rw_address, + &rw_len ) ) != 0 ) + goto err_no_rw; + + /* Locate old field, if any */ + if ( ( rc = pci_vpd_find ( vpd, field, &old_address, + &old_len ) ) == 0 ) { + + /* Field already exists */ + if ( old_address > rw_address ) { + DBGC ( vpd, PCI_FMT " VPD field " PCI_VPD_FIELD_FMT + " at [%04x,%04zx) is after field " + PCI_VPD_FIELD_FMT " at [%04x,%04zx)\n", + PCI_ARGS ( vpd->pci ), + PCI_VPD_FIELD_ARGS ( field ), + old_address, ( old_address + old_len ), + PCI_VPD_FIELD_ARGS ( PCI_VPD_FIELD_RW ), + rw_address, ( rw_address + rw_len ) ); + rc = -ENXIO; + goto err_after_rw; + } + dst_address = ( old_address - sizeof ( old_field ) ); + copy_address = ( old_address + old_len ); + copy_len = ( rw_address - sizeof ( rw_field ) - copy_address ); + + /* Calculate available length */ + available_len = ( rw_len + old_len ); + + } else { + + /* Field does not yet exist */ + dst_address = ( rw_address - sizeof ( rw_field ) ); + copy_address = dst_address; + copy_len = 0; + + /* Calculate available length */ + available_len = ( ( rw_len > sizeof ( new_field ) ) ? + ( rw_len - sizeof ( new_field ) ) : 0 ); + } + + /* Dump region before changes */ + dump_address = dst_address; + dump_len = ( rw_address + rw_len - dump_address ); + DBGC ( vpd, PCI_FMT " VPD before resizing field " PCI_VPD_FIELD_FMT + " to %zd bytes:\n", PCI_ARGS ( vpd->pci ), + PCI_VPD_FIELD_ARGS ( field ), len ); + pci_vpd_dump ( vpd, dump_address, dump_len ); + + /* Check available length */ + if ( available_len > PCI_VPD_MAX_LEN ) + available_len = PCI_VPD_MAX_LEN; + if ( len > available_len ) { + DBGC ( vpd, PCI_FMT " VPD no space for field " + PCI_VPD_FIELD_FMT " (need %02zx, have %02zx)\n", + PCI_ARGS ( vpd->pci ), PCI_VPD_FIELD_ARGS ( field ), + len, available_len ); + rc = -ENOSPC; + goto err_no_space; + } + + /* Preserve intermediate fields, if any */ + copy = malloc ( copy_len ); + if ( ! copy ) { + rc = -ENOMEM; + goto err_copy_alloc; + } + if ( ( rc = pci_vpd_read ( vpd, copy_address, copy, copy_len ) ) != 0 ) + goto err_copy_read; + + /* Create new field, if applicable */ + if ( len ) { + new_field.keyword = PCI_VPD_KEYWORD ( field ); + new_field.len = len; + if ( ( rc = pci_vpd_write ( vpd, dst_address, &new_field, + sizeof ( new_field ) ) ) != 0 ) + goto err_new_write; + dst_address += sizeof ( new_field ); + *address = dst_address; + DBGC ( vpd, PCI_FMT " VPD field " PCI_VPD_FIELD_FMT " is now " + "at [%04x,%04x)\n", PCI_ARGS ( vpd->pci ), + PCI_VPD_FIELD_ARGS ( field ), dst_address, + ( dst_address + new_field.len ) ); + dst_address += len; + } else { + DBGC ( vpd, PCI_FMT " VPD field " PCI_VPD_FIELD_FMT + " no longer exists\n", PCI_ARGS ( vpd->pci ), + PCI_VPD_FIELD_ARGS ( field ) ); + } + + /* Restore intermediate fields, if any */ + if ( ( rc = pci_vpd_write ( vpd, dst_address, copy, copy_len ) ) != 0 ) + goto err_copy_write; + dst_address += copy_len; + + /* Create 'RW' field */ + rw_field.keyword = PCI_VPD_KEYWORD ( PCI_VPD_FIELD_RW ); + rw_field.len = ( rw_len + + ( rw_address - sizeof ( rw_field ) ) - dst_address ); + if ( ( rc = pci_vpd_write ( vpd, dst_address, &rw_field, + sizeof ( rw_field ) ) ) != 0 ) + goto err_rw_write; + dst_address += sizeof ( rw_field ); + DBGC ( vpd, PCI_FMT " VPD field " PCI_VPD_FIELD_FMT " is now " + "at [%04x,%04x)\n", PCI_ARGS ( vpd->pci ), + PCI_VPD_FIELD_ARGS ( PCI_VPD_FIELD_RW ), dst_address, + ( dst_address + rw_field.len ) ); + + /* Dump region after changes */ + DBGC ( vpd, PCI_FMT " VPD after resizing field " PCI_VPD_FIELD_FMT + " to %zd bytes:\n", PCI_ARGS ( vpd->pci ), + PCI_VPD_FIELD_ARGS ( field ), len ); + pci_vpd_dump ( vpd, dump_address, dump_len ); + + rc = 0; + + err_rw_write: + err_new_write: + err_copy_write: + err_copy_read: + free ( copy ); + err_copy_alloc: + err_no_space: + err_after_rw: + err_no_rw: + return rc; +} diff --git a/qemu/roms/ipxe/src/drivers/bus/virtio-pci.c b/qemu/roms/ipxe/src/drivers/bus/virtio-pci.c new file mode 100644 index 000000000..fbef067bc --- /dev/null +++ b/qemu/roms/ipxe/src/drivers/bus/virtio-pci.c @@ -0,0 +1,64 @@ +/* virtio-pci.c - pci interface for virtio interface + * + * (c) Copyright 2008 Bull S.A.S. + * + * Author: Laurent Vivier <Laurent.Vivier@bull.net> + * + * some parts from Linux Virtio PCI driver + * + * Copyright IBM Corp. 2007 + * Authors: Anthony Liguori <aliguori@us.ibm.com> + * + */ + +#include "etherboot.h" +#include "ipxe/io.h" +#include "ipxe/virtio-ring.h" +#include "ipxe/virtio-pci.h" + +int vp_find_vq(unsigned int ioaddr, int queue_index, + struct vring_virtqueue *vq) +{ + struct vring * vr = &vq->vring; + u16 num; + + /* select the queue */ + + outw(queue_index, ioaddr + VIRTIO_PCI_QUEUE_SEL); + + /* check if the queue is available */ + + num = inw(ioaddr + VIRTIO_PCI_QUEUE_NUM); + if (!num) { + printf("ERROR: queue size is 0\n"); + return -1; + } + + if (num > MAX_QUEUE_NUM) { + printf("ERROR: queue size %d > %d\n", num, MAX_QUEUE_NUM); + return -1; + } + + /* check if the queue is already active */ + + if (inl(ioaddr + VIRTIO_PCI_QUEUE_PFN)) { + printf("ERROR: queue already active\n"); + return -1; + } + + vq->queue_index = queue_index; + + /* initialize the queue */ + + vring_init(vr, num, (unsigned char*)&vq->queue); + + /* activate the queue + * + * NOTE: vr->desc is initialized by vring_init() + */ + + outl((unsigned long)virt_to_phys(vr->desc) >> PAGE_SHIFT, + ioaddr + VIRTIO_PCI_QUEUE_PFN); + + return num; +} diff --git a/qemu/roms/ipxe/src/drivers/bus/virtio-ring.c b/qemu/roms/ipxe/src/drivers/bus/virtio-ring.c new file mode 100644 index 000000000..e55b6d0ed --- /dev/null +++ b/qemu/roms/ipxe/src/drivers/bus/virtio-ring.c @@ -0,0 +1,136 @@ +/* virtio-pci.c - virtio ring management + * + * (c) Copyright 2008 Bull S.A.S. + * + * Author: Laurent Vivier <Laurent.Vivier@bull.net> + * + * some parts from Linux Virtio Ring + * + * Copyright Rusty Russell IBM Corporation 2007 + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + * + * + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +#include "etherboot.h" +#include "ipxe/io.h" +#include "ipxe/virtio-ring.h" +#include "ipxe/virtio-pci.h" + +#define BUG() do { \ + printf("BUG: failure at %s:%d/%s()!\n", \ + __FILE__, __LINE__, __FUNCTION__); \ + while(1); \ +} while (0) +#define BUG_ON(condition) do { if (condition) BUG(); } while (0) + +/* + * vring_free + * + * put at the begin of the free list the current desc[head] + */ + +void vring_detach(struct vring_virtqueue *vq, unsigned int head) +{ + struct vring *vr = &vq->vring; + unsigned int i; + + /* find end of given descriptor */ + + i = head; + while (vr->desc[i].flags & VRING_DESC_F_NEXT) + i = vr->desc[i].next; + + /* link it with free list and point to it */ + + vr->desc[i].next = vq->free_head; + wmb(); + vq->free_head = head; +} + +/* + * vring_get_buf + * + * get a buffer from the used list + * + */ + +void *vring_get_buf(struct vring_virtqueue *vq, unsigned int *len) +{ + struct vring *vr = &vq->vring; + struct vring_used_elem *elem; + u32 id; + void *opaque; + + BUG_ON(!vring_more_used(vq)); + + elem = &vr->used->ring[vq->last_used_idx % vr->num]; + wmb(); + id = elem->id; + if (len != NULL) + *len = elem->len; + + opaque = vq->vdata[id]; + + vring_detach(vq, id); + + vq->last_used_idx++; + + return opaque; +} + +void vring_add_buf(struct vring_virtqueue *vq, + struct vring_list list[], + unsigned int out, unsigned int in, + void *opaque, int num_added) +{ + struct vring *vr = &vq->vring; + int i, avail, head, prev; + + BUG_ON(out + in == 0); + + prev = 0; + head = vq->free_head; + for (i = head; out; i = vr->desc[i].next, out--) { + + vr->desc[i].flags = VRING_DESC_F_NEXT; + vr->desc[i].addr = (u64)virt_to_phys(list->addr); + vr->desc[i].len = list->length; + prev = i; + list++; + } + for ( ; in; i = vr->desc[i].next, in--) { + + vr->desc[i].flags = VRING_DESC_F_NEXT|VRING_DESC_F_WRITE; + vr->desc[i].addr = (u64)virt_to_phys(list->addr); + vr->desc[i].len = list->length; + prev = i; + list++; + } + vr->desc[prev].flags &= ~VRING_DESC_F_NEXT; + + vq->free_head = i; + + vq->vdata[head] = opaque; + + avail = (vr->avail->idx + num_added) % vr->num; + vr->avail->ring[avail] = head; + wmb(); +} + +void vring_kick(unsigned int ioaddr, struct vring_virtqueue *vq, int num_added) +{ + struct vring *vr = &vq->vring; + + wmb(); + vr->avail->idx += num_added; + + mb(); + if (!(vr->used->flags & VRING_USED_F_NO_NOTIFY)) + vp_notify(ioaddr, vq->queue_index); +} + |