/* * Copyright (C) 2011 Michael Brown . * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * You can also choose to distribute this program under the terms of * the Unmodified Binary Distribution Licence (as given in the file * COPYING.UBDL), provided that you have satisfied its requirements. */ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include #include #include #include #include #include #include /** @file * * IBM BladeCenter Open Fabric Manager (BOFM) * */ /** List of BOFM devices */ static LIST_HEAD ( bofmdevs ); /** * Register BOFM device * * @v bofm BOFM device * @ret rc Return status code */ int bofm_register ( struct bofm_device *bofm ) { list_add ( &bofm->list, &bofmdevs ); DBG ( "BOFM: " PCI_FMT " registered using driver \"%s\"\n", PCI_ARGS ( bofm->pci ), bofm->pci->id->name ); return 0; } /** * Unregister BOFM device * * @v bofm BOFM device */ void bofm_unregister ( struct bofm_device *bofm ) { list_del ( &bofm->list ); DBG ( "BOFM: " PCI_FMT " unregistered\n", PCI_ARGS ( bofm->pci ) ); } /** * Find BOFM device matching PCI bus:dev.fn address * * @v busdevfn PCI bus:dev.fn address * @ret bofm BOFM device, or NULL */ static struct bofm_device * bofm_find_busdevfn ( unsigned int busdevfn ) { struct bofm_device *bofm; list_for_each_entry ( bofm, &bofmdevs, list ) { if ( bofm->pci->busdevfn == busdevfn ) return bofm; } return NULL; } /** * Find BOFM driver for PCI device * * @v pci PCI device * @ret rc Return status code */ int bofm_find_driver ( struct pci_device *pci ) { struct pci_driver *driver; struct pci_device_id *id; unsigned int i; for_each_table_entry ( driver, BOFM_DRIVERS ) { for ( i = 0 ; i < driver->id_count ; i++ ) { id = &driver->ids[i]; if ( ( id->vendor == pci->vendor ) && ( id->device == pci->device ) ) { pci_set_driver ( pci, driver, id ); return 0; } } } return -ENOENT; } /** * Probe PCI device for BOFM driver * * @v pci PCI device * @ret rc Return status code */ static int bofm_probe ( struct pci_device *pci ) { int rc; /* Probe device */ if ( ( rc = pci_probe ( pci ) ) != 0 ) { DBG ( "BOFM: " PCI_FMT " could not load driver: %s\n", PCI_ARGS ( pci ), strerror ( rc ) ); return rc; } return 0; } /** * Remove PCI device * * @v pci PCI device */ static void bofm_remove ( struct pci_device *pci ) { /* Note that the IBM BIOS may re-read the expansion ROM after * the BOFM initialisation call. The BOFM driver must ensure * that the card is left in a state in which expansion ROM * reads will succeed. (For example, if a card contains an * embedded CPU that may issue reads to the same underlying * flash device, and these reads are not locked against reads * via the expansion ROM BAR, then the CPU must be stopped.) * * If this is not done, then occasional corrupted reads from * the expansion ROM will be seen, and the BIOS may complain * about a ROM checksum error. */ pci_remove ( pci ); DBG ( "BOFM: " PCI_FMT " removed\n", PCI_ARGS ( pci ) ); } /** * Locate BOFM table section * * @v bofmtab BOFM table * @v len Length of BOFM table * @v magic Section magic * @v bofmsec BOFM section header to fill in * @ret offset Offset to section, or 0 if not found */ static size_t bofm_locate_section ( userptr_t bofmtab, size_t len, uint32_t magic, struct bofm_section_header *bofmsec ) { size_t offset = sizeof ( struct bofm_global_header ); while ( offset < len ) { copy_from_user ( bofmsec, bofmtab, offset, sizeof ( *bofmsec ) ); if ( bofmsec->magic == magic ) return offset; if ( bofmsec->magic == BOFM_DONE_MAGIC ) break; offset += ( sizeof ( *bofmsec ) + bofmsec->length ); } return 0; } /** * Process BOFM Ethernet parameter entry * * @v bofm BOFM device * @v en EN parameter entry * @ret rc Return status code */ static int bofm_en ( struct bofm_device *bofm, struct bofm_en *en ) { uint8_t mac[6]; int rc; /* Retrieve current MAC address */ if ( ( rc = bofm->op->harvest ( bofm, en->mport, mac ) ) != 0 ) { DBG ( "BOFM: " PCI_FMT " mport %d could not harvest: %s\n", PCI_ARGS ( bofm->pci ), en->mport, strerror ( rc ) ); return rc; } /* Harvest MAC address if necessary */ if ( en->options & BOFM_EN_RQ_HVST_MASK ) { DBG ( "BOFM: " PCI_FMT " mport %d harvested MAC %s\n", PCI_ARGS ( bofm->pci ), en->mport, eth_ntoa ( mac ) ); memcpy ( en->mac_a, mac, sizeof ( en->mac_a ) ); en->options |= ( BOFM_EN_EN_A | BOFM_EN_HVST ); } /* Mark as changed if necessary */ if ( ( en->options & BOFM_EN_EN_A ) && ( memcmp ( en->mac_a, mac, sizeof ( en->mac_a ) ) != 0 ) ) { DBG ( "BOFM: " PCI_FMT " mport %d MAC %s", PCI_ARGS ( bofm->pci ), en->mport, eth_ntoa ( mac ) ); DBG ( " changed to %s\n", eth_ntoa ( en->mac_a ) ); en->options |= BOFM_EN_CHG_CHANGED; } /* Apply MAC address if necessary */ if ( ( en->options & BOFM_EN_EN_A ) && ( en->options & BOFM_EN_USAGE_ENTRY ) && ( ! ( en->options & BOFM_EN_USAGE_HARVEST ) ) ) { DBG ( "BOFM: " PCI_FMT " mport %d applied MAC %s\n", PCI_ARGS ( bofm->pci ), en->mport, eth_ntoa ( en->mac_a ) ); memcpy ( mac, en->mac_a, sizeof ( mac ) ); } /* Store MAC address */ if ( ( rc = bofm->op->update ( bofm, en->mport, mac ) ) != 0 ) { DBG ( "BOFM: " PCI_FMT " mport %d could not update: %s\n", PCI_ARGS ( bofm->pci ), en->mport, strerror ( rc ) ); return rc; } return 0; } /** * Process BOFM table * * @v bofmtab BOFM table * @v pci PCI device * @ret bofmrc BOFM return status */ int bofm ( userptr_t bofmtab, struct pci_device *pci ) { struct bofm_global_header bofmhdr; struct bofm_section_header bofmsec; struct bofm_en en; struct bofm_device *bofm; size_t en_region_offset; size_t en_offset; int skip; int rc; int bofmrc; /* Read BOFM structure */ copy_from_user ( &bofmhdr, bofmtab, 0, sizeof ( bofmhdr ) ); if ( bofmhdr.magic != BOFM_IOAA_MAGIC ) { DBG ( "BOFM: invalid table signature " BOFM_MAGIC_FMT "\n", BOFM_MAGIC_ARGS ( bofmhdr.magic ) ); bofmrc = BOFM_ERR_INVALID_ACTION; goto err_bad_signature; } DBG ( "BOFM: " BOFM_MAGIC_FMT " (profile \"%s\")\n", BOFM_MAGIC_ARGS ( bofmhdr.action ), bofmhdr.profile ); /* Determine whether or not we should skip normal POST * initialisation. */ switch ( bofmhdr.action ) { case BOFM_ACTION_UPDT: case BOFM_ACTION_DFLT: case BOFM_ACTION_HVST: skip = BOFM_SKIP_INIT; break; case BOFM_ACTION_PARM: case BOFM_ACTION_NONE: skip = 0; break; default: DBG ( "BOFM: invalid action " BOFM_MAGIC_FMT "\n", BOFM_MAGIC_ARGS ( bofmhdr.action ) ); bofmrc = BOFM_ERR_INVALID_ACTION; goto err_bad_action; } /* Find BOFM driver */ if ( ( rc = bofm_find_driver ( pci ) ) != 0 ) { DBG ( "BOFM: " PCI_FMT " has no driver\n", PCI_ARGS ( pci ) ); bofmrc = BOFM_ERR_DEVICE_ERROR; goto err_find_driver; } /* Probe driver for PCI device */ if ( ( rc = bofm_probe ( pci ) ) != 0 ) { bofmrc = BOFM_ERR_DEVICE_ERROR; goto err_probe; } /* Locate EN section, if present */ en_region_offset = bofm_locate_section ( bofmtab, bofmhdr.length, BOFM_EN_MAGIC, &bofmsec ); if ( ! en_region_offset ) { DBG ( "BOFM: No EN section found\n" ); bofmrc = ( BOFM_SUCCESS | skip ); goto err_no_en_section; } /* Iterate through EN entries */ for ( en_offset = ( en_region_offset + sizeof ( bofmsec ) ) ; en_offset < ( en_region_offset + sizeof ( bofmsec ) + bofmsec.length ) ; en_offset += sizeof ( en ) ) { copy_from_user ( &en, bofmtab, en_offset, sizeof ( en ) ); DBG2 ( "BOFM: EN entry found:\n" ); DBG2_HDA ( en_offset, &en, sizeof ( en ) ); if ( ( en.options & BOFM_EN_MAP_MASK ) != BOFM_EN_MAP_PFA ) { DBG ( "BOFM: slot %d port %d has no PCI mapping\n", en.slot, ( en.port + 1 ) ); continue; } DBG ( "BOFM: slot %d port %d%s is " PCI_FMT " mport %d\n", en.slot, ( en.port + 1 ), ( ( en.slot || en.port ) ? "" : "(?)" ), 0, PCI_BUS ( en.busdevfn ), PCI_SLOT ( en.busdevfn ), PCI_FUNC ( en.busdevfn ), en.mport ); bofm = bofm_find_busdevfn ( en.busdevfn ); if ( ! bofm ) { DBG ( "BOFM: " PCI_FMT " mport %d ignored\n", 0, PCI_BUS ( en.busdevfn ), PCI_SLOT ( en.busdevfn ), PCI_FUNC ( en.busdevfn ), en.mport ); continue; } if ( ( rc = bofm_en ( bofm, &en ) ) == 0 ) { en.options |= BOFM_EN_CSM_SUCCESS; } else { en.options |= BOFM_EN_CSM_FAILED; } DBG2 ( "BOFM: EN entry after processing:\n" ); DBG2_HDA ( en_offset, &en, sizeof ( en ) ); copy_to_user ( bofmtab, en_offset, &en, sizeof ( en ) ); } bofmrc = ( BOFM_SUCCESS | skip ); err_no_en_section: bofm_remove ( pci ); err_probe: err_find_driver: err_bad_action: err_bad_signature: return bofmrc; }