#ifndef _IPXE_VIRT_OFFSET_H
#define _IPXE_VIRT_OFFSET_H

/**
 * @file
 *
 * Virtual offset memory model
 *
 * No currently supported machine provides a full 64 bits of physical
 * address space.  When we have ownership of the page tables (or
 * segmentation mechanism), we can therefore use the following model:
 *
 *   - For 32-bit builds: set up a circular map so that all 32-bit
 *     virtual addresses are at a fixed offset from the 32-bit
 *     physical addresses.
 *
 *   - For 64-bit builds: identity-map the required portion of the
 *     physical address space, place iPXE within the 32-bit physical
 *     address space, map iPXE using virtual addresses in the top part
 *     of the negative (kernel) address space, and optionally map the
 *     32-bit physical address space with attributes suitable for
 *     coherent DMA accesses.
 *
 * In both cases, we can define "virt_offset" as "the value to be
 * added to an address within iPXE's own image in order to obtain its
 * physical address".  With this definition:
 *
 *   - For 32-bit builds: conversion between physical and virtual
 *     addresses is a straightforward addition or subtraction of
 *     virt_offset, since the whole 32-bit address space is circular.
 *
 *   - For 64-bit builds: conversion from any valid physical address
 *     is a no-op (since all physical addresses are identity-mapped),
 *     and conversion from a virtual address to a physical address
 *     requires an addition of virt_offset if and only if the virtual
 *     address lies in the high negative portion of the address space
 *     (i.e. has the MSB set, but has the MSB clear after adding
 *     virt_offset).
 *
 * For x86_64-pcbios, we identity-map the low 4GB of address space
 * since the only accesses required above 4GB are for MMIO (typically
 * PCI devices with large memory BARs).
 *
 * For riscv64-sbi, we identity-map as much of the physical address
 * space as can be mapped by the paging model (Sv39, Sv48, or Sv57)
 * and create a coherent DMA mapping of the low 4GB.
 */

FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
FILE_SECBOOT ( PERMITTED );

#ifdef UACCESS_OFFSET
#define UACCESS_PREFIX_offset
#else
#define UACCESS_PREFIX_offset __offset_
#endif

/** Virtual address offset
 *
 * This is defined to be the value to be added to an address within
 * iPXE's own image in order to obtain its physical address, as
 * described above.
 */
extern const unsigned long virt_offset;

/** Allow for architecture-specific overrides of virt_offset */
#include <bits/virt_offset.h>

/**
 * Convert physical address to virtual address
 *
 * @v phys		Physical address
 * @ret virt		Virtual address
 */
static inline __always_inline void *
UACCESS_INLINE ( offset, phys_to_virt ) ( unsigned long phys ) {

	/* In a 64-bit build, any valid physical address is directly
	 * usable as a virtual address, since physical addresses are
	 * identity-mapped.
	 */
	if ( sizeof ( physaddr_t ) > sizeof ( uint32_t ) )
		return ( ( void * ) phys );

	/* In a 32-bit build: subtract virt_offset */
	return ( ( void * ) ( phys - virt_offset ) );
}

/**
 * Convert virtual address to physical address
 *
 * @v virt		Virtual address
 * @ret phys		Physical address
 */
static inline __always_inline physaddr_t
UACCESS_INLINE ( offset, virt_to_phys ) ( volatile const void *virt ) {
	const physaddr_t msb = ( 1ULL << ( 8 * sizeof ( physaddr_t ) - 1 ) );
	physaddr_t addr = ( ( physaddr_t ) virt );

	/* In a 64-bit build, any valid virtual address with the MSB
	 * clear is directly usable as a physical address, since it
	 * must lie within the identity-mapped portion.
	 *
	 * This test will typically reduce to a single "branch if less
	 * than zero" instruction.
	 */
	if ( ( sizeof ( physaddr_t ) > sizeof ( uint32_t ) ) &&
	     ( ! ( addr & msb ) ) ) {
		return addr;
	}

	/* In a 32-bit build or in a 64-bit build with a virtual
	 * address with the MSB set: add virt_offset
	 */
	addr += virt_offset;

	/* In a 64-bit build with an address that still has the MSB
	 * set after adding virt_offset: truncate the original virtual
	 * address to form a 32-bit physical address.
	 *
	 * This test will also typically reduce to a single "branch if
	 * less than zero" instruction.
	 */
	if ( ( sizeof ( physaddr_t ) > sizeof ( uint32_t ) ) &&
	     ( addr & msb ) ) {
		return ( ( uint32_t ) ( physaddr_t ) virt );
	}

	return addr;
}

#endif /* _IPXE_VIRT_OFFSET_H */
