#ifndef _IPXE_DMA_H
#define _IPXE_DMA_H

/** @file
 *
 * DMA mappings
 *
 */

FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );

#include <stdint.h>
#include <ipxe/api.h>
#include <ipxe/io.h>
#include <ipxe/malloc.h>
#include <ipxe/umalloc.h>
#include <config/ioapi.h>

#ifdef DMAAPI_OP
#define DMAAPI_PREFIX_op
#else
#define DMAAPI_PREFIX_op __op_
#endif

#ifdef DMAAPI_FLAT
#define DMAAPI_PREFIX_flat
#else
#define DMAAPI_PREFIX_flat __flat_
#endif

/** A DMA mapping */
struct dma_mapping {
	/** Address offset
	 *
	 * This is the value that must be added to a physical address
	 * within the mapping in order to produce the corresponding
	 * device-side DMA address.
	 */
	physaddr_t offset;
	/** DMA device (if unmapping is required) */
	struct dma_device *dma;
	/** Platform mapping token */
	void *token;
};

/** A DMA-capable device */
struct dma_device {
	/** DMA operations */
	struct dma_operations *op;
	/** Addressable space mask */
	physaddr_t mask;
	/** Total number of mappings (for debugging) */
	unsigned int mapped;
	/** Total number of allocations (for debugging) */
	unsigned int allocated;
};

/** DMA operations */
struct dma_operations {
	/**
	 * Map buffer for DMA
	 *
	 * @v dma		DMA device
	 * @v map		DMA mapping to fill in
	 * @v addr		Buffer address
	 * @v len		Length of buffer
	 * @v flags		Mapping flags
	 * @ret rc		Return status code
	 */
	int ( * map ) ( struct dma_device *dma, struct dma_mapping *map,
			physaddr_t addr, size_t len, int flags );
	/**
	 * Unmap buffer
	 *
	 * @v dma		DMA device
	 * @v map		DMA mapping
	 */
	void ( * unmap ) ( struct dma_device *dma, struct dma_mapping *map );
	/**
	 * Allocate and map DMA-coherent buffer
	 *
	 * @v dma		DMA device
	 * @v map		DMA mapping to fill in
	 * @v len		Length of buffer
	 * @v align		Physical alignment
	 * @ret addr		Buffer address, or NULL on error
	 */
	void * ( * alloc ) ( struct dma_device *dma, struct dma_mapping *map,
			     size_t len, size_t align );
	/**
	 * Unmap and free DMA-coherent buffer
	 *
	 * @v dma		DMA device
	 * @v map		DMA mapping
	 * @v addr		Buffer address
	 * @v len		Length of buffer
	 */
	void ( * free ) ( struct dma_device *dma, struct dma_mapping *map,
			  void *addr, size_t len );
	/**
	 * Allocate and map DMA-coherent buffer from external (user) memory
	 *
	 * @v dma		DMA device
	 * @v map		DMA mapping to fill in
	 * @v len		Length of buffer
	 * @v align		Physical alignment
	 * @ret addr		Buffer address, or NULL on error
	 */
	userptr_t ( * umalloc ) ( struct dma_device *dma,
				  struct dma_mapping *map,
				  size_t len, size_t align );
	/**
	 * Unmap and free DMA-coherent buffer from external (user) memory
	 *
	 * @v dma		DMA device
	 * @v map		DMA mapping
	 * @v addr		Buffer address
	 * @v len		Length of buffer
	 */
	void ( * ufree ) ( struct dma_device *dma, struct dma_mapping *map,
			   userptr_t addr, size_t len );
	/**
	 * Set addressable space mask
	 *
	 * @v dma		DMA device
	 * @v mask		Addressable space mask
	 */
	void ( * set_mask ) ( struct dma_device *dma, physaddr_t mask );
};

/** Device will read data from host memory */
#define DMA_TX 0x01

/** Device will write data to host memory */
#define DMA_RX 0x02

/** Device will both read data from and write data to host memory */
#define DMA_BI ( DMA_TX | DMA_RX )

/**
 * Calculate static inline DMA I/O API function name
 *
 * @v _prefix		Subsystem prefix
 * @v _api_func		API function
 * @ret _subsys_func	Subsystem API function
 */
