iPXE
pcivpd.c
Go to the documentation of this file.
00001 /*
00002  * Copyright (C) 2010 Michael Brown <mbrown@fensystems.co.uk>.
00003  *
00004  * This program is free software; you can redistribute it and/or
00005  * modify it under the terms of the GNU General Public License as
00006  * published by the Free Software Foundation; either version 2 of the
00007  * License, or any later version.
00008  *
00009  * This program is distributed in the hope that it will be useful, but
00010  * WITHOUT ANY WARRANTY; without even the implied warranty of
00011  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00012  * General Public License for more details.
00013  *
00014  * You should have received a copy of the GNU General Public License
00015  * along with this program; if not, write to the Free Software
00016  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
00017  * 02110-1301, USA.
00018  *
00019  * You can also choose to distribute this program under the terms of
00020  * the Unmodified Binary Distribution Licence (as given in the file
00021  * COPYING.UBDL), provided that you have satisfied its requirements.
00022  */
00023 
00024 FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
00025 
00026 #include <stdint.h>
00027 #include <stdlib.h>
00028 #include <unistd.h>
00029 #include <errno.h>
00030 #include <byteswap.h>
00031 #include <ipxe/pci.h>
00032 #include <ipxe/isapnp.h>
00033 #include <ipxe/pcivpd.h>
00034 
00035 /** @file
00036  *
00037  * PCI Vital Product Data
00038  *
00039  */
00040 
00041 /**
00042  * Initialise PCI Vital Product Data
00043  *
00044  * @v vpd               PCI VPD
00045  * @v pci               PCI device
00046  * @ret rc              Return status code
00047  */
00048 int pci_vpd_init ( struct pci_vpd *vpd, struct pci_device *pci ) {
00049 
00050         /* Initialise structure */
00051         vpd->pci = pci;
00052         pci_vpd_invalidate_cache ( vpd );
00053 
00054         /* Locate VPD capability */
00055         vpd->cap = pci_find_capability ( pci, PCI_CAP_ID_VPD );
00056         if ( ! vpd->cap ) {
00057                 DBGC ( vpd, PCI_FMT " does not support VPD\n",
00058                        PCI_ARGS ( pci ) );
00059                 return -ENOTTY;
00060         }
00061 
00062         DBGC ( vpd, PCI_FMT " VPD is at offset %02x\n",
00063                PCI_ARGS ( pci ), vpd->cap );
00064         return 0;
00065 }
00066 
00067 /**
00068  * Read one dword of PCI Vital Product Data
00069  *
00070  * @v vpd               PCI VPD
00071  * @v address           Address to read
00072  * @ret data            Read data
00073  * @ret rc              Return status code
00074  */
00075 static int pci_vpd_read_dword ( struct pci_vpd *vpd, int address,
00076                                 uint32_t *data ) {
00077         struct pci_device *pci = vpd->pci;
00078         unsigned int cap = vpd->cap;
00079         unsigned int retries;
00080         uint16_t flag;
00081 
00082         /* Fail if no VPD present */
00083         if ( ! cap )
00084                 return -ENOTTY;
00085 
00086         /* Return cached value, if present */
00087         if ( pci_vpd_cache_is_valid ( vpd ) &&
00088              ( vpd->cache.address == address ) ) {
00089                 *data = vpd->cache.data;
00090                 return 0;
00091         }
00092 
00093         /* Initiate read */
00094         pci_write_config_word ( pci, ( cap + PCI_VPD_ADDRESS ), address );
00095 
00096         /* Wait for read to complete */
00097         for ( retries = 0 ; retries < PCI_VPD_MAX_WAIT_MS ; retries++ ) {
00098 
00099                 /* Check if data is ready */
00100                 pci_read_config_word ( pci, ( cap + PCI_VPD_ADDRESS ), &flag );
00101                 if ( flag & PCI_VPD_FLAG ) {
00102 
00103                         /* Read data */
00104                         pci_read_config_dword ( pci, ( cap + PCI_VPD_DATA ),
00105                                                 data );
00106                         DBGC2 ( vpd, PCI_FMT " VPD %04x => %08x\n",
00107                                 PCI_ARGS ( pci ), address, htonl ( *data ) );
00108 
00109                         /* Populate cache */
00110                         vpd->cache.address = address;
00111                         vpd->cache.data = *data;
00112 
00113                         return 0;
00114                 }
00115 
00116                 /* Wait 1ms before retrying */
00117                 mdelay ( 1 );
00118         }
00119 
00120         DBGC ( vpd, PCI_FMT " VPD %04x read via %02x timed out\n",
00121                PCI_ARGS ( pci ), address, cap );
00122         return -ETIMEDOUT;
00123 }
00124 
00125 /**
00126  * Write one dword of PCI Vital Product Data
00127  *
00128  * @v vpd               PCI VPD
00129  * @v address           Address to write
00130  * @v data              Data to write
00131  * @ret rc              Return status code
00132  */
00133 static int pci_vpd_write_dword ( struct pci_vpd *vpd, int address,
00134                                  uint32_t data ) {
00135         struct pci_device *pci = vpd->pci;
00136         unsigned int cap = vpd->cap;
00137         unsigned int retries;
00138         uint16_t flag;
00139 
00140         /* Fail if no VPD present */
00141         if ( ! cap )
00142                 return -ENOTTY;
00143 
00144         /* Invalidate cache */
00145         pci_vpd_invalidate_cache ( vpd );
00146 
00147         DBGC2 ( vpd, PCI_FMT " VPD %04x <= %08x\n",
00148                 PCI_ARGS ( pci ), address, htonl ( data ) );
00149 
00150         /* Write data */
00151         pci_write_config_dword ( pci, ( cap + PCI_VPD_DATA ), data );
00152 
00153         /* Initiate write */
00154         pci_write_config_word ( pci, ( cap + PCI_VPD_ADDRESS ),
00155                                 ( address | PCI_VPD_FLAG ) );
00156 
00157         /* Wait for write to complete */
00158         for ( retries = 0 ; retries < PCI_VPD_MAX_WAIT_MS ; retries++ ) {
00159 
00160                 /* Check if write has completed */
00161                 pci_read_config_word ( pci, ( cap + PCI_VPD_ADDRESS ), &flag );
00162                 if ( ! ( flag & PCI_VPD_FLAG ) )
00163                         return 0;
00164 
00165                 /* Wait 1ms before retrying */
00166                 mdelay ( 1 );
00167         }
00168 
00169         DBGC ( vpd, PCI_FMT " VPD %04x write via %02x timed out\n",
00170                PCI_ARGS ( pci ), address, cap );
00171         return -ETIMEDOUT;
00172 }
00173 
00174 /**
00175  * Read PCI VPD
00176  *
00177  * @v vpd               PCI VPD
00178  * @v address           Starting address
00179  * @v buf               Data buffer
00180  * @v len               Length of data buffer
00181  * @ret rc              Return status code
00182  */
00183 int pci_vpd_read ( struct pci_vpd *vpd, unsigned int address, void *buf,
00184                    size_t len ) {
00185         uint8_t *bytes = buf;
00186         uint32_t data;
00187         size_t skip_len;
00188         unsigned int i;
00189         int rc;
00190 
00191         /* Calculate length to skip at start of data */
00192         skip_len = ( address & 0x03 );
00193 
00194         /* Read data, a dword at a time */
00195         for ( address &= ~0x03 ; len ; address += 4 ) {
00196 
00197                 /* Read whole dword */
00198                 if ( ( rc = pci_vpd_read_dword ( vpd, address, &data ) ) != 0 )
00199                         return rc;
00200 
00201                 /* Copy data to buffer */
00202                 for ( i = 4 ; i ; i-- ) {
00203                         if ( skip_len ) {
00204                                 skip_len--;
00205                         } else if ( len ) {
00206                                 *(bytes++) = data;
00207                                 len--;
00208                         }
00209                         data = ( ( data << 24 ) | ( data >> 8 ) );
00210                 }
00211         }
00212 
00213         return 0;
00214 }
00215 
00216 /**
00217  * Write PCI VPD
00218  *
00219  * @v vpd               PCI VPD
00220  * @v address           Starting address
00221  * @v buf               Data buffer
00222  * @v len               Length of data buffer
00223  * @ret rc              Return status code
00224  */
00225 int pci_vpd_write ( struct pci_vpd *vpd, unsigned int address, const void *buf,
00226                     size_t len ) {
00227         const uint8_t *bytes = buf;
00228         uint32_t data;
00229         size_t skip_len;
00230         unsigned int i;
00231         int rc;
00232 
00233         /* Calculate length to skip at start of data */
00234         skip_len = ( address & 0x03 );
00235 
00236         /* Write data, a dword at a time */
00237         for ( address &= ~0x03 ; len ; address += 4 ) {
00238 
00239                 /* Read existing dword, if necessary */
00240                 if ( skip_len || ( len <= 0x03 ) ) {
00241                         if ( ( rc = pci_vpd_read_dword ( vpd, address,
00242                                                          &data ) ) != 0 )
00243                                 return rc;
00244                 }
00245 
00246                 /* Copy data from buffer */
00247                 for ( i = 4 ; i ; i-- ) {
00248                         if ( skip_len ) {
00249                                 skip_len--;
00250                         } else if ( len ) {
00251                                 data = ( ( data & ~0xff ) | *(bytes++) );
00252                                 len--;
00253                         }
00254                         data = ( ( data << 24 ) | ( data >> 8 ) );
00255                 }
00256 
00257                 /* Write whole dword */
00258                 if ( ( rc = pci_vpd_write_dword ( vpd, address, data ) ) != 0 )
00259                         return rc;
00260         }
00261         return 0;
00262 }
00263 
00264 /**
00265  * Dump PCI VPD region (for debugging)
00266  *
00267  * @v vpd               PCI VPD
00268  * @v address           Starting address
00269  * @v len               Length of data
00270  */
00271 static void pci_vpd_dump ( struct pci_vpd *vpd, unsigned int address,
00272                            size_t len ) {
00273         int rc;
00274 
00275         /* Do nothing in non-debug builds */
00276         if ( ! DBG_LOG )
00277                 return;
00278 
00279         /* Read data */
00280         {
00281                 char buf[len];
00282                 if ( ( rc = pci_vpd_read ( vpd, address, buf,
00283                                            sizeof ( buf ) ) ) != 0 )
00284                         return;
00285                 DBGC_HDA ( vpd, address, buf, sizeof ( buf ) );
00286         }
00287 }
00288 
00289 /**
00290  * Locate PCI VPD tag
00291  *
00292  * @v vpd               PCI VPD
00293  * @v tag               ISAPnP tag
00294  * @ret address         Address of tag body
00295  * @ret len             Length of tag body
00296  * @ret rc              Return status code
00297  */
00298 static int pci_vpd_find_tag ( struct pci_vpd *vpd, unsigned int tag,
00299                               unsigned int *address, size_t *len ) {
00300         uint8_t read_tag;
00301         uint16_t read_len;
00302         int rc;
00303 
00304         /* Scan through tags looking for a match */
00305         *address = 0;
00306         do {
00307                 /* Read tag byte */
00308                 if ( ( rc = pci_vpd_read ( vpd, (*address)++, &read_tag,
00309                                            sizeof ( read_tag ) ) ) != 0 )
00310                         return rc;
00311 
00312                 /* Extract tag and length */
00313                 if ( ISAPNP_IS_LARGE_TAG ( read_tag ) ) {
00314                         if ( ( rc = pci_vpd_read ( vpd, *address, &read_len,
00315                                                    sizeof ( read_len ) ) ) != 0)
00316                                 return rc;
00317                         *address += sizeof ( read_len );
00318                         read_len = le16_to_cpu ( read_len );
00319                         read_tag = ISAPNP_LARGE_TAG_NAME ( read_tag );
00320                 } else {
00321                         read_len = ISAPNP_SMALL_TAG_LEN ( read_tag );
00322                         read_tag = ISAPNP_SMALL_TAG_NAME ( read_tag );
00323                 }
00324 
00325                 /* Check for tag match */
00326                 if ( tag == read_tag ) {
00327                         *len = read_len;
00328                         DBGC ( vpd, PCI_FMT " VPD tag %02x is at "
00329                                "[%04x,%04zx)\n", PCI_ARGS ( vpd->pci ), tag,
00330                                *address, ( *address + *len ) );
00331                         return 0;
00332                 }
00333 
00334                 /* Move to next tag */
00335                 *address += read_len;
00336 
00337         } while ( read_tag != ISAPNP_TAG_END );
00338 
00339         DBGC ( vpd, PCI_FMT " VPD tag %02x not found\n",
00340                PCI_ARGS ( vpd->pci ), tag );
00341         return -ENOENT;
00342 }
00343 
00344 /**
00345  * Locate PCI VPD field
00346  *
00347  * @v vpd               PCI VPD
00348  * @v field             VPD field descriptor
00349  * @ret address         Address of field body
00350  * @ret len             Length of field body
00351  * @ret rc              Return status code
00352  */
00353 int pci_vpd_find ( struct pci_vpd *vpd, unsigned int field,
00354                    unsigned int *address, size_t *len ) {
00355         struct pci_vpd_field read_field;
00356         int rc;
00357 
00358         /* Locate containing tag */
00359         if ( ( rc = pci_vpd_find_tag ( vpd, PCI_VPD_TAG ( field ),
00360                                        address, len ) ) != 0 )
00361                 return rc;
00362 
00363         /* Return immediately if we are searching for a whole-tag field */
00364         if ( ! PCI_VPD_KEYWORD ( field ) ) {
00365                 pci_vpd_dump ( vpd, *address, *len );
00366                 return 0;
00367         }
00368 
00369         /* Scan through fields looking for a match */
00370         while ( *len >= sizeof ( read_field ) ) {
00371 
00372                 /* Read field header */
00373                 if ( ( rc = pci_vpd_read ( vpd, *address, &read_field,
00374                                            sizeof ( read_field ) ) ) != 0 )
00375                         return rc;
00376                 *address += sizeof ( read_field );
00377                 *len -= sizeof ( read_field );
00378 
00379                 /* Check for keyword match */
00380                 if ( read_field.keyword == PCI_VPD_KEYWORD ( field ) ) {
00381                         *len = read_field.len;
00382                         DBGC ( vpd, PCI_FMT " VPD field " PCI_VPD_FIELD_FMT
00383                                " is at [%04x,%04zx)\n", PCI_ARGS ( vpd->pci ),
00384                                PCI_VPD_FIELD_ARGS ( field ),
00385                                *address, ( *address + *len ) );
00386                         pci_vpd_dump ( vpd, *address, *len );
00387                         return 0;
00388                 }
00389 
00390                 /* Move to next field */
00391                 if ( read_field.len > *len )
00392                         break;
00393                 *address += read_field.len;
00394                 *len -= read_field.len;
00395         }
00396 
00397         DBGC ( vpd, PCI_FMT " VPD field " PCI_VPD_FIELD_FMT " not found\n",
00398                PCI_ARGS ( vpd->pci ), PCI_VPD_FIELD_ARGS ( field ) );
00399         return -ENOENT;
00400 }
00401 
00402 /**
00403  * Resize VPD field
00404  *
00405  * @v vpd               PCI VPD
00406  * @v field             VPD field descriptor
00407  * @v len               New length of field body
00408  * @ret address         Address of field body
00409  * @ret rc              Return status code
00410  */
00411 int pci_vpd_resize ( struct pci_vpd *vpd, unsigned int field, size_t len,
00412                      unsigned int *address ) {
00413         struct pci_vpd_field rw_field;
00414         struct pci_vpd_field old_field;
00415         struct pci_vpd_field new_field;
00416         unsigned int rw_address;
00417         unsigned int old_address;
00418         unsigned int copy_address;
00419         unsigned int dst_address;
00420         unsigned int dump_address;
00421         size_t rw_len;
00422         size_t old_len;
00423         size_t available_len;
00424         size_t copy_len;
00425         size_t dump_len;
00426         void *copy;
00427         int rc;
00428 
00429         /* Sanity checks */
00430         assert ( PCI_VPD_TAG ( field ) == PCI_VPD_TAG_RW );
00431         assert ( PCI_VPD_KEYWORD ( field ) != 0 );
00432         assert ( field != PCI_VPD_FIELD_RW );
00433 
00434         /* Locate 'RW' field */
00435         if ( ( rc = pci_vpd_find ( vpd, PCI_VPD_FIELD_RW, &rw_address,
00436                                    &rw_len ) ) != 0 )
00437                 goto err_no_rw;
00438 
00439         /* Locate old field, if any */
00440         if ( ( rc = pci_vpd_find ( vpd, field, &old_address,
00441                                    &old_len ) ) == 0 ) {
00442 
00443                 /* Field already exists */
00444                 if ( old_address > rw_address ) {
00445                         DBGC ( vpd, PCI_FMT " VPD field " PCI_VPD_FIELD_FMT
00446                                " at [%04x,%04zx) is after field "
00447                                PCI_VPD_FIELD_FMT " at [%04x,%04zx)\n",
00448                                PCI_ARGS ( vpd->pci ),
00449                                PCI_VPD_FIELD_ARGS ( field ),
00450                                old_address, ( old_address + old_len ),
00451                                PCI_VPD_FIELD_ARGS ( PCI_VPD_FIELD_RW ),
00452                                rw_address, ( rw_address + rw_len ) );
00453                         rc = -ENXIO;
00454                         goto err_after_rw;
00455                 }
00456                 dst_address = ( old_address - sizeof ( old_field ) );
00457                 copy_address = ( old_address + old_len );
00458                 copy_len = ( rw_address - sizeof ( rw_field ) - copy_address );
00459 
00460                 /* Calculate available length */
00461                 available_len = ( rw_len + old_len );
00462 
00463         } else {
00464 
00465                 /* Field does not yet exist */
00466                 dst_address = ( rw_address - sizeof ( rw_field ) );
00467                 copy_address = dst_address;
00468                 copy_len = 0;
00469 
00470                 /* Calculate available length */
00471                 available_len = ( ( rw_len > sizeof ( new_field ) ) ?
00472                                   ( rw_len - sizeof ( new_field ) ) : 0 );
00473         }
00474 
00475         /* Dump region before changes */
00476         dump_address = dst_address;
00477         dump_len = ( rw_address + rw_len - dump_address );
00478         DBGC ( vpd, PCI_FMT " VPD before resizing field " PCI_VPD_FIELD_FMT
00479                " to %zd bytes:\n", PCI_ARGS ( vpd->pci ),
00480                PCI_VPD_FIELD_ARGS ( field ), len );
00481         pci_vpd_dump ( vpd, dump_address, dump_len );
00482 
00483         /* Check available length */
00484         if ( available_len > PCI_VPD_MAX_LEN )
00485                 available_len = PCI_VPD_MAX_LEN;
00486         if ( len > available_len ) {
00487                 DBGC ( vpd, PCI_FMT " VPD no space for field "
00488                        PCI_VPD_FIELD_FMT " (need %02zx, have %02zx)\n",
00489                        PCI_ARGS ( vpd->pci ), PCI_VPD_FIELD_ARGS ( field ),
00490                        len, available_len );
00491                 rc = -ENOSPC;
00492                 goto err_no_space;
00493         }
00494 
00495         /* Preserve intermediate fields, if any */
00496         copy = malloc ( copy_len );
00497         if ( ! copy ) {
00498                 rc = -ENOMEM;
00499                 goto err_copy_alloc;
00500         }
00501         if ( ( rc = pci_vpd_read ( vpd, copy_address, copy, copy_len ) ) != 0 )
00502                 goto err_copy_read;
00503 
00504         /* Create new field, if applicable */
00505         if ( len ) {
00506                 new_field.keyword = PCI_VPD_KEYWORD ( field );
00507                 new_field.len = len;
00508                 if ( ( rc = pci_vpd_write ( vpd, dst_address, &new_field,
00509                                             sizeof ( new_field ) ) ) != 0 )
00510                         goto err_new_write;
00511                 dst_address += sizeof ( new_field );
00512                 *address = dst_address;
00513                 DBGC ( vpd, PCI_FMT " VPD field " PCI_VPD_FIELD_FMT " is now "
00514                        "at [%04x,%04x)\n", PCI_ARGS ( vpd->pci ),
00515                        PCI_VPD_FIELD_ARGS ( field ), dst_address,
00516                        ( dst_address + new_field.len ) );
00517                 dst_address += len;
00518         } else {
00519                 DBGC ( vpd, PCI_FMT " VPD field " PCI_VPD_FIELD_FMT
00520                        " no longer exists\n", PCI_ARGS ( vpd->pci ),
00521                        PCI_VPD_FIELD_ARGS ( field ) );
00522         }
00523 
00524         /* Restore intermediate fields, if any */
00525         if ( ( rc = pci_vpd_write ( vpd, dst_address, copy, copy_len ) ) != 0 )
00526                 goto err_copy_write;
00527         dst_address += copy_len;
00528 
00529         /* Create 'RW' field */
00530         rw_field.keyword = PCI_VPD_KEYWORD ( PCI_VPD_FIELD_RW );
00531         rw_field.len = ( rw_len +
00532                          ( rw_address - sizeof ( rw_field ) ) - dst_address );
00533         if ( ( rc = pci_vpd_write ( vpd, dst_address, &rw_field,
00534                                     sizeof ( rw_field ) ) ) != 0 )
00535                 goto err_rw_write;
00536         dst_address += sizeof ( rw_field );
00537         DBGC ( vpd, PCI_FMT " VPD field " PCI_VPD_FIELD_FMT " is now "
00538                "at [%04x,%04x)\n", PCI_ARGS ( vpd->pci ),
00539                PCI_VPD_FIELD_ARGS ( PCI_VPD_FIELD_RW ), dst_address,
00540                ( dst_address + rw_field.len ) );
00541 
00542         /* Dump region after changes */
00543         DBGC ( vpd, PCI_FMT " VPD after resizing field " PCI_VPD_FIELD_FMT
00544                " to %zd bytes:\n", PCI_ARGS ( vpd->pci ),
00545                PCI_VPD_FIELD_ARGS ( field ), len );
00546         pci_vpd_dump ( vpd, dump_address, dump_len );
00547 
00548         rc = 0;
00549 
00550  err_rw_write:
00551  err_new_write:
00552  err_copy_write:
00553  err_copy_read:
00554         free ( copy );
00555  err_copy_alloc:
00556  err_no_space:
00557  err_after_rw:
00558  err_no_rw:
00559         return rc;
00560 }