root/src/add-ons/kernel/drivers/disk/nvme/compat/libnvme_haiku.cpp
/*
 * Copyright 2019-2022, Haiku, Inc. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *              Augustin Cavalier <waddlesplash>
 */

#include <debug.h>
#include <kernel/vm/vm.h>
#include <PCI.h>

extern "C" {
#include "nvme.h"
#include "nvme_log.h"
#include "nvme_mem.h"
#include "nvme_pci.h"
}


static pci_module_info* sPCIModule = NULL;


// #pragma mark - memory


int
nvme_mem_init()
{
        /* nothing to do */
        return 0;
}


void
nvme_mem_cleanup()
{
        /* nothing to do */
}


void*
nvme_mem_alloc_node(size_t size, size_t align, unsigned int node_id,
        phys_addr_t* paddr)
{
        size = ROUNDUP(size, B_PAGE_SIZE);

        virtual_address_restrictions virtualRestrictions = {};

        physical_address_restrictions physicalRestrictions = {};
        physicalRestrictions.alignment = align;

        void* address;
        area_id area = create_area_etc(B_SYSTEM_TEAM, "nvme physical buffer",
                size, B_CONTIGUOUS, B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA,
                0, 0, &virtualRestrictions, &physicalRestrictions, &address);
        if (area < 0)
                return NULL;

        if (paddr != NULL)
                *paddr = nvme_mem_vtophys(address);
        return address;
}


void*
nvme_malloc_node(size_t size, size_t align, unsigned int node_id)
{
        return nvme_mem_alloc_node(size, align, node_id, NULL);
}


void
nvme_free(void* addr)
{
        delete_area(area_for(addr));
}


phys_addr_t
nvme_mem_vtophys(void* vaddr)
{
        physical_entry entry;
        status_t status = get_memory_map((void*)vaddr, 1, &entry, 1);
        if (status != B_OK) {
                panic("nvme: get_memory_map failed for %p: %s\n",
                        (void*)vaddr, strerror(status));
                return NVME_VTOPHYS_ERROR;
        }

        return entry.address;
}


// #pragma mark - PCI


int
nvme_pci_init()
{
        status_t status = get_module(B_PCI_MODULE_NAME,
                (module_info**)&sPCIModule);
        return status;
}


int
nvme_pcicfg_read32(struct pci_device* dev, uint32_t* value, uint32_t offset)
{
        *value = sPCIModule->read_pci_config(dev->bus, dev->dev, dev->func, offset,
                sizeof(*value));
        return 0;
}


int
nvme_pcicfg_write32(struct pci_device* dev, uint32_t value, uint32_t offset)
{
        sPCIModule->write_pci_config(dev->bus, dev->dev, dev->func, offset,
                sizeof(value), value);
        return 0;
}


void
nvme_pcicfg_get_bar_addr_len(void* devhandle, unsigned int bar,
        uint64_t* _addr, uint64_t* _size)
{
        struct pci_device* dev = (struct pci_device*)devhandle;
        pci_info* info = (pci_info*)dev->pci_info;

        uint64 addr = info->u.h0.base_registers[bar];
        uint64 size = info->u.h0.base_register_sizes[bar];
        if ((info->u.h0.base_register_flags[bar] & PCI_address_type) == PCI_address_type_64) {
                addr |= (uint64)info->u.h0.base_registers[bar + 1] << 32;
                size |= (uint64)info->u.h0.base_register_sizes[bar + 1] << 32;
        }

        *_addr = addr;
        *_size = size;
}


int
nvme_pcicfg_map_bar(void* devhandle, unsigned int bar, bool read_only,
        void** mapped_addr)
{
        uint64 addr, size;
        nvme_pcicfg_get_bar_addr_len(devhandle, bar, &addr, &size);

        area_id area = map_physical_memory("nvme mapped bar", (phys_addr_t)addr, (size_t)size,
                B_ANY_KERNEL_ADDRESS, B_KERNEL_READ_AREA | (read_only ? 0 : B_KERNEL_WRITE_AREA),
                mapped_addr);
        if (area < B_OK)
                return area;

        return 0;
}


int
nvme_pcicfg_map_bar_write_combine(void* devhandle, unsigned int bar,
        void** mapped_addr)
{
        status_t status = nvme_pcicfg_map_bar(devhandle, bar, false, mapped_addr);
        if (status != 0)
                return status;

        // Turn on write combining for the area
        status = vm_set_area_memory_type(area_for(*mapped_addr),
                nvme_mem_vtophys(*mapped_addr), B_WRITE_COMBINING_MEMORY);
        if (status != 0)
                nvme_pcicfg_unmap_bar(devhandle, bar, *mapped_addr);
        return status;
}


int
nvme_pcicfg_unmap_bar(void* devhandle, unsigned int bar, void* addr)
{
        return delete_area(area_for(addr));
}


// #pragma mark - logging


void
nvme_log(enum nvme_log_level level, const char *format, ...)
{
        va_list ap;

        va_start(ap, format);
        nvme_vlog(level, format, ap);
        va_end(ap);
}


void
nvme_vlog(enum nvme_log_level level, const char *format, va_list ap)
{
        dvprintf(format, ap);
}