#define DMAAPI_INLINE( _subsys, _api_func ) \
	SINGLE_API_INLINE ( DMAAPI_PREFIX_ ## _subsys, _api_func )

/**
 * Provide a DMA I/O API implementation
 *
 * @v _prefix		Subsystem prefix
 * @v _api_func		API function
 * @v _func		Implementing function
 */
#define PROVIDE_DMAAPI( _subsys, _api_func, _func ) \
	PROVIDE_SINGLE_API ( DMAAPI_PREFIX_ ## _subsys, _api_func, _func )

/**
 * Provide a static inline DMA I/O API implementation
 *
 * @v _prefix		Subsystem prefix
 * @v _api_func		API function
 */
#define PROVIDE_DMAAPI_INLINE( _subsys, _api_func ) \
	PROVIDE_SINGLE_API_INLINE ( DMAAPI_PREFIX_ ## _subsys, _api_func )

/**
 * Map buffer for DMA
 *
 * @v dma		DMA device
 * @v map		DMA mapping to fill in
 * @v addr		Buffer address
 * @v len		Length of buffer
 * @v flags		Mapping flags
 * @ret rc		Return status code
 */
static inline __always_inline int
DMAAPI_INLINE ( flat, dma_map ) ( struct dma_device *dma,
				  struct dma_mapping *map,
				  physaddr_t addr __unused,
				  size_t len __unused, int flags __unused ) {

	/* Increment mapping count (for debugging) */
	if ( DBG_LOG ) {
		map->dma = dma;
		dma->mapped++;
	}

	return 0;
}

/**
 * Unmap buffer
 *
 * @v map		DMA mapping
 */
static inline __always_inline void
DMAAPI_INLINE ( flat, dma_unmap ) ( struct dma_mapping *map ) {

	/* Decrement mapping count (for debugging) */
	if ( DBG_LOG ) {
		assert ( map->dma != NULL );
		map->dma->mapped--;
		map->dma = NULL;
	}
}

/**
 * Allocate and map DMA-coherent buffer
 *
 * @v dma		DMA device
 * @v map		DMA mapping to fill in
 * @v len		Length of buffer
 * @v align		Physical alignment
 * @ret addr		Buffer address, or NULL on error
 */
static inline __always_inline void *
DMAAPI_INLINE ( flat, dma_alloc ) ( struct dma_device *dma,
				    struct dma_mapping *map,
				    size_t len, size_t align ) {
	void *addr;

	/* Allocate buffer */
	addr = malloc_phys ( len, align );

	/* Increment mapping count (for debugging) */
	if ( DBG_LOG && addr ) {
		map->dma = dma;
		dma->mapped++;
	}

	return addr;
}

/**
 * Unmap and free DMA-coherent buffer
 *
 * @v map		DMA mapping
 * @v addr		Buffer address
 * @v len		Length of buffer
 */
static inline __always_inline void
DMAAPI_INLINE ( flat, dma_free ) ( struct dma_mapping *map,
				   void *addr, size_t len ) {

	/* Free buffer */
	free_phys ( addr, len );

	/* Decrement mapping count (for debugging) */
	if ( DBG_LOG ) {
		assert ( map->dma != NULL );
		map->dma->mapped--;
		map->dma = NULL;
	}
}

/**
 * Allocate and map DMA-coherent buffer from external (user) memory
 *
 * @v dma		DMA device
 * @v map		DMA mapping to fill in
 * @v len		Length of buffer
 * @v align		Physical alignment
 * @ret addr		Buffer address, or NULL on error
 */
static inline __always_inline userptr_t
DMAAPI_INLINE ( flat, dma_umalloc ) ( struct dma_device *dma,
				      struct dma_mapping *map,
				      size_t len, size_t align __unused ) {
	userptr_t addr;

	/* Allocate buffer */
	addr = umalloc ( len );

	/* Increment mapping count (for debugging) */
	if ( DBG_LOG && addr ) {
		map->dma = dma;
		dma->mapped++;
	}

	return addr;
}

/**
 * Unmap and free DMA-coherent buffer from external (user) memory
 *
 * @v map		DMA mapping
 * @v addr		Buffer address
 * @v len		Length of buffer
 */
static inline __always_inline void
DMAAPI_INLINE ( flat, dma_ufree ) ( struct dma_mapping *map,
				    userptr_t addr, size_t len __unused ) {

	/* Free buffer */
	ufree ( addr );

	/* Decrement mapping count (for debugging) */
	if ( DBG_LOG ) {
		assert ( map->dma != NULL );
		map->dma->mapped--;
		map->dma = NULL;
	}
}

/**
 * Set addressable space mask
 *
 * @v dma		DMA device
 * @v mask		Addressable space mask
 */
static inline __always_inline void
DMAAPI_INLINE ( flat, dma_set_mask ) ( struct dma_device *dma __unused,
				       physaddr_t mask __unused ) {

	/* Nothing to do */
}

/**
 * Get DMA address from physical address
 *
 * @v map		DMA mapping
 * @v addr		Physical address within the mapped region
 * @ret addr		Device-side DMA address
 */
static inline __always_inline physaddr_t
DMAAPI_INLINE ( flat, dma_phys ) ( struct dma_mapping *map __unused,
				   physaddr_t addr ) {

	/* Use physical address as device address */
	return addr;
}

/**
 * Get DMA address from physical address
 *
 * @v map		DMA mapping
 * @v addr		Physical address within the mapped region
 * @ret addr		Device-side DMA address
 */
static inline __always_inline physaddr_t
DMAAPI_INLINE ( op, dma_phys ) ( struct dma_mapping *map, physaddr_t addr ) {

	/* Adjust physical address using mapping offset */
	return ( addr + map->offset );
}

/**
 * Map buffer for DMA
 *
 * @v dma		DMA device
 * @v map		DMA mapping to fill in
 * @v addr		Buffer address
 * @v len		Length of buffer
 * @v flags		Mapping flags
 * @ret rc		Return status code
 */
int dma_map ( struct dma_device *dma, struct dma_mapping *map,
	      physaddr_t addr, size_t len, int flags );

/**
 * Unmap buffer
 *
 * @v map		DMA mapping
 */
void dma_unmap ( struct dma_mapping *map );

/**
 * Allocate and map DMA-coherent buffer
 *
 * @v dma		DMA device
 * @v map		DMA mapping to fill in
 * @v len		Length of buffer
 * @v align		Physical alignment
 * @ret addr		Buffer address, or NULL on error
 */
void * dma_alloc ( struct dma_device *dma, struct dma_mapping *map,
		   size_t len, size_t align );

/**
 * Unmap and free DMA-coherent buffer
 *
 * @v map		DMA mapping
 * @v addr		Buffer address
 * @v len		Length of buffer
 */
void dma_free ( struct dma_mapping *map, void *addr, size_t len );

/**
 * Allocate and map DMA-coherent buffer from external (user) memory
 *
 * @v dma		DMA device
 * @v map		DMA mapping to fill in
 * @v len		Length of buffer
 * @v align		Physical alignment
 * @ret addr		Buffer address, or NULL on error
 */
userptr_t dma_umalloc ( struct dma_device *dma, struct dma_mapping *map,
			size_t len, size_t align );

/**
 * Unmap and free DMA-coherent buffer from external (user) memory
 *
 * @v map		DMA mapping
 * @v addr		Buffer address
 * @v len		Length of buffer
 */
void dma_ufree ( struct dma_mapping *map, userptr_t addr, size_t len );

/**
 * Set addressable space mask
 *
 * @v dma		DMA device
 * @v mask		Addressable space mask
 */
void dma_set_mask ( struct dma_device *dma, physaddr_t mask );

/**
 * Get DMA address from physical address
 *
 * @v map		DMA mapping
 * @v addr		Physical address within the mapped region
 * @ret addr		Device-side DMA address
 */
physaddr_t dma_phys ( struct dma_mapping *map, physaddr_t addr );

/**
 * Get DMA address from virtual address
 *
 * @v map		DMA mapping
 * @v addr		Virtual address within the mapped region
 * @ret addr		Device-side DMA address
 */
static inline __always_inline physaddr_t dma ( struct dma_mapping *map,
					       void *addr ) {

	/* Get DMA address from corresponding physical address */
	return dma_phys ( map, virt_to_phys ( addr ) );
}

/**
 * Check if DMA unmapping is required
 *
 * @v map		DMA mapping
 * @v unmap		Unmapping is required
 */
static inline __always_inline int dma_mapped ( struct dma_mapping *map ) {

	/* Unmapping is required if a DMA device was recorded */
	return ( map->dma != NULL );
}

/**
 * Initialise DMA device
 *
 * @v dma		DMA device
 * @v op		DMA operations
 */
static inline __always_inline void dma_init ( struct dma_device *dma,
					      struct dma_operations *op ) {

	/* Set operations table */
	dma->op = op;
}

/**
 * Set 64-bit addressable space mask
 *
 * @v dma		DMA device
 */
static inline __always_inline void
dma_set_mask_64bit ( struct dma_device *dma ) {

	/* Set mask to maximum physical address */
	dma_set_mask ( dma, ~( ( physaddr_t ) 0 ) );
}

#endif /* _IPXE_DMA_H */
