# SPDX-License-Identifier: (GPL-2.0 OR Linux-OpenIB)
# Copyright (c) 2018, Mellanox Technologies. All rights reserved. See COPYING file

#cython: language_level=3

from posix.stdlib cimport posix_memalign as c_posix_memalign
from libc.stdlib cimport malloc as c_malloc, free as c_free
from posix.mman cimport mmap as c_mmap, munmap as c_munmap, madvise as c_madvise

from libc.stdint cimport uintptr_t, uint32_t, uint64_t
from pyverbs.base import PyverbsRDMAErrno
from libc.string cimport memcpy
from libc.string cimport memset
cimport posix.mman as mm

cdef extern from 'sys/mman.h':
    cdef void* MAP_FAILED
    cdef int MADV_DONTNEED

cdef extern from 'endian.h':
    unsigned long htobe32(unsigned long host_32bits)
    unsigned long htobe64(unsigned long host_64bits)


def mmap(addr=0, length=100, prot=mm.PROT_READ | mm.PROT_WRITE,
         flags=mm.MAP_PRIVATE | mm.MAP_ANONYMOUS, fd=0, offset=0):
    """
    Python wrapper for sys mmap function
    :param addr: Address to mmap the memory
    :param length: The length of the requested memory in bytes
    :param prot: Indicate the protection of this memory
    :param flags: Specify speicific flags to this memory
    :param fd: File descriptor to mmap specific file
    :param offset: Offset to use when mmap
    :return: The address to the mapped memory
    """
    # uintptr_t is guaranteed to be large enough to hold any pointer.
    # In order to safely cast addr to void*, it is firstly cast to uintptr_t.
    ptr = c_mmap(<void*><uintptr_t>addr, length, prot, flags, fd, offset)
    if <void *>ptr == MAP_FAILED:
        raise MemoryError('Failed to mmap memory')
    return <uintptr_t> ptr


def madvise(addr, length, flags=MADV_DONTNEED):
    """
    Python wrapper for sys madvise function
    :param addr: Address of the memory to be advised about
    :param length: The length of the requested memory in bytes
    :param flags: Specify speicific flags to this memory
    """
    rc = c_madvise(<void*><uintptr_t>addr, length, flags)
    if rc:
        raise PyverbsRDMAErrno('Failed to madvise memory')


def munmap(addr, length):
    """
    Python wrapper for sys munmap function
    :param addr: The address of the mapped memory to unmap
    :param length: The length of this mapped memory
    """
    ret = c_munmap(<void*><uintptr_t>addr, length)
    if ret:
        raise MemoryError('Failed to munmap requested memory')


def malloc(size):
    """
    Python wrapper for stdlib malloc function
    :param size: The size of the memory block in bytes
    :return: The address of the allocated memory, or 0 if the request fails
    """
    ptr = c_malloc(size)
    if not ptr:
        raise MemoryError('Failed to allocate memory')
    return <uintptr_t>ptr


def posix_memalign(size, alignment=8):
    """
    Python wrapper for the stdlib posix_memalign function.
    The function calls posix_memalign and memsets the memory to 0.
    :param size: The size of the memory block in bytes
    :param alignment: Alignment of the allocated memory, must be a power of two
    :return: The address of the allocated memory, which is a multiple of
             alignment.
    """
    cdef void* ptr
    ret = c_posix_memalign(&ptr, alignment, size)
    if ret:
        raise MemoryError('Failed to allocate memory ({err}'.format(ret))
    memset(ptr, 0, size)
    return <uintptr_t>ptr


def free(ptr):
    """
    Python wrapper for stdlib free function
    :param ptr: The address of a previously allocated memory block
    """
    c_free(<void*><uintptr_t>ptr)


def writebe32(addr, val, offset=0):
    """
    Write 32-bit value <val> as Big Endian to address <addr> and offset <offset>
    :param addr: The start of the address to write the value to
    :param val: Value to write
    :param offset: Offset of the address  to write the value to (in 4-bytes)
    """
    (<uint32_t*><void*><uintptr_t>addr)[offset] = htobe32(val)


def writebe64(addr, val, offset=0):
    """
    Write 64-bit value <val> as Big Endian to address <addr> and offset <offset>
    :param addr: The start of the address to write the value to
    :param val: Value to write
    :param offset: Offset of the address  to write the value to (in 8-bytes)
    """
    (<uint64_t*><void*><uintptr_t>addr)[offset] = htobe64(val)


def write(addr, data, length, offset=0):
    """
    Write user data to a given address
    :param addr: The start of the address to write to
    :param data: User data to write (string or bytes)
    :param length: Length of the data to write (in bytes)
    :param offset: Writing offset (in bytes)
    """
    cdef int off = offset
    cdef void* buf = <void*><uintptr_t>addr
    # If data is a string, cast it to bytes as Python3 doesn't
    # automatically convert it.
    if isinstance(data, str):
        data = data.encode()
    memcpy(<char*>(buf + off), <char *>data, length)


def read32(addr, offset=0):
    """
    Read 32-bit value from address <addr> and offset <offset>
    :param addr: The start of the address to read from
    :param offset: Offset of the address to read from (in 4-bytes)
    :return: The read value
    """
    return (<uint32_t*><uintptr_t>addr)[offset]


def read64(addr, offset=0):
    """
    Read 64-bit value from address <addr> and offset <offset>
    :param addr: The start of the address to read from
    :param offset: Offset of the address to read from (in 8-bytes)
    :return: The read value
    """
    return (<uint64_t*><uintptr_t>addr)[offset]


def read(addr, length, offset=0):
    """
    Reads data from a given address
    :param addr: The start of the address to read from
    :param length: Length of data to read (in bytes)
    :param offset: Reading offset (in bytes)
    :return: The data on the buffer in the requested offset (bytes)
    """
    cdef char *data
    data = <char*><uintptr_t>(addr + offset)
    return data[:length]


# protection bits for mmap/mprotect
PROT_EXEC_ = mm.PROT_EXEC
PROT_READ_ = mm.PROT_READ
PROT_WRITE_ = mm.PROT_WRITE
PROT_NONE_ = mm.PROT_NONE

# flag bits for mmap
MAP_PRIVATE_ = mm.MAP_PRIVATE
MAP_SHARED_ = mm.MAP_SHARED
MAP_FIXED_ = mm.MAP_FIXED
MAP_ANONYMOUS_ = mm.MAP_ANONYMOUS
MAP_STACK_ = mm.MAP_STACK
MAP_LOCKED_ = mm.MAP_LOCKED
MAP_HUGETLB_ = mm.MAP_HUGETLB
MAP_POPULATE_ = mm.MAP_POPULATE
MAP_NORESERVE_ = mm.MAP_NORESERVE
MAP_GROWSDOWN_ = mm.MAP_GROWSDOWN
