iPXE
Functions
pcivpd.c File Reference

PCI Vital Product Data. More...

#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <byteswap.h>
#include <ipxe/pci.h>
#include <ipxe/isapnp.h>
#include <ipxe/pcivpd.h>

Go to the source code of this file.

Functions

 FILE_LICENCE (GPL2_OR_LATER_OR_UBDL)
int pci_vpd_init (struct pci_vpd *vpd, struct pci_device *pci)
 Initialise PCI Vital Product Data.
static int pci_vpd_read_dword (struct pci_vpd *vpd, int address, uint32_t *data)
 Read one dword of PCI Vital Product Data.
static int pci_vpd_write_dword (struct pci_vpd *vpd, int address, uint32_t data)
 Write one dword of PCI Vital Product Data.
int pci_vpd_read (struct pci_vpd *vpd, unsigned int address, void *buf, size_t len)
 Read PCI VPD.
int pci_vpd_write (struct pci_vpd *vpd, unsigned int address, const void *buf, size_t len)
 Write PCI VPD.
static void pci_vpd_dump (struct pci_vpd *vpd, unsigned int address, size_t len)
 Dump PCI VPD region (for debugging)
static int pci_vpd_find_tag (struct pci_vpd *vpd, unsigned int tag, unsigned int *address, size_t *len)
 Locate PCI VPD tag.
int pci_vpd_find (struct pci_vpd *vpd, unsigned int field, unsigned int *address, size_t *len)
 Locate PCI VPD field.
int pci_vpd_resize (struct pci_vpd *vpd, unsigned int field, size_t len, unsigned int *address)
 Resize VPD field.

Detailed Description

PCI Vital Product Data.

Definition in file pcivpd.c.


Function Documentation

FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL  )
int pci_vpd_init ( struct pci_vpd vpd,
struct pci_device pci 
)

Initialise PCI Vital Product Data.

Parameters:
vpdPCI VPD
pciPCI device
Return values:
rcReturn status code

Definition at line 48 of file pcivpd.c.

References pci_vpd::cap, DBGC, ENOTTY, pci_vpd::pci, PCI_ARGS, PCI_CAP_ID_VPD, pci_find_capability(), PCI_FMT, and pci_vpd_invalidate_cache().

Referenced by nvs_vpd_init().

                                                                 {

        /* Initialise structure */
        vpd->pci = pci;
        pci_vpd_invalidate_cache ( vpd );

        /* Locate VPD capability */
        vpd->cap = pci_find_capability ( pci, PCI_CAP_ID_VPD );
        if ( ! vpd->cap ) {
                DBGC ( vpd, PCI_FMT " does not support VPD\n",
                       PCI_ARGS ( pci ) );
                return -ENOTTY;
        }

        DBGC ( vpd, PCI_FMT " VPD is at offset %02x\n",
               PCI_ARGS ( pci ), vpd->cap );
        return 0;
}
static int pci_vpd_read_dword ( struct pci_vpd vpd,
int  address,
uint32_t data 
) [static]

Read one dword of PCI Vital Product Data.

Parameters:
vpdPCI VPD
addressAddress to read
Return values:
dataRead data
rcReturn status code

Definition at line 75 of file pcivpd.c.

References pci_vpd_cache::address, address, pci_vpd::cache, pci_vpd::cap, pci_vpd_cache::data, data, DBGC, DBGC2, ENOTTY, ETIMEDOUT, flag, htonl, mdelay(), pci_vpd::pci, PCI_ARGS, PCI_FMT, pci_read_config_dword(), pci_read_config_word(), PCI_VPD_ADDRESS, pci_vpd_cache_is_valid(), PCI_VPD_DATA, PCI_VPD_FLAG, PCI_VPD_MAX_WAIT_MS, and pci_write_config_word().

