| /************************************************************************** |
| * |
| * 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. |
| * |
| */ |
| |
| FILE_LICENCE ( GPL2_OR_LATER ); |
| |
| #include <stdint.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <stdio.h> |
| #include <errno.h> |
| #include <gpxe/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 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->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, |
| }; |