/*
 * (C) Copyright IBM Corporation 2006
 * All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * on the rights to use, copy, modify, merge, publish, distribute, sub
 * license, and/or sell copies of the Software, and to permit persons to whom
 * the Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the next
 * paragraph) shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.  IN NO EVENT SHALL
 * IBM AND/OR THEIR SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

/**
 * \file common_interface.c
 * Platform independent interface glue.
 *
 * \author Ian Romanick <idr@us.ibm.com>
 */

#include <stdlib.h>
#include <errno.h>

#include "pciaccess.h"
#include "pciaccess_private.h"

#include <byteswap.h>

#if __BYTE_ORDER == __BIG_ENDIAN
# define LETOH_16(x)   bswap_16(x)
# define HTOLE_16(x)   bswap_16(x)
# define LETOH_32(x)   bswap_32(x)
# define HTOLE_32(x)   bswap_32(x)
#else
# define LETOH_16(x)   (x)
# define HTOLE_16(x)   (x)
# define LETOH_32(x)   (x)
# define HTOLE_32(x)   (x)
#endif

/**
 * Read a device's expansion ROM.
 * 
 * Reads the device's expansion ROM and stores the data in the memory pointed
 * to by \c buffer.  The buffer must be at least \c pci_device::rom_size
 * bytes.
 *
 * \param dev    Device whose expansion ROM is to be read.
 * \param buffer Memory in which to store the ROM.
 * 
 * \return
 * Zero on success or an \c errno value on failure.
 */
int
pci_device_read_rom( struct pci_device * dev, void * buffer )
{
    if ( (dev == NULL) || (buffer == NULL) ) {
	return EFAULT;
    }


    return (pci_sys->methods->read_rom)( dev, buffer );
}


/**
 * Probe a PCI device to learn information about the device.
 * 
 * Probes a PCI device to learn various information about the device.  Before
 * calling this function, the only public fields in the \c pci_device
 * structure that have valid values are \c pci_device::domain,
 * \c pci_device::bus, \c pci_device::dev, and \c pci_device::func.
 * 
 * \param dev  Device to be probed.
 * 
 * \return
 * Zero on succes or an \c errno value on failure.
 */
int
pci_device_probe( struct pci_device * dev )
{
    if ( dev == NULL ) {
	return EFAULT;
    }


    return (pci_sys->methods->probe)( dev );
}


/**
 * Map the specified BAR so that it can be accessed by the CPU.
 *
 * Maps the specified BAR for acces by the processor.  The pointer to the
 * mapped region is stored in the \c pci_mem_region::memory pointer for the
 * BAR.
 *
 * \param dev          Device whose memory region is to be mapped.
 * \param region       Region, on the range [0, 5], that is to be mapped.
 * \param write_enable Map for writing (non-zero).
 * 
 * \return
 * Zero on success or an \c errno value on failure.
 *
 * \sa pci_device_unmap_region
 */
int
pci_device_map_region( struct pci_device * dev, unsigned region,
		       int write_enable )
{
    if ( dev == NULL ) {
	return EFAULT;
    }

    if ( (region > 5) || (dev->regions[ region ].size == 0) ) {
	return ENOENT;
    }

    if ( dev->regions[ region ].memory != NULL ) {
	return 0;
    }
    
    return (pci_sys->methods->map)( dev, region, write_enable );
}


/**
 * Unmap the specified BAR so that it can no longer be accessed by the CPU.
 *
 * Unmaps the specified BAR that was previously mapped via
 * \c pci_device_map_region.
 *
 * \param dev          Device whose memory region is to be mapped.
 * \param region       Region, on the range [0, 5], that is to be mapped.
 * 
 * \return
 * Zero on success or an \c errno value on failure.
 *
 * \sa pci_device_map_region
 */
int
pci_device_unmap_region( struct pci_device * dev, unsigned region )
{
    if ( dev == NULL ) {
	return EFAULT;
    }

    if ( (region > 5) || (dev->regions[ region ].size == 0) ) {
	return ENOENT;
    }

    if ( dev->regions[ region ].memory == NULL ) {
	return 0;
    }
    
    return (pci_sys->methods->unmap)( dev, region );
}