Referenced by pci_vpd_read(), and pci_vpd_write().

                                                 {
        struct pci_device *pci = vpd->pci;
        unsigned int cap = vpd->cap;
        unsigned int retries;
        uint16_t flag;

        /* Fail if no VPD present */
        if ( ! cap )
                return -ENOTTY;

        /* Return cached value, if present */
        if ( pci_vpd_cache_is_valid ( vpd ) &&
             ( vpd->cache.address == address ) ) {
                *data = vpd->cache.data;
                return 0;
        }

        /* Initiate read */
        pci_write_config_word ( pci, ( cap + PCI_VPD_ADDRESS ), address );

        /* Wait for read to complete */
        for ( retries = 0 ; retries < PCI_VPD_MAX_WAIT_MS ; retries++ ) {

                /* Check if data is ready */
                pci_read_config_word ( pci, ( cap + PCI_VPD_ADDRESS ), &flag );
                if ( flag & PCI_VPD_FLAG ) {

                        /* Read data */
                        pci_read_config_dword ( pci, ( cap + PCI_VPD_DATA ),
                                                data );
                        DBGC2 ( vpd, PCI_FMT " VPD %04x => %08x\n",
                                PCI_ARGS ( pci ), address, htonl ( *data ) );

                        /* Populate cache */
                        vpd->cache.address = address;
                        vpd->cache.data = *data;

                        return 0;
                }

                /* Wait 1ms before retrying */
                mdelay ( 1 );
        }

        DBGC ( vpd, PCI_FMT " VPD %04x read via %02x timed out\n",
               PCI_ARGS ( pci ), address, cap );
        return -ETIMEDOUT;
}
static int pci_vpd_write_dword ( struct pci_vpd vpd,
int  address,
uint32_t  data 
) [static]

Write one dword of PCI Vital Product Data.

Parameters:
vpdPCI VPD
addressAddress to write
dataData to write
Return values:
rcReturn status code

Definition at line 133 of file pcivpd.c.

References pci_vpd::cap, DBGC, DBGC2, ENOTTY, ETIMEDOUT, flag, htonl, mdelay(), pci_vpd::pci, PCI_ARGS, PCI_FMT, pci_read_config_word(), PCI_VPD_ADDRESS, PCI_VPD_DATA, PCI_VPD_FLAG, pci_vpd_invalidate_cache(), PCI_VPD_MAX_WAIT_MS, pci_write_config_dword(), and pci_write_config_word().

Referenced by pci_vpd_write().

                                                 {
        struct pci_device *pci = vpd->pci;
        unsigned int cap = vpd->cap;
        unsigned int retries;
        uint16_t flag;

        /* Fail if no VPD present */
        if ( ! cap )
                return -ENOTTY;

        /* Invalidate cache */
        pci_vpd_invalidate_cache ( vpd );

        DBGC2 ( vpd, PCI_FMT " VPD %04x <= %08x\n",
                PCI_ARGS ( pci ), address, htonl ( data ) );

        /* Write data */
        pci_write_config_dword ( pci, ( cap + PCI_VPD_DATA ), data );

        /* Initiate write */
        pci_write_config_word ( pci, ( cap + PCI_VPD_ADDRESS ),
                                ( address | PCI_VPD_FLAG ) );

        /* Wait for write to complete */
        for ( retries = 0 ; retries < PCI_VPD_MAX_WAIT_MS ; retries++ ) {

                /* Check if write has completed */
                pci_read_config_word ( pci, ( cap + PCI_VPD_ADDRESS ), &flag );
                if ( ! ( flag & PCI_VPD_FLAG ) )
                        return 0;

                /* Wait 1ms before retrying */
                mdelay ( 1 );
        }

        DBGC ( vpd, PCI_FMT " VPD %04x write via %02x timed out\n",
               PCI_ARGS ( pci ), address, cap );
        return -ETIMEDOUT;
}
int pci_vpd_read ( struct pci_vpd vpd,
unsigned int  address,
void *  buf,
size_t  len 
)

Read PCI VPD.

Parameters:
vpdPCI VPD
addressStarting address
bufData buffer
lenLength of data buffer
Return values:
rcReturn status code

