|
|
1.1 ! root 1: /* ! 2: * Copyright (C) 2010 Michael Brown <[email protected]>. ! 3: * ! 4: * This program is free software; you can redistribute it and/or ! 5: * modify it under the terms of the GNU General Public License as ! 6: * published by the Free Software Foundation; either version 2 of the ! 7: * License, or any later version. ! 8: * ! 9: * This program is distributed in the hope that it will be useful, but ! 10: * WITHOUT ANY WARRANTY; without even the implied warranty of ! 11: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ! 12: * General Public License for more details. ! 13: * ! 14: * You should have received a copy of the GNU General Public License ! 15: * along with this program; if not, write to the Free Software ! 16: * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ! 17: */ ! 18: ! 19: FILE_LICENCE ( GPL2_OR_LATER ); ! 20: ! 21: #include <stdint.h> ! 22: #include <stdlib.h> ! 23: #include <unistd.h> ! 24: #include <errno.h> ! 25: #include <byteswap.h> ! 26: #include <ipxe/pci.h> ! 27: #include <ipxe/isapnp.h> ! 28: #include <ipxe/pcivpd.h> ! 29: ! 30: /** @file ! 31: * ! 32: * PCI Vital Product Data ! 33: * ! 34: */ ! 35: ! 36: /** ! 37: * Initialise PCI Vital Product Data ! 38: * ! 39: * @v vpd PCI VPD ! 40: * @v pci PCI device ! 41: * @ret rc Return status code ! 42: */ ! 43: int pci_vpd_init ( struct pci_vpd *vpd, struct pci_device *pci ) { ! 44: ! 45: /* Initialise structure */ ! 46: vpd->pci = pci; ! 47: pci_vpd_invalidate_cache ( vpd ); ! 48: ! 49: /* Locate VPD capability */ ! 50: vpd->cap = pci_find_capability ( pci, PCI_CAP_ID_VPD ); ! 51: if ( ! vpd->cap ) { ! 52: DBGC ( vpd, PCI_FMT " does not support VPD\n", ! 53: PCI_ARGS ( pci ) ); ! 54: return -ENOTTY; ! 55: } ! 56: ! 57: DBGC ( vpd, PCI_FMT " VPD is at offset %02x\n", ! 58: PCI_ARGS ( pci ), vpd->cap ); ! 59: return 0; ! 60: } ! 61: ! 62: /** ! 63: * Read one dword of PCI Vital Product Data ! 64: * ! 65: * @v vpd PCI VPD ! 66: * @v address Address to read ! 67: * @ret data Read data ! 68: * @ret rc Return status code ! 69: */ ! 70: static int pci_vpd_read_dword ( struct pci_vpd *vpd, int address, ! 71: uint32_t *data ) { ! 72: struct pci_device *pci = vpd->pci; ! 73: unsigned int cap = vpd->cap; ! 74: unsigned int retries; ! 75: uint16_t flag; ! 76: ! 77: /* Fail if no VPD present */ ! 78: if ( ! cap ) ! 79: return -ENOTTY; ! 80: ! 81: /* Return cached value, if present */ ! 82: if ( pci_vpd_cache_is_valid ( vpd ) && ! 83: ( vpd->cache.address == address ) ) { ! 84: *data = vpd->cache.data; ! 85: return 0; ! 86: } ! 87: ! 88: /* Initiate read */ ! 89: pci_write_config_word ( pci, ( cap + PCI_VPD_ADDRESS ), address ); ! 90: ! 91: /* Wait for read to complete */ ! 92: for ( retries = 0 ; retries < PCI_VPD_MAX_WAIT_MS ; retries++ ) { ! 93: ! 94: /* Check if data is ready */ ! 95: pci_read_config_word ( pci, ( cap + PCI_VPD_ADDRESS ), &flag ); ! 96: if ( flag & PCI_VPD_FLAG ) { ! 97: ! 98: /* Read data */ ! 99: pci_read_config_dword ( pci, ( cap + PCI_VPD_DATA ), ! 100: data ); ! 101: DBGC2 ( vpd, PCI_FMT " VPD %04x => %08x\n", ! 102: PCI_ARGS ( pci ), address, htonl ( *data ) ); ! 103: ! 104: /* Populate cache */ ! 105: vpd->cache.address = address; ! 106: vpd->cache.data = *data; ! 107: ! 108: return 0; ! 109: } ! 110: ! 111: /* Wait 1ms before retrying */ ! 112: mdelay ( 1 ); ! 113: } ! 114: ! 115: DBGC ( vpd, PCI_FMT " VPD %04x read via %02x timed out\n", ! 116: PCI_ARGS ( pci ), address, cap ); ! 117: return -ETIMEDOUT; ! 118: } ! 119: ! 120: /** ! 121: * Write one dword of PCI Vital Product Data ! 122: * ! 123: * @v vpd PCI VPD ! 124: * @v address Address to write ! 125: * @v data Data to write ! 126: * @ret rc Return status code ! 127: */ ! 128: static int pci_vpd_write_dword ( struct pci_vpd *vpd, int address, ! 129: uint32_t data ) { ! 130: struct pci_device *pci = vpd->pci; ! 131: unsigned int cap = vpd->cap; ! 132: unsigned int retries; ! 133: uint16_t flag; ! 134: ! 135: /* Fail if no VPD present */ ! 136: if ( ! cap ) ! 137: return -ENOTTY; ! 138: ! 139: /* Invalidate cache */ ! 140: pci_vpd_invalidate_cache ( vpd ); ! 141: ! 142: DBGC2 ( vpd, PCI_FMT " VPD %04x <= %08x\n", ! 143: PCI_ARGS ( pci ), address, htonl ( data ) ); ! 144: ! 145: /* Write data */ ! 146: pci_write_config_dword ( pci, ( cap + PCI_VPD_DATA ), data ); ! 147: ! 148: /* Initiate write */ ! 149: pci_write_config_word ( pci, ( cap + PCI_VPD_ADDRESS ), ! 150: ( address | PCI_VPD_FLAG ) ); ! 151: ! 152: /* Wait for write to complete */ ! 153: for ( retries = 0 ; retries < PCI_VPD_MAX_WAIT_MS ; retries++ ) { ! 154: ! 155: /* Check if write has completed */ ! 156: pci_read_config_word ( pci, ( cap + PCI_VPD_ADDRESS ), &flag ); ! 157: if ( ! ( flag & PCI_VPD_FLAG ) ) ! 158: return 0; ! 159: ! 160: /* Wait 1ms before retrying */ ! 161: mdelay ( 1 ); ! 162: } ! 163: ! 164: DBGC ( vpd, PCI_FMT " VPD %04x write via %02x timed out\n", ! 165: PCI_ARGS ( pci ), address, cap ); ! 166: return -ETIMEDOUT; ! 167: } ! 168: ! 169: /** ! 170: * Read PCI VPD ! 171: * ! 172: * @v vpd PCI VPD ! 173: * @v address Starting address ! 174: * @v buf Data buffer ! 175: * @v len Length of data buffer ! 176: * @ret rc Return status code ! 177: */ ! 178: int pci_vpd_read ( struct pci_vpd *vpd, unsigned int address, void *buf, ! 179: size_t len ) { ! 180: uint8_t *bytes = buf; ! 181: uint32_t data; ! 182: size_t skip_len; ! 183: unsigned int i; ! 184: int rc; ! 185: ! 186: /* Calculate length to skip at start of data */ ! 187: skip_len = ( address & 0x03 ); ! 188: ! 189: /* Read data, a dword at a time */ ! 190: for ( address &= ~0x03 ; len ; address += 4 ) { ! 191: ! 192: /* Read whole dword */ ! 193: if ( ( rc = pci_vpd_read_dword ( vpd, address, &data ) ) != 0 ) ! 194: return rc; ! 195: ! 196: /* Copy data to buffer */ ! 197: for ( i = 4 ; i ; i-- ) { ! 198: if ( skip_len ) { ! 199: skip_len--; ! 200: } else if ( len ) { ! 201: *(bytes++) = data; ! 202: len--; ! 203: } ! 204: data = ( ( data << 24 ) | ( data >> 8 ) ); ! 205: } ! 206: } ! 207: ! 208: return 0; ! 209: } ! 210: ! 211: /** ! 212: * Write PCI VPD ! 213: * ! 214: * @v vpd PCI VPD ! 215: * @v address Starting address ! 216: * @v buf Data buffer ! 217: * @v len Length of data buffer ! 218: * @ret rc Return status code ! 219: */ ! 220: int pci_vpd_write ( struct pci_vpd *vpd, unsigned int address, const void *buf, ! 221: size_t len ) { ! 222: const uint8_t *bytes = buf; ! 223: uint32_t data; ! 224: size_t skip_len; ! 225: unsigned int i; ! 226: int rc; ! 227: ! 228: /* Calculate length to skip at start of data */ ! 229: skip_len = ( address & 0x03 ); ! 230: ! 231: /* Write data, a dword at a time */ ! 232: for ( address &= ~0x03 ; len ; address += 4 ) { ! 233: ! 234: /* Read existing dword, if necessary */ ! 235: if ( skip_len || ( len <= 0x03 ) ) { ! 236: if ( ( rc = pci_vpd_read_dword ( vpd, address, ! 237: &data ) ) != 0 ) ! 238: return rc; ! 239: } ! 240: ! 241: /* Copy data from buffer */ ! 242: for ( i = 4 ; i ; i-- ) { ! 243: if ( skip_len ) { ! 244: skip_len--; ! 245: } else if ( len ) { ! 246: data = ( ( data & ~0xff ) | *(bytes++) ); ! 247: len--; ! 248: } ! 249: data = ( ( data << 24 ) | ( data >> 8 ) ); ! 250: } ! 251: ! 252: /* Write whole dword */ ! 253: if ( ( rc = pci_vpd_write_dword ( vpd, address, data ) ) != 0 ) ! 254: return rc; ! 255: } ! 256: return 0; ! 257: } ! 258: ! 259: /** ! 260: * Dump PCI VPD region (for debugging) ! 261: * ! 262: * @v vpd PCI VPD ! 263: * @v address Starting address ! 264: * @v len Length of data ! 265: */ ! 266: static void pci_vpd_dump ( struct pci_vpd *vpd, unsigned int address, ! 267: size_t len ) { ! 268: int rc; ! 269: ! 270: /* Do nothing in non-debug builds */ ! 271: if ( ! DBG_LOG ) ! 272: return; ! 273: ! 274: /* Read data */ ! 275: { ! 276: char buf[len]; ! 277: if ( ( rc = pci_vpd_read ( vpd, address, buf, ! 278: sizeof ( buf ) ) ) != 0 ) ! 279: return; ! 280: DBGC_HDA ( vpd, address, buf, sizeof ( buf ) ); ! 281: } ! 282: } ! 283: ! 284: /** ! 285: * Locate PCI VPD tag ! 286: * ! 287: * @v vpd PCI VPD ! 288: * @v tag ISAPnP tag ! 289: * @ret address Address of tag body ! 290: * @ret len Length of tag body ! 291: * @ret rc Return status code ! 292: */ ! 293: static int pci_vpd_find_tag ( struct pci_vpd *vpd, unsigned int tag, ! 294: unsigned int *address, size_t *len ) { ! 295: uint8_t read_tag; ! 296: uint16_t read_len; ! 297: int rc; ! 298: ! 299: /* Scan through tags looking for a match */ ! 300: *address = 0; ! 301: do { ! 302: /* Read tag byte */ ! 303: if ( ( rc = pci_vpd_read ( vpd, (*address)++, &read_tag, ! 304: sizeof ( read_tag ) ) ) != 0 ) ! 305: return rc; ! 306: ! 307: /* Extract tag and length */ ! 308: if ( ISAPNP_IS_LARGE_TAG ( read_tag ) ) { ! 309: if ( ( rc = pci_vpd_read ( vpd, *address, &read_len, ! 310: sizeof ( read_len ) ) ) != 0) ! 311: return rc; ! 312: *address += sizeof ( read_len ); ! 313: read_len = le16_to_cpu ( read_len ); ! 314: read_tag = ISAPNP_LARGE_TAG_NAME ( read_tag ); ! 315: } else { ! 316: read_len = ISAPNP_SMALL_TAG_LEN ( read_tag ); ! 317: read_tag = ISAPNP_SMALL_TAG_NAME ( read_tag ); ! 318: } ! 319: ! 320: /* Check for tag match */ ! 321: if ( tag == read_tag ) { ! 322: *len = read_len; ! 323: DBGC ( vpd, PCI_FMT " VPD tag %02x is at " ! 324: "[%04x,%04zx)\n", PCI_ARGS ( vpd->pci ), tag, ! 325: *address, ( *address + *len ) ); ! 326: return 0; ! 327: } ! 328: ! 329: /* Move to next tag */ ! 330: *address += read_len; ! 331: ! 332: } while ( read_tag != ISAPNP_TAG_END ); ! 333: ! 334: DBGC ( vpd, PCI_FMT " VPD tag %02x not found\n", ! 335: PCI_ARGS ( vpd->pci ), tag ); ! 336: return -ENOENT; ! 337: } ! 338: ! 339: /** ! 340: * Locate PCI VPD field ! 341: * ! 342: * @v vpd PCI VPD ! 343: * @v field VPD field descriptor ! 344: * @ret address Address of field body ! 345: * @ret len Length of field body ! 346: * @ret rc Return status code ! 347: */ ! 348: int pci_vpd_find ( struct pci_vpd *vpd, unsigned int field, ! 349: unsigned int *address, size_t *len ) { ! 350: struct pci_vpd_field read_field; ! 351: int rc; ! 352: ! 353: /* Locate containing tag */ ! 354: if ( ( rc = pci_vpd_find_tag ( vpd, PCI_VPD_TAG ( field ), ! 355: address, len ) ) != 0 ) ! 356: return rc; ! 357: ! 358: /* Return immediately if we are searching for a whole-tag field */ ! 359: if ( ! PCI_VPD_KEYWORD ( field ) ) { ! 360: pci_vpd_dump ( vpd, *address, *len ); ! 361: return 0; ! 362: } ! 363: ! 364: /* Scan through fields looking for a match */ ! 365: while ( *len >= sizeof ( read_field ) ) { ! 366: ! 367: /* Read field header */ ! 368: if ( ( rc = pci_vpd_read ( vpd, *address, &read_field, ! 369: sizeof ( read_field ) ) ) != 0 ) ! 370: return rc; ! 371: *address += sizeof ( read_field ); ! 372: *len -= sizeof ( read_field ); ! 373: ! 374: /* Check for keyword match */ ! 375: if ( read_field.keyword == PCI_VPD_KEYWORD ( field ) ) { ! 376: *len = read_field.len; ! 377: DBGC ( vpd, PCI_FMT " VPD field " PCI_VPD_FIELD_FMT ! 378: " is at [%04x,%04zx)\n", PCI_ARGS ( vpd->pci ), ! 379: PCI_VPD_FIELD_ARGS ( field ), ! 380: *address, ( *address + *len ) ); ! 381: pci_vpd_dump ( vpd, *address, *len ); ! 382: return 0; ! 383: } ! 384: ! 385: /* Move to next field */ ! 386: if ( read_field.len > *len ) ! 387: break; ! 388: *address += read_field.len; ! 389: *len -= read_field.len; ! 390: } ! 391: ! 392: DBGC ( vpd, PCI_FMT " VPD field " PCI_VPD_FIELD_FMT " not found\n", ! 393: PCI_ARGS ( vpd->pci ), PCI_VPD_FIELD_ARGS ( field ) ); ! 394: return -ENOENT; ! 395: } ! 396: ! 397: /** ! 398: * Resize VPD field ! 399: * ! 400: * @v vpd PCI VPD ! 401: * @v field VPD field descriptor ! 402: * @v len New length of field body ! 403: * @ret address Address of field body ! 404: * @ret rc Return status code ! 405: */ ! 406: int pci_vpd_resize ( struct pci_vpd *vpd, unsigned int field, size_t len, ! 407: unsigned int *address ) { ! 408: struct pci_vpd_field rw_field; ! 409: struct pci_vpd_field old_field; ! 410: struct pci_vpd_field new_field; ! 411: unsigned int rw_address; ! 412: unsigned int old_address; ! 413: unsigned int copy_address; ! 414: unsigned int dst_address; ! 415: unsigned int dump_address; ! 416: size_t rw_len; ! 417: size_t old_len; ! 418: size_t available_len; ! 419: size_t copy_len; ! 420: size_t dump_len; ! 421: void *copy; ! 422: int rc; ! 423: ! 424: /* Sanity checks */ ! 425: assert ( PCI_VPD_TAG ( field ) == PCI_VPD_TAG_RW ); ! 426: assert ( PCI_VPD_KEYWORD ( field ) != 0 ); ! 427: assert ( field != PCI_VPD_FIELD_RW ); ! 428: ! 429: /* Locate 'RW' field */ ! 430: if ( ( rc = pci_vpd_find ( vpd, PCI_VPD_FIELD_RW, &rw_address, ! 431: &rw_len ) ) != 0 ) ! 432: goto err_no_rw; ! 433: ! 434: /* Locate old field, if any */ ! 435: if ( ( rc = pci_vpd_find ( vpd, field, &old_address, ! 436: &old_len ) ) == 0 ) { ! 437: ! 438: /* Field already exists */ ! 439: if ( old_address > rw_address ) { ! 440: DBGC ( vpd, PCI_FMT " VPD field " PCI_VPD_FIELD_FMT ! 441: " at [%04x,%04zx) is after field " ! 442: PCI_VPD_FIELD_FMT " at [%04x,%04zx)\n", ! 443: PCI_ARGS ( vpd->pci ), ! 444: PCI_VPD_FIELD_ARGS ( field ), ! 445: old_address, ( old_address + old_len ), ! 446: PCI_VPD_FIELD_ARGS ( PCI_VPD_FIELD_RW ), ! 447: rw_address, ( rw_address + rw_len ) ); ! 448: rc = -ENXIO; ! 449: goto err_after_rw; ! 450: } ! 451: dst_address = ( old_address - sizeof ( old_field ) ); ! 452: copy_address = ( old_address + old_len ); ! 453: copy_len = ( rw_address - sizeof ( rw_field ) - copy_address ); ! 454: ! 455: /* Calculate available length */ ! 456: available_len = ( rw_len + old_len ); ! 457: ! 458: } else { ! 459: ! 460: /* Field does not yet exist */ ! 461: dst_address = ( rw_address - sizeof ( rw_field ) ); ! 462: copy_address = dst_address; ! 463: copy_len = 0; ! 464: ! 465: /* Calculate available length */ ! 466: available_len = ( ( rw_len > sizeof ( new_field ) ) ? ! 467: ( rw_len - sizeof ( new_field ) ) : 0 ); ! 468: } ! 469: ! 470: /* Dump region before changes */ ! 471: dump_address = dst_address; ! 472: dump_len = ( rw_address + rw_len - dump_address ); ! 473: DBGC ( vpd, PCI_FMT " VPD before resizing field " PCI_VPD_FIELD_FMT ! 474: " to %zd bytes:\n", PCI_ARGS ( vpd->pci ), ! 475: PCI_VPD_FIELD_ARGS ( field ), len ); ! 476: pci_vpd_dump ( vpd, dump_address, dump_len ); ! 477: ! 478: /* Check available length */ ! 479: if ( available_len > PCI_VPD_MAX_LEN ) ! 480: available_len = PCI_VPD_MAX_LEN; ! 481: if ( len > available_len ) { ! 482: DBGC ( vpd, PCI_FMT " VPD no space for field " ! 483: PCI_VPD_FIELD_FMT " (need %02zx, have %02zx)\n", ! 484: PCI_ARGS ( vpd->pci ), PCI_VPD_FIELD_ARGS ( field ), ! 485: len, available_len ); ! 486: rc = -ENOSPC; ! 487: goto err_no_space; ! 488: } ! 489: ! 490: /* Preserve intermediate fields, if any */ ! 491: copy = malloc ( copy_len ); ! 492: if ( ! copy ) { ! 493: rc = -ENOMEM; ! 494: goto err_copy_alloc; ! 495: } ! 496: if ( ( rc = pci_vpd_read ( vpd, copy_address, copy, copy_len ) ) != 0 ) ! 497: goto err_copy_read; ! 498: ! 499: /* Create new field, if applicable */ ! 500: if ( len ) { ! 501: new_field.keyword = PCI_VPD_KEYWORD ( field ); ! 502: new_field.len = len; ! 503: if ( ( rc = pci_vpd_write ( vpd, dst_address, &new_field, ! 504: sizeof ( new_field ) ) ) != 0 ) ! 505: goto err_new_write; ! 506: dst_address += sizeof ( new_field ); ! 507: *address = dst_address; ! 508: DBGC ( vpd, PCI_FMT " VPD field " PCI_VPD_FIELD_FMT " is now " ! 509: "at [%04x,%04x)\n", PCI_ARGS ( vpd->pci ), ! 510: PCI_VPD_FIELD_ARGS ( field ), dst_address, ! 511: ( dst_address + new_field.len ) ); ! 512: dst_address += len; ! 513: } else { ! 514: DBGC ( vpd, PCI_FMT " VPD field " PCI_VPD_FIELD_FMT ! 515: " no longer exists\n", PCI_ARGS ( vpd->pci ), ! 516: PCI_VPD_FIELD_ARGS ( field ) ); ! 517: } ! 518: ! 519: /* Restore intermediate fields, if any */ ! 520: if ( ( rc = pci_vpd_write ( vpd, dst_address, copy, copy_len ) ) != 0 ) ! 521: goto err_copy_write; ! 522: dst_address += copy_len; ! 523: ! 524: /* Create 'RW' field */ ! 525: rw_field.keyword = PCI_VPD_KEYWORD ( PCI_VPD_FIELD_RW ); ! 526: rw_field.len = ( rw_len + ! 527: ( rw_address - sizeof ( rw_field ) ) - dst_address ); ! 528: if ( ( rc = pci_vpd_write ( vpd, dst_address, &rw_field, ! 529: sizeof ( rw_field ) ) ) != 0 ) ! 530: goto err_rw_write; ! 531: dst_address += sizeof ( rw_field ); ! 532: DBGC ( vpd, PCI_FMT " VPD field " PCI_VPD_FIELD_FMT " is now " ! 533: "at [%04x,%04x)\n", PCI_ARGS ( vpd->pci ), ! 534: PCI_VPD_FIELD_ARGS ( PCI_VPD_FIELD_RW ), dst_address, ! 535: ( dst_address + rw_field.len ) ); ! 536: ! 537: /* Dump region after changes */ ! 538: DBGC ( vpd, PCI_FMT " VPD after resizing field " PCI_VPD_FIELD_FMT ! 539: " to %zd bytes:\n", PCI_ARGS ( vpd->pci ), ! 540: PCI_VPD_FIELD_ARGS ( field ), len ); ! 541: pci_vpd_dump ( vpd, dump_address, dump_len ); ! 542: ! 543: rc = 0; ! 544: ! 545: err_rw_write: ! 546: err_new_write: ! 547: err_copy_write: ! 548: err_copy_read: ! 549: free ( copy ); ! 550: err_copy_alloc: ! 551: err_no_space: ! 552: err_after_rw: ! 553: err_no_rw: ! 554: return rc; ! 555: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.