/**
 * Read arbitrary bytes from device's PCI config space
 *
 * Reads data from the device's PCI configuration space.  As with the system
 * read command, less data may be returned, without an error, than was
 * requested.  This is particuarly the case if a non-root user tries to read
 * beyond the first 64-bytes of configuration space.
 *
 * \param dev         Device whose PCI configuration data is to be read.
 * \param data        Location to store the data
 * \param offset      Initial byte offset to read
 * \param size        Total number of bytes to read
 * \param bytes_read  Location to store the actual number of bytes read.  This
 *                    pointer may be \c NULL.
 *
 * \returns
 * Zero on success or an errno value on failure.
 *
 * \note
 * Data read from PCI configuartion space using this routine is \b not
 * byte-swapped to the host's byte order.  PCI configuration data is always
 * stored in little-endian order, and that is what this routine returns.
 */
int
pci_device_cfg_read( struct pci_device * dev, void * data,
		     pciaddr_t offset, pciaddr_t size, 
		     pciaddr_t * bytes_read )
{
    pciaddr_t  scratch;

    if ( (dev == NULL) || (data == NULL) ) {
	return EFAULT;
    }

    return pci_sys->methods->read( dev, data, offset, size,
				   (bytes_read == NULL) 
				   ? & scratch : bytes_read );
}


int
pci_device_cfg_read_u8( struct pci_device * dev, uint8_t * data,
			pciaddr_t offset )
{
    pciaddr_t bytes;
    int err = pci_device_cfg_read( dev, data, offset, 1, & bytes );
    
    if ( (err == 0) && (bytes != 1) ) {
	err = ENODATA;
    }

    return err;
}


int
pci_device_cfg_read_u16( struct pci_device * dev, uint16_t * data,
			 pciaddr_t offset )
{
    pciaddr_t bytes;
    int err = pci_device_cfg_read( dev, data, offset, 2, & bytes );
    
    if ( (err == 0) && (bytes != 2) ) {
	err = ENODATA;
    }

    *data = LETOH_16( *data );
    return err;
}


int
pci_device_cfg_read_u32( struct pci_device * dev, uint32_t * data,
			 pciaddr_t offset )
{
    pciaddr_t bytes;
    int err = pci_device_cfg_read( dev, data, offset, 4, & bytes );

    if ( (err == 0) && (bytes != 4) ) {
	err = ENODATA;
    }

    *data = LETOH_32( *data );
    return err;
}


/**
 * Write arbitrary bytes to device's PCI config space
 *
 * Writess data to the device's PCI configuration space.  As with the system
 * write command, less data may be written, without an error, than was
 * requested.
 *
 * \param dev         Device whose PCI configuration data is to be written.
 * \param data        Location of the source data
 * \param offset      Initial byte offset to write
 * \param size        Total number of bytes to write
 * \param bytes_read  Location to store the actual number of bytes written.
 *                    This pointer may be \c NULL.
 *
 * \returns
 * Zero on success or an errno value on failure.
 *
 * \note
 * Data written to PCI configuartion space using this routine is \b not
 * byte-swapped from the host's byte order.  PCI configuration data is always
 * stored in little-endian order, so data written with this routine should be
 * put in that order in advance.
 */
int
pci_device_cfg_write( struct pci_device * dev, const void * data,
		      pciaddr_t offset, pciaddr_t size, 
		      pciaddr_t * bytes_written )
{
    pciaddr_t  scratch;

    if ( (dev == NULL) || (data == NULL) ) {
	return EFAULT;
    }

    return pci_sys->methods->write( dev, data, offset, size,
				    (bytes_written == NULL) 
				    ? & scratch : bytes_written );
}


int
pci_device_cfg_write_u8( struct pci_device * dev, const uint8_t * data,
			 pciaddr_t offset )
{
    pciaddr_t bytes;
    int err = pci_device_cfg_write( dev, data, offset, 1, & bytes );

    if ( (err == 0) && (bytes != 1) ) {
	err = ENOSPC;
    }


    return err;
}
  

int
pci_device_cfg_write_u16( struct pci_device * dev, const uint16_t * data,
			  pciaddr_t offset )
{
    pciaddr_t bytes;
    const uint16_t temp = HTOLE_16( *data );
    int err = pci_device_cfg_write( dev, & temp, offset, 2, & bytes );

    if ( (err == 0) && (bytes != 2) ) {
	err = ENOSPC;
    }


    return err;
}


int
pci_device_cfg_write_u32( struct pci_device * dev, const uint32_t * data,
			  pciaddr_t offset )
{
    pciaddr_t bytes;
    const uint32_t temp = HTOLE_32( *data );
    int err = pci_device_cfg_write( dev, & temp, offset, 4, & bytes );

    if ( (err == 0) && (bytes != 4) ) {
	err = ENOSPC;
    }


    return err;
}