Definition at line 183 of file pcivpd.c.

References bytes, data, len, pci_vpd_read_dword(), and rc.

Referenced by nvs_vpd_read(), pci_vpd_dump(), pci_vpd_find(), pci_vpd_find_tag(), and pci_vpd_resize().

                                {
        uint8_t *bytes = buf;
        uint32_t data;
        size_t skip_len;
        unsigned int i;
        int rc;

        /* Calculate length to skip at start of data */
        skip_len = ( address & 0x03 );

        /* Read data, a dword at a time */
        for ( address &= ~0x03 ; len ; address += 4 ) {

                /* Read whole dword */
                if ( ( rc = pci_vpd_read_dword ( vpd, address, &data ) ) != 0 )
                        return rc;

                /* Copy data to buffer */
                for ( i = 4 ; i ; i-- ) {
                        if ( skip_len ) {
                                skip_len--;
                        } else if ( len ) {
                                *(bytes++) = data;
                                len--;
                        }
                        data = ( ( data << 24 ) | ( data >> 8 ) );
                }
        }

        return 0;
}
int pci_vpd_write ( struct pci_vpd vpd,
unsigned int  address,
const void *  buf,
size_t  len 
)

Write PCI VPD.

Parameters:
vpdPCI VPD
addressStarting address
bufData buffer
lenLength of data buffer
Return values:
rcReturn status code

Definition at line 225 of file pcivpd.c.

References bytes, data, len, pci_vpd_read_dword(), pci_vpd_write_dword(), and rc.

Referenced by nvs_vpd_write(), and pci_vpd_resize().

                                 {
        const uint8_t *bytes = buf;
        uint32_t data;
        size_t skip_len;
        unsigned int i;
        int rc;

        /* Calculate length to skip at start of data */
        skip_len = ( address & 0x03 );

        /* Write data, a dword at a time */
        for ( address &= ~0x03 ; len ; address += 4 ) {

                /* Read existing dword, if necessary */
                if ( skip_len || ( len <= 0x03 ) ) {
                        if ( ( rc = pci_vpd_read_dword ( vpd, address,
                                                         &data ) ) != 0 )
                                return rc;
                }

                /* Copy data from buffer */
                for ( i = 4 ; i ; i-- ) {
                        if ( skip_len ) {
                                skip_len--;
                        } else if ( len ) {
                                data = ( ( data & ~0xff ) | *(bytes++) );
                                len--;
                        }
                        data = ( ( data << 24 ) | ( data >> 8 ) );
                }

                /* Write whole dword */
                if ( ( rc = pci_vpd_write_dword ( vpd, address, data ) ) != 0 )
                        return rc;
        }
        return 0;
}
static void pci_vpd_dump ( struct pci_vpd vpd,
unsigned int  address,
size_t  len 
) [static]

Dump PCI VPD region (for debugging)

Parameters:
vpdPCI VPD
addressStarting address
lenLength of data

Definition at line 271 of file pcivpd.c.

References DBG_LOG, DBGC_HDA, len, pci_vpd_read(), and rc.

Referenced by pci_vpd_find(), and pci_vpd_resize().

                                        {
        int rc;

        /* Do nothing in non-debug builds */
        if ( ! DBG_LOG )
                return;

        /* Read data */
        {
                char buf[len];
                if ( ( rc = pci_vpd_read ( vpd, address, buf,
                                           sizeof ( buf ) ) ) != 0 )
                        return;
                DBGC_HDA ( vpd, address, buf, sizeof ( buf ) );
        }
}
static int pci_vpd_find_tag ( struct pci_vpd vpd,
unsigned int  tag,
unsigned int *  address,
size_t len 
) [static]

Locate PCI VPD tag.

Parameters:
vpdPCI VPD
tagISAPnP tag
Return values:
addressAddress of tag body
lenLength of tag body
rcReturn status code

Definition at line 298 of file pcivpd.c.

