diff options
author | H. Peter Anvin <hpa@zytor.com> | 2008-03-26 16:25:35 -0700 |
---|---|---|
committer | H. Peter Anvin <hpa@zytor.com> | 2008-03-26 16:25:35 -0700 |
commit | 9eddd22a7b53b1d02fbae0d987df8af122924248 (patch) | |
tree | 882f5152880b0b1aa2d7a0619d30065acc69fb16 /gpxe/src/drivers/bus | |
parent | bbb8f15936b851e6a0ef6f7bb2c95197bff35994 (diff) | |
download | syslinux-9eddd22a7b53b1d02fbae0d987df8af122924248.tar.gz |
Add gPXE into the source tree; build unified imagesyslinux-3.70-pre7
Diffstat (limited to 'gpxe/src/drivers/bus')
-rw-r--r-- | gpxe/src/drivers/bus/eisa.c | 185 | ||||
-rw-r--r-- | gpxe/src/drivers/bus/isa.c | 175 | ||||
-rw-r--r-- | gpxe/src/drivers/bus/isa_ids.c | 26 | ||||
-rw-r--r-- | gpxe/src/drivers/bus/isapnp.c | 758 | ||||
-rw-r--r-- | gpxe/src/drivers/bus/mca.c | 180 | ||||
-rw-r--r-- | gpxe/src/drivers/bus/pci.c | 343 | ||||
-rw-r--r-- | gpxe/src/drivers/bus/pciextra.c | 79 |
7 files changed, 1746 insertions, 0 deletions
diff --git a/gpxe/src/drivers/bus/eisa.c b/gpxe/src/drivers/bus/eisa.c new file mode 100644 index 00000000..ee03df3a --- /dev/null +++ b/gpxe/src/drivers/bus/eisa.c @@ -0,0 +1,185 @@ +#include <stdint.h> +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <io.h> +#include <unistd.h> +#include <gpxe/eisa.h> + +static struct eisa_driver eisa_drivers[0] + __table_start ( struct eisa_driver, eisa_drivers ); +static struct eisa_driver eisa_drivers_end[0] + __table_end ( struct eisa_driver, eisa_drivers ); + +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 ( driver = eisa_drivers; driver < eisa_drivers_end; driver++ ) { + 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->driver_name = id->name; + DBG ( "...using driver %s\n", eisa->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/gpxe/src/drivers/bus/isa.c b/gpxe/src/drivers/bus/isa.c new file mode 100644 index 00000000..a4105fd0 --- /dev/null +++ b/gpxe/src/drivers/bus/isa.c @@ -0,0 +1,175 @@ +#include <stdint.h> +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <io.h> +#include <gpxe/isa.h> + +/* + * 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 ) ? \ + isa_extra_probe_addrs[ (ioidx) + ISA_EXTRA_PROBE_ADDR_COUNT ] : \ + (driver)->probe_addrs[(ioidx)] ) + +static struct isa_driver isa_drivers[0] + __table_start ( struct isa_driver, isa_driver ); +static struct isa_driver isa_drivers_end[0] + __table_end ( struct isa_driver, isa_driver ); + +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 ( driver = isa_drivers ; driver < isa_drivers_end ; driver++ ) { + 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.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/gpxe/src/drivers/bus/isa_ids.c b/gpxe/src/drivers/bus/isa_ids.c new file mode 100644 index 00000000..73101584 --- /dev/null +++ b/gpxe/src/drivers/bus/isa_ids.c @@ -0,0 +1,26 @@ +#include <stdint.h> +#include <stdio.h> +#include <byteswap.h> +#include <gpxe/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/gpxe/src/drivers/bus/isapnp.c b/gpxe/src/drivers/bus/isapnp.c new file mode 100644 index 00000000..f4968eb1 --- /dev/null +++ b/gpxe/src/drivers/bus/isapnp.c @@ -0,0 +1,758 @@ +/************************************************************************** +* +* 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., 675 Mass Ave, Cambridge, MA 02139, 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. + * + */ + +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <errno.h> +#include <io.h> +#include <unistd.h> +#include <gpxe/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 struct isapnp_driver isapnp_drivers[0] + __table_start ( struct isapnp_driver, isapnp_drivers ); +static struct isapnp_driver isapnp_drivers_end[0] + __table_end ( struct isapnp_driver, isapnp_drivers ); + +static void isapnpbus_remove ( struct root_device *rootdev ); + +/* + * ISAPnP utility functions + * + */ + +#define ISAPNP_CARD_ID_FMT "ID %04x:%04x (\"%s\") serial %lx" +#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 ( driver = isapnp_drivers; driver < isapnp_drivers_end; driver++ ){ + 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->driver_name = id->name; + DBG ( "...using driver %s\n", isapnp->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/gpxe/src/drivers/bus/mca.c b/gpxe/src/drivers/bus/mca.c new file mode 100644 index 00000000..eb7b7e39 --- /dev/null +++ b/gpxe/src/drivers/bus/mca.c @@ -0,0 +1,180 @@ +/* + * MCA bus driver code + * + * Abstracted from 3c509.c. + * + */ + +#include <stdint.h> +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <io.h> +#include <gpxe/mca.h> + +static struct mca_driver mca_drivers[0] + __table_start ( struct mca_driver, mca_drivers ); +static struct mca_driver mca_drivers_end[0] + __table_end ( struct mca_driver, mca_drivers ); + +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 ( driver = mca_drivers; driver < mca_drivers_end; driver++ ){ + for ( i = 0 ; i < driver->id_count ; i++ ) { + id = &driver->ids[i]; + if ( id->id != MCA_ID ( mca ) ) + continue; + mca->driver = driver; + mca->driver_name = id->name; + DBG ( "...using driver %s\n", mca->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/gpxe/src/drivers/bus/pci.c b/gpxe/src/drivers/bus/pci.c new file mode 100644 index 00000000..967441ac --- /dev/null +++ b/gpxe/src/drivers/bus/pci.c @@ -0,0 +1,343 @@ +/* + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <stdint.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <gpxe/tables.h> +#include <gpxe/device.h> +#include <gpxe/pci.h> + +/** @file + * + * PCI bus + * + */ + +static struct pci_driver pci_drivers[0] + __table_start ( struct pci_driver, pci_drivers ); +static struct pci_driver pci_drivers_end[0] + __table_end ( struct pci_driver, pci_drivers ); + +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 { + DBG ( "Unhandled 64-bit BAR %08lx%08lx\n", + 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_IO; + if ( pci_command != new_command ) { + DBG ( "PCI BIOS has not enabled device %02x:%02x.%x! " + "Updating PCI command %04x->%04x\n", pci->bus, + PCI_SLOT ( pci->devfn ), PCI_FUNC ( pci->devfn ), + 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 ) { + DBG ( "PCI device %02x:%02x.%x latency timer is unreasonably " + "low at %d. Setting to 32.\n", pci->bus, + PCI_SLOT ( pci->devfn ), PCI_FUNC ( pci->devfn ), + pci_latency ); + pci_write_config_byte ( pci, PCI_LATENCY_TIMER, 32); + } +} + +/** + * 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. + */ +static int pci_probe ( struct pci_device *pci ) { + struct pci_driver *driver; + struct pci_device_id *id; + unsigned int i; + int rc; + + DBG ( "Adding PCI device %02x:%02x.%x (%04x:%04x mem %lx io %lx " + "irq %d)\n", pci->bus, PCI_SLOT ( pci->devfn ), + PCI_FUNC ( pci->devfn ), pci->vendor, pci->device, + pci->membase, pci->ioaddr, pci->irq ); + + for ( driver = pci_drivers ; driver < pci_drivers_end ; driver++ ) { + 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->driver = driver; + pci->driver_name = id->name; + DBG ( "...using driver %s\n", pci->driver_name ); + if ( ( rc = driver->probe ( pci, id ) ) != 0 ) { + DBG ( "......probe failed\n" ); + continue; + } + return 0; + } + } + + DBG ( "...no driver found\n" ); + return -ENOTTY; +} + +/** + * Remove a PCI device + * + * @v pci PCI device + */ +static void pci_remove ( struct pci_device *pci ) { + pci->driver->remove ( pci ); + DBG ( "Removed PCI device %02x:%02x.%x\n", pci->bus, + PCI_SLOT ( pci->devfn ), PCI_FUNC ( pci->devfn ) ); +} + +/** + * 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; + unsigned int max_bus; + unsigned int bus; + unsigned int devfn; + uint8_t hdrtype = 0; + uint32_t tmp; + int rc; + + max_bus = pci_max_bus(); + for ( bus = 0 ; bus <= max_bus ; bus++ ) { + for ( devfn = 0 ; devfn <= 0xff ; devfn++ ) { + + /* Allocate struct pci_device */ + if ( ! pci ) + pci = malloc ( sizeof ( *pci ) ); + if ( ! pci ) { + rc = -ENOMEM; + goto err; + } + memset ( pci, 0, sizeof ( *pci ) ); + pci->bus = bus; + pci->devfn = devfn; + + /* Skip all but the first function on + * non-multifunction cards + */ + if ( PCI_FUNC ( devfn ) == 0 ) { + pci_read_config_byte ( pci, PCI_HEADER_TYPE, + &hdrtype ); + } else if ( ! ( hdrtype & 0x80 ) ) { + continue; + } + + /* Check for physical device presence */ + pci_read_config_dword ( pci, PCI_VENDOR_ID, &tmp ); + if ( ( tmp == 0xffffffff ) || ( tmp == 0 ) ) + continue; + + /* 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 ); + + /* Add to device hierarchy */ + snprintf ( pci->dev.name, sizeof ( pci->dev.name ), + "PCI%02x:%02x.%x", bus, + PCI_SLOT ( devfn ), PCI_FUNC ( devfn ) ); + pci->dev.desc.bus_type = BUS_TYPE_PCI; + pci->dev.desc.location = PCI_BUSDEVFN (bus, devfn); + 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; + pci->dev.parent = &rootdev->dev; + list_add ( &pci->dev.siblings, &rootdev->dev.children); + INIT_LIST_HEAD ( &pci->dev.children ); + + /* Look for a driver */ + if ( 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/gpxe/src/drivers/bus/pciextra.c b/gpxe/src/drivers/bus/pciextra.c new file mode 100644 index 00000000..4603bcb9 --- /dev/null +++ b/gpxe/src/drivers/bus/pciextra.c @@ -0,0 +1,79 @@ +#include <stdint.h> +#include <gpxe/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 ) { + uint32_t start, size; + + /* 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 */ + 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; +} |