diff options
author | Yang Zhang <yang.z.zhang@intel.com> | 2015-08-28 09:58:54 +0800 |
---|---|---|
committer | Yang Zhang <yang.z.zhang@intel.com> | 2015-09-01 12:44:00 +0800 |
commit | e44e3482bdb4d0ebde2d8b41830ac2cdb07948fb (patch) | |
tree | 66b09f592c55df2878107a468a91d21506104d3f /qemu/roms/ipxe/src/drivers/nvs | |
parent | 9ca8dbcc65cfc63d6f5ef3312a33184e1d726e00 (diff) |
Add qemu 2.4.0
Change-Id: Ic99cbad4b61f8b127b7dc74d04576c0bcbaaf4f5
Signed-off-by: Yang Zhang <yang.z.zhang@intel.com>
Diffstat (limited to 'qemu/roms/ipxe/src/drivers/nvs')
-rw-r--r-- | qemu/roms/ipxe/src/drivers/nvs/nvs.c | 166 | ||||
-rw-r--r-- | qemu/roms/ipxe/src/drivers/nvs/nvsvpd.c | 234 | ||||
-rw-r--r-- | qemu/roms/ipxe/src/drivers/nvs/spi.c | 141 | ||||
-rw-r--r-- | qemu/roms/ipxe/src/drivers/nvs/threewire.c | 132 |
4 files changed, 673 insertions, 0 deletions
diff --git a/qemu/roms/ipxe/src/drivers/nvs/nvs.c b/qemu/roms/ipxe/src/drivers/nvs/nvs.c new file mode 100644 index 000000000..ccb2145bd --- /dev/null +++ b/qemu/roms/ipxe/src/drivers/nvs/nvs.c @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2006 Michael Brown <mbrown@fensystems.co.uk>. + * + * 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. + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +#include <stdint.h> +#include <string.h> +#include <errno.h> +#include <assert.h> +#include <ipxe/nvs.h> + +/** @file + * + * Non-volatile storage + * + */ + +/** + * Calculate length up to next block boundary + * + * @v nvs NVS device + * @v address Starting address + * @v max_len Maximum length + * @ret len Length to use, stopping at block boundaries + */ +static size_t nvs_frag_len ( struct nvs_device *nvs, unsigned int address, + size_t max_len ) { + size_t frag_len; + + /* If there are no block boundaries, return the maximum length */ + if ( ! nvs->block_size ) + return max_len; + + /* Calculate space remaining up to next block boundary */ + frag_len = ( ( nvs->block_size - + ( address & ( nvs->block_size - 1 ) ) ) + << nvs->word_len_log2 ); + + /* Limit to maximum length */ + if ( max_len < frag_len ) + return max_len; + + return frag_len; +} + +/** + * Read from non-volatile storage device + * + * @v nvs NVS device + * @v address Address from which to read + * @v data Data buffer + * @v len Length of data buffer + * @ret rc Return status code + */ +int nvs_read ( struct nvs_device *nvs, unsigned int address, + void *data, size_t len ) { + size_t frag_len; + int rc; + + /* We don't even attempt to handle buffer lengths that aren't + * an integral number of words. + */ + assert ( ( len & ( ( 1 << nvs->word_len_log2 ) - 1 ) ) == 0 ); + + while ( len ) { + + /* Calculate length to read, stopping at block boundaries */ + frag_len = nvs_frag_len ( nvs, address, len ); + + /* Read this portion of the buffer from the device */ + if ( ( rc = nvs->read ( nvs, address, data, frag_len ) ) != 0 ) + return rc; + + /* Update parameters */ + data += frag_len; + address += ( frag_len >> nvs->word_len_log2 ); + len -= frag_len; + } + + return 0; +} + +/** + * Verify content of non-volatile storage device + * + * @v nvs NVS device + * @v address Address from which to read + * @v data Data to compare against + * @v len Length of data buffer + * @ret rc Return status code + */ +static int nvs_verify ( struct nvs_device *nvs, unsigned int address, + const void *data, size_t len ) { + uint8_t read_data[len]; + int rc; + + /* Read data into temporary buffer */ + if ( ( rc = nvs_read ( nvs, address, read_data, len ) ) != 0 ) + return rc; + + /* Compare data */ + if ( memcmp ( data, read_data, len ) != 0 ) { + DBG ( "NVS %p verification failed at %#04x+%zd\n", + nvs, address, len ); + return -EIO; + } + + return 0; +} + +/** + * Write to non-volatile storage device + * + * @v nvs NVS device + * @v address Address to which to write + * @v data Data buffer + * @v len Length of data buffer + * @ret rc Return status code + */ +int nvs_write ( struct nvs_device *nvs, unsigned int address, + const void *data, size_t len ) { + size_t frag_len; + int rc; + + /* We don't even attempt to handle buffer lengths that aren't + * an integral number of words. + */ + assert ( ( len & ( ( 1 << nvs->word_len_log2 ) - 1 ) ) == 0 ); + + while ( len ) { + + /* Calculate length to write, stopping at block boundaries */ + frag_len = nvs_frag_len ( nvs, address, len ); + + /* Write this portion of the buffer to the device */ + if ( ( rc = nvs->write ( nvs, address, data, frag_len ) ) != 0) + return rc; + + /* Read back and verify data */ + if ( ( rc = nvs_verify ( nvs, address, data, frag_len ) ) != 0) + return rc; + + /* Update parameters */ + data += frag_len; + address += ( frag_len >> nvs->word_len_log2 ); + len -= frag_len; + } + + return 0; +} diff --git a/qemu/roms/ipxe/src/drivers/nvs/nvsvpd.c b/qemu/roms/ipxe/src/drivers/nvs/nvsvpd.c new file mode 100644 index 000000000..33148d5b9 --- /dev/null +++ b/qemu/roms/ipxe/src/drivers/nvs/nvsvpd.c @@ -0,0 +1,234 @@ +/* + * Copyright (C) 2010 Michael Brown <mbrown@fensystems.co.uk>. + * + * 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. + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +#include <stdio.h> +#include <errno.h> +#include <ipxe/nvs.h> +#include <ipxe/pci.h> +#include <ipxe/pcivpd.h> +#include <ipxe/nvo.h> +#include <ipxe/nvsvpd.h> + +/** @file + * + * Non-Volatile Storage using Vital Product Data + * + */ + +/** + * Read from VPD field + * + * @v nvs NVS device + * @v field VPD field descriptor + * @v data Data buffer + * @v len Length of data buffer + * @ret rc Return status code + */ +static int nvs_vpd_read ( struct nvs_device *nvs, unsigned int field, + void *data, size_t len ) { + struct nvs_vpd_device *nvsvpd = + container_of ( nvs, struct nvs_vpd_device, nvs ); + struct pci_device *pci = nvsvpd->vpd.pci; + unsigned int address; + size_t max_len; + int rc; + + /* Allow reading non-existent field */ + if ( len == 0 ) + return 0; + + /* Locate VPD field */ + if ( ( rc = pci_vpd_find ( &nvsvpd->vpd, field, &address, + &max_len ) ) != 0 ) { + DBGC ( pci, PCI_FMT " NVS VPD could not locate field " + PCI_VPD_FIELD_FMT ": %s\n", PCI_ARGS ( pci ), + PCI_VPD_FIELD_ARGS ( field ), strerror ( rc ) ); + return rc; + } + + /* Sanity check */ + if ( len > max_len ) { + DBGC ( pci, PCI_FMT " NVS VPD cannot read %#02zx bytes " + "beyond field " PCI_VPD_FIELD_FMT " at [%04x,%04zx)\n", + PCI_ARGS ( pci ), len, PCI_VPD_FIELD_ARGS ( field ), + address, ( address + max_len ) ); + return -ENXIO; + } + + /* Read from VPD field */ + if ( ( rc = pci_vpd_read ( &nvsvpd->vpd, address, data, len ) ) != 0 ) { + DBGC ( pci, PCI_FMT " NVS VPD could not read field " + PCI_VPD_FIELD_FMT " at [%04x,%04zx): %s\n", + PCI_ARGS ( pci ), PCI_VPD_FIELD_ARGS ( field ), + address, ( address + len ), strerror ( rc ) ); + return rc; + } + + return 0; +} + +/** + * Write to VPD field + * + * @v nvs NVS device + * @v field VPD field descriptor + * @v data Data buffer + * @v len Length of data buffer + * @ret rc Return status code + */ +static int nvs_vpd_write ( struct nvs_device *nvs, unsigned int field, + const void *data, size_t len ) { + struct nvs_vpd_device *nvsvpd = + container_of ( nvs, struct nvs_vpd_device, nvs ); + struct pci_device *pci = nvsvpd->vpd.pci; + unsigned int address; + size_t max_len; + int rc; + + /* Locate VPD field */ + if ( ( rc = pci_vpd_find ( &nvsvpd->vpd, field, &address, + &max_len ) ) != 0 ) { + DBGC ( pci, PCI_FMT " NVS VPD could not locate field " + PCI_VPD_FIELD_FMT ": %s\n", PCI_ARGS ( pci ), + PCI_VPD_FIELD_ARGS ( field ), strerror ( rc ) ); + return rc; + } + + /* Sanity check */ + if ( len > max_len ) { + DBGC ( pci, PCI_FMT " NVS VPD cannot write %#02zx bytes " + "beyond field " PCI_VPD_FIELD_FMT " at [%04x,%04zx)\n", + PCI_ARGS ( pci ), len, PCI_VPD_FIELD_ARGS ( field ), + address, ( address + max_len ) ); + return -ENXIO; + } + + /* Write field */ + if ( ( rc = pci_vpd_write ( &nvsvpd->vpd, address, data, + len ) ) != 0 ) { + DBGC ( pci, PCI_FMT " NVS VPD could not write field " + PCI_VPD_FIELD_FMT " at [%04x,%04zx): %s\n", + PCI_ARGS ( pci ), PCI_VPD_FIELD_ARGS ( field ), + address, ( address + len ), strerror ( rc ) ); + return rc; + } + + return 0; +} + +/** + * Resize VPD field + * + * @v nvs NVS device + * @v field VPD field descriptor + * @v data Data buffer + * @v len Length of data buffer + * @ret rc Return status code + */ +static int nvs_vpd_resize ( struct nvs_device *nvs, unsigned int field, + size_t len ) { + struct nvs_vpd_device *nvsvpd = + container_of ( nvs, struct nvs_vpd_device, nvs ); + struct pci_device *pci = nvsvpd->vpd.pci; + unsigned int address; + int rc; + + /* Resize field */ + if ( ( rc = pci_vpd_resize ( &nvsvpd->vpd, field, len, + &address ) ) != 0 ) { + DBGC ( pci, PCI_FMT " NVS VPD could not resize field " + PCI_VPD_FIELD_FMT " to %#02zx bytes: %s\n", + PCI_ARGS ( pci ), PCI_VPD_FIELD_ARGS ( field ), + len, strerror ( rc ) ); + return rc; + } + + return 0; +} + +/** + * Initialise NVS VPD device + * + * @v nvsvpd NVS VPD device + * @v pci PCI device + * @ret rc Return status code + */ +int nvs_vpd_init ( struct nvs_vpd_device *nvsvpd, struct pci_device *pci ) { + int rc; + + /* Initialise VPD device */ + if ( ( rc = pci_vpd_init ( &nvsvpd->vpd, pci ) ) != 0 ) { + DBGC ( pci, PCI_FMT " NVS could not initialise " + "VPD: %s\n", PCI_ARGS ( pci ), strerror ( rc ) ); + return rc; + } + + /* Initialise NVS device */ + nvsvpd->nvs.read = nvs_vpd_read; + nvsvpd->nvs.write = nvs_vpd_write; + + return 0; +} + +/** + * Resize non-volatile option storage within NVS VPD device + * + * @v nvo Non-volatile options block + * @v len New length + * @ret rc Return status code + */ +static int nvs_vpd_nvo_resize ( struct nvo_block *nvo, size_t len ) { + int rc; + + /* Resize VPD field */ + if ( ( rc = nvs_vpd_resize ( nvo->nvs, nvo->address, len ) ) != 0 ) + return rc; + + return 0; +} + +/** + * Initialise non-volatile option storage within NVS VPD device + * + * @v nvsvpd NVS VPD device + * @v field VPD field descriptor + * @v nvo Non-volatile options block + * @v refcnt Containing object reference counter, or NULL + */ +void nvs_vpd_nvo_init ( struct nvs_vpd_device *nvsvpd, unsigned int field, + struct nvo_block *nvo, struct refcnt *refcnt ) { + struct pci_device *pci = nvsvpd->vpd.pci; + unsigned int address; + size_t len; + int rc; + + /* Locate VPD field, if present */ + if ( ( rc = pci_vpd_find ( &nvsvpd->vpd, field, &address, + &len ) ) != 0 ) { + DBGC ( pci, PCI_FMT " NVS VPD field " PCI_VPD_FIELD_FMT + " not present; assuming empty\n", + PCI_ARGS ( pci ), PCI_VPD_FIELD_ARGS ( field ) ); + len = 0; + } + + /* Initialise non-volatile options block */ + nvo_init ( nvo, &nvsvpd->nvs, field, len, nvs_vpd_nvo_resize, refcnt ); +} diff --git a/qemu/roms/ipxe/src/drivers/nvs/spi.c b/qemu/roms/ipxe/src/drivers/nvs/spi.c new file mode 100644 index 000000000..84613b9dd --- /dev/null +++ b/qemu/roms/ipxe/src/drivers/nvs/spi.c @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2006 Michael Brown <mbrown@fensystems.co.uk>. + * + * 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. + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +#include <stddef.h> +#include <errno.h> +#include <unistd.h> +#include <ipxe/spi.h> + +/** @file + * + * SPI devices + * + */ + +/** + * Munge SPI device address into command + * + * @v command SPI command + * @v address Address + * @v munge_address Device requires address munging + * @ret command Actual SPI command to use + * + * Some devices with 9-bit addresses (e.g. AT25040A EEPROM) use bit 3 + * of the command byte as address bit A8, rather than having a + * two-byte address. This function takes care of generating the + * appropriate command. + */ +static inline unsigned int spi_command ( unsigned int command, + unsigned int address, + int munge_address ) { + return ( command | ( ( ( address >> 8 ) & munge_address ) << 3 ) ); +} + +/** + * Wait for SPI device to complete operation + * + * @v device SPI device + * @ret rc Return status code + */ +static int spi_wait ( struct spi_device *device ) { + struct spi_bus *bus = device->bus; + uint8_t status; + int i; + int rc; + + for ( i = 0 ; i < 50 ; i++ ) { + udelay ( 20 ); + if ( ( rc = bus->rw ( bus, device, SPI_RDSR, -1, NULL, + &status, sizeof ( status ) ) ) != 0 ) + return rc; + if ( ! ( status & SPI_STATUS_NRDY ) ) + return 0; + } + DBG ( "SPI %p timed out\n", device ); + return -ETIMEDOUT; +} + +/** + * Read data from SPI device + * + * @v nvs NVS device + * @v address Address from which to read + * @v data Data buffer + * @v len Length of data buffer + * @ret rc Return status code + */ +int spi_read ( struct nvs_device *nvs, unsigned int address, + void *data, size_t len ) { + struct spi_device *device = nvs_to_spi ( nvs ); + struct spi_bus *bus = device->bus; + unsigned int command = spi_command ( SPI_READ, address, + device->munge_address ); + int rc; + + DBG ( "SPI %p reading %zd bytes from %#04x\n", device, len, address ); + if ( ( rc = bus->rw ( bus, device, command, address, + NULL, data, len ) ) != 0 ) { + DBG ( "SPI %p failed to read data from device\n", device ); + return rc; + } + + return 0; +} + +/** + * Write data to SPI device + * + * @v nvs NVS device + * @v address Address from which to read + * @v data Data buffer + * @v len Length of data buffer + * @ret rc Return status code + */ +int spi_write ( struct nvs_device *nvs, unsigned int address, + const void *data, size_t len ) { + struct spi_device *device = nvs_to_spi ( nvs ); + struct spi_bus *bus = device->bus; + unsigned int command = spi_command ( SPI_WRITE, address, + device->munge_address ); + int rc; + + DBG ( "SPI %p writing %zd bytes to %#04x\n", device, len, address ); + + if ( ( rc = bus->rw ( bus, device, SPI_WREN, -1, + NULL, NULL, 0 ) ) != 0 ) { + DBG ( "SPI %p failed to write-enable device\n", device ); + return rc; + } + + if ( ( rc = bus->rw ( bus, device, command, address, + data, NULL, len ) ) != 0 ) { + DBG ( "SPI %p failed to write data to device\n", device ); + return rc; + } + + if ( ( rc = spi_wait ( device ) ) != 0 ) { + DBG ( "SPI %p failed to complete write operation\n", device ); + return rc; + } + + return 0; +} + diff --git a/qemu/roms/ipxe/src/drivers/nvs/threewire.c b/qemu/roms/ipxe/src/drivers/nvs/threewire.c new file mode 100644 index 000000000..53f1ad8de --- /dev/null +++ b/qemu/roms/ipxe/src/drivers/nvs/threewire.c @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2006 Michael Brown <mbrown@fensystems.co.uk>. + * + * 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. + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +#include <stddef.h> +#include <string.h> +#include <assert.h> +#include <unistd.h> +#include <ipxe/threewire.h> + +/** @file + * + * Three-wire serial devices + * + */ + +/** + * Read data from three-wire device + * + * @v nvs NVS device + * @v address Address from which to read + * @v data Data buffer + * @v len Length of data buffer + * @ret rc Return status code + */ +int threewire_read ( struct nvs_device *nvs, unsigned int address, + void *data, size_t len ) { + struct spi_device *device = nvs_to_spi ( nvs ); + struct spi_bus *bus = device->bus; + int rc; + + assert ( bus->mode == SPI_MODE_THREEWIRE ); + + DBGC ( device, "3wire %p reading %zd bytes at %04x\n", + device, len, address ); + + if ( ( rc = bus->rw ( bus, device, THREEWIRE_READ, address, + NULL, data, len ) ) != 0 ) { + DBGC ( device, "3wire %p could not read: %s\n", + device, strerror ( rc ) ); + return rc; + } + + return 0; +} + +/** + * Write data to three-wire device + * + * @v nvs NVS device + * @v address Address from which to read + * @v data Data buffer + * @v len Length of data buffer + * @ret rc Return status code + */ +int threewire_write ( struct nvs_device *nvs, unsigned int address, + const void *data, size_t len ) { + struct spi_device *device = nvs_to_spi ( nvs ); + struct spi_bus *bus = device->bus; + int rc; + + assert ( bus->mode == SPI_MODE_THREEWIRE ); + + DBGC ( device, "3wire %p writing %zd bytes at %04x\n", + device, len, address ); + + /* Enable device for writing */ + if ( ( rc = bus->rw ( bus, device, THREEWIRE_EWEN, + THREEWIRE_EWEN_ADDRESS, NULL, NULL, 0 ) ) != 0 ){ + DBGC ( device, "3wire %p could not enable writing: %s\n", + device, strerror ( rc ) ); + return rc; + } + + /* Write data */ + if ( ( rc = bus->rw ( bus, device, THREEWIRE_WRITE, address, + data, NULL, len ) ) != 0 ) { + DBGC ( device, "3wire %p could not write: %s\n", + device, strerror ( rc ) ); + return rc; + } + + /* Our model of an SPI bus doesn't provide a mechanism for + * "assert CS, wait for MISO to become high, so just wait for + * long enough to ensure that the write has completed. + */ + mdelay ( THREEWIRE_WRITE_MDELAY ); + + return 0; +} + +/** + * Autodetect device address length + * + * @v device SPI device + * @ret rc Return status code + */ +int threewire_detect_address_len ( struct spi_device *device ) { + struct nvs_device *nvs = &device->nvs; + int rc; + + DBGC ( device, "3wire %p autodetecting address length\n", device ); + + device->address_len = SPI_AUTODETECT_ADDRESS_LEN; + if ( ( rc = threewire_read ( nvs, 0, NULL, + ( 1 << nvs->word_len_log2 ) ) ) != 0 ) { + DBGC ( device, "3wire %p could not autodetect address " + "length: %s\n", device, strerror ( rc ) ); + return rc; + } + + DBGC ( device, "3wire %p autodetected address length %d\n", + device, device->address_len ); + return 0; +} |