References DBGC, ENOENT, le16_to_cpu, pci_vpd::pci, PCI_ARGS, PCI_FMT, pci_vpd_read(), and rc.

Referenced by pci_vpd_find().

                                                                   {
        uint8_t read_tag;
        uint16_t read_len;
        int rc;

        /* Scan through tags looking for a match */
        *address = 0;
        do {
                /* Read tag byte */
                if ( ( rc = pci_vpd_read ( vpd, (*address)++, &read_tag,
                                           sizeof ( read_tag ) ) ) != 0 )
                        return rc;

                /* Extract tag and length */
                if ( ISAPNP_IS_LARGE_TAG ( read_tag ) ) {
                        if ( ( rc = pci_vpd_read ( vpd, *address, &read_len,
                                                   sizeof ( read_len ) ) ) != 0)
                                return rc;
                        *address += sizeof ( read_len );
                        read_len = le16_to_cpu ( read_len );
                        read_tag = ISAPNP_LARGE_TAG_NAME ( read_tag );
                } else {
                        read_len = ISAPNP_SMALL_TAG_LEN ( read_tag );
                        read_tag = ISAPNP_SMALL_TAG_NAME ( read_tag );
                }

                /* Check for tag match */
                if ( tag == read_tag ) {
                        *len = read_len;
                        DBGC ( vpd, PCI_FMT " VPD tag %02x is at "
                               "[%04x,%04zx)\n", PCI_ARGS ( vpd->pci ), tag,
                               *address, ( *address + *len ) );
                        return 0;
                }

                /* Move to next tag */
                *address += read_len;

        } while ( read_tag != ISAPNP_TAG_END );

        DBGC ( vpd, PCI_FMT " VPD tag %02x not found\n",
               PCI_ARGS ( vpd->pci ), tag );
        return -ENOENT;
}
int pci_vpd_find ( struct pci_vpd vpd,
unsigned int  field,
unsigned int *  address,
size_t len 
)

Locate PCI VPD field.

Parameters:
vpdPCI VPD
fieldVPD field descriptor
Return values:
addressAddress of field body
lenLength of field body
rcReturn status code

Definition at line 353 of file pcivpd.c.

References DBGC, ENOENT, pci_vpd_field::keyword, pci_vpd_field::len, pci_vpd::pci, PCI_ARGS, PCI_FMT, pci_vpd_dump(), PCI_VPD_FIELD_ARGS, PCI_VPD_FIELD_FMT, pci_vpd_find_tag(), PCI_VPD_KEYWORD, pci_vpd_read(), PCI_VPD_TAG, and rc.

Referenced by nvs_vpd_nvo_init(), nvs_vpd_read(), nvs_vpd_write(), and pci_vpd_resize().

                                                        {
        struct pci_vpd_field read_field;
        int rc;

        /* Locate containing tag */
        if ( ( rc = pci_vpd_find_tag ( vpd, PCI_VPD_TAG ( field ),
                                       address, len ) ) != 0 )
                return rc;

        /* Return immediately if we are searching for a whole-tag field */
        if ( ! PCI_VPD_KEYWORD ( field ) ) {
                pci_vpd_dump ( vpd, *address, *len );
                return 0;
        }

        /* Scan through fields looking for a match */
        while ( *len >= sizeof ( read_field ) ) {

                /* Read field header */
                if ( ( rc = pci_vpd_read ( vpd, *address, &read_field,
                                           sizeof ( read_field ) ) ) != 0 )
                        return rc;
                *address += sizeof ( read_field );
                *len -= sizeof ( read_field );

                /* Check for keyword match */
                if ( read_field.keyword == PCI_VPD_KEYWORD ( field ) ) {
                        *len = read_field.len;
                        DBGC ( vpd, PCI_FMT " VPD field " PCI_VPD_FIELD_FMT
                               " is at [%04x,%04zx)\n", PCI_ARGS ( vpd->pci ),
                               PCI_VPD_FIELD_ARGS ( field ),
                               *address, ( *address + *len ) );
                        pci_vpd_dump ( vpd, *address, *len );
                        return 0;
                }

                /* Move to next field */
                if ( read_field.len > *len )
                        break;
                *address += read_field.len;
                *len -= read_field.len;
        }

        DBGC ( vpd, PCI_FMT " VPD field " PCI_VPD_FIELD_FMT " not found\n",
               PCI_ARGS ( vpd->pci ), PCI_VPD_FIELD_ARGS ( field ) );
        return -ENOENT;
}
int pci_vpd_resize ( struct pci_vpd vpd,
unsigned int  field,
size_t  len,
unsigned int *  address 
)

Resize VPD field.

Parameters:
vpdPCI VPD
fieldVPD field descriptor
lenNew length of field body
Return values:
addressAddress of field body
rcReturn status code

Definition at line 411 of file pcivpd.c.

References assert, DBGC, ENOMEM, ENOSPC, ENXIO, free, pci_vpd_field::keyword, pci_vpd_field::len, len, malloc(), pci_vpd::pci, PCI_ARGS, PCI_FMT, pci_vpd_dump(), PCI_VPD_FIELD_ARGS, PCI_VPD_FIELD_FMT, PCI_VPD_FIELD_RW, pci_vpd_find(), PCI_VPD_KEYWORD, PCI_VPD_MAX_LEN, pci_vpd_read(), PCI_VPD_TAG, PCI_VPD_TAG_RW, pci_vpd_write(), and rc.

Referenced by nvs_vpd_resize().

                                             {
        struct pci_vpd_field rw_field;
        struct pci_vpd_field old_field;
        struct pci_vpd_field new_field;
        unsigned int rw_address;
        unsigned int old_address;
        unsigned int copy_address;
        unsigned int dst_address;
        unsigned int dump_address;
        size_t rw_len;
        size_t old_len;
        size_t available_len;
        size_t copy_len;
        size_t dump_len;
        void *copy;
        int rc;

        /* Sanity checks */
        assert ( PCI_VPD_TAG ( field ) == PCI_VPD_TAG_RW );
        assert ( PCI_VPD_KEYWORD ( field ) != 0 );
        assert ( field != PCI_VPD_FIELD_RW );

        /* Locate 'RW' field */
        if ( ( rc = pci_vpd_find ( vpd, PCI_VPD_FIELD_RW, &rw_address,
                                   &rw_len ) ) != 0 )
                goto err_no_rw;

        /* Locate old field, if any */
        if ( ( rc = pci_vpd_find ( vpd, field, &old_address,
                                   &old_len ) ) == 0 ) {

                /* Field already exists */
                if ( old_address > rw_address ) {
                        DBGC ( vpd, PCI_FMT " VPD field " PCI_VPD_FIELD_FMT
                               " at [%04x,%04zx) is after field "
                               PCI_VPD_FIELD_FMT " at [%04x,%04zx)\n",
                               PCI_ARGS ( vpd->pci ),
                               PCI_VPD_FIELD_ARGS ( field ),
                               old_address, ( old_address + old_len ),
                               PCI_VPD_FIELD_ARGS ( PCI_VPD_FIELD_RW ),
                               rw_address, ( rw_address + rw_len ) );
                        rc = -ENXIO;
                        goto err_after_rw;
                }
                dst_address = ( old_address - sizeof ( old_field ) );
                copy_address = ( old_address + old_len );
                copy_len = ( rw_address - sizeof ( rw_field ) - copy_address );

                /* Calculate available length */
                available_len = ( rw_len + old_len );

        } else {

                /* Field does not yet exist */
                dst_address = ( rw_address - sizeof ( rw_field ) );
                copy_address = dst_address;
                copy_len = 0;

                /* Calculate available length */
                available_len = ( ( rw_len > sizeof ( new_field ) ) ?
                                  ( rw_len - sizeof ( new_field ) ) : 0 );
        }

        /* Dump region before changes */
        dump_address = dst_address;
        dump_len = ( rw_address + rw_len - dump_address );
        DBGC ( vpd, PCI_FMT " VPD before resizing field " PCI_VPD_FIELD_FMT
               " to %zd bytes:\n", PCI_ARGS ( vpd->pci ),
               PCI_VPD_FIELD_ARGS ( field ), len );
        pci_vpd_dump ( vpd, dump_address, dump_len );

        /* Check available length */
        if ( available_len > PCI_VPD_MAX_LEN )
                available_len = PCI_VPD_MAX_LEN;
        if ( len > available_len ) {
                DBGC ( vpd, PCI_FMT " VPD no space for field "
                       PCI_VPD_FIELD_FMT " (need %02zx, have %02zx)\n",
                       PCI_ARGS ( vpd->pci ), PCI_VPD_FIELD_ARGS ( field ),
                       len, available_len );
                rc = -ENOSPC;
                goto err_no_space;
        }

        /* Preserve intermediate fields, if any */
        copy = malloc ( copy_len );
        if ( ! copy ) {
                rc = -ENOMEM;
                goto err_copy_alloc;
        }
        if ( ( rc = pci_vpd_read ( vpd, copy_address, copy, copy_len ) ) != 0 )
                goto err_copy_read;

        /* Create new field, if applicable */
        if ( len ) {
                new_field.keyword = PCI_VPD_KEYWORD ( field );
                new_field.len = len;
                if ( ( rc = pci_vpd_write ( vpd, dst_address, &new_field,
                                            sizeof ( new_field ) ) ) != 0 )
                        goto err_new_write;
                dst_address += sizeof ( new_field );
                *address = dst_address;
                DBGC ( vpd, PCI_FMT " VPD field " PCI_VPD_FIELD_FMT " is now "
                       "at [%04x,%04x)\n", PCI_ARGS ( vpd->pci ),
                       PCI_VPD_FIELD_ARGS ( field ), dst_address,
                       ( dst_address + new_field.len ) );
                dst_address += len;
        } else {
                DBGC ( vpd, PCI_FMT " VPD field " PCI_VPD_FIELD_FMT
                       " no longer exists\n", PCI_ARGS ( vpd->pci ),
                       PCI_VPD_FIELD_ARGS ( field ) );
        }

        /* Restore intermediate fields, if any */
        if ( ( rc = pci_vpd_write ( vpd, dst_address, copy, copy_len ) ) != 0 )
                goto err_copy_write;
        dst_address += copy_len;

        /* Create 'RW' field */
        rw_field.keyword = PCI_VPD_KEYWORD ( PCI_VPD_FIELD_RW );
        rw_field.len = ( rw_len +
                         ( rw_address - sizeof ( rw_field ) ) - dst_address );
        if ( ( rc = pci_vpd_write ( vpd, dst_address, &rw_field,
                                    sizeof ( rw_field ) ) ) != 0 )
                goto err_rw_write;
        dst_address += sizeof ( rw_field );
        DBGC ( vpd, PCI_FMT " VPD field " PCI_VPD_FIELD_FMT " is now "
               "at [%04x,%04x)\n", PCI_ARGS ( vpd->pci ),
               PCI_VPD_FIELD_ARGS ( PCI_VPD_FIELD_RW ), dst_address,
               ( dst_address + rw_field.len ) );

        /* Dump region after changes */
        DBGC ( vpd, PCI_FMT " VPD after resizing field " PCI_VPD_FIELD_FMT
               " to %zd bytes:\n", PCI_ARGS ( vpd->pci ),
               PCI_VPD_FIELD_ARGS ( field ), len );
        pci_vpd_dump ( vpd, dump_address, dump_len );

        rc = 0;

 err_rw_write:
 err_new_write:
 err_copy_write:
 err_copy_read:
        free ( copy );
 err_copy_alloc:
 err_no_space:
 err_after_rw:
 err_no_rw:
        return rc;
}