<commit_before>/**
 * \file
 * \brief DriverKit IOMMU Client
 *
 * Contains functions to request mappings from the IOMMU service
 */
/*
 * Copyright (c) 2018, ETH Zurich.
 * All rights reserved.
 *
 * This file is distributed under the terms in the attached LICENSE file.
 * If you do not find this file, copies can be found by writing to:
 * ETH Zurich D-INFK, Universitaetstr. 6, CH-8092 Zurich.
 * Attn: Systems Group.
 */
#include <stdlib.h>

#include <barrelfish/barrelfish.h>
#include <target/x86_64/barrelfish_kpi/paging_target.h>
#include <driverkit/driverkit.h>
#include <driverkit/iommu.h>
#include <skb/skb.h>

#include <if/iommu_defs.h>
#include <if/mem_defs.h>
#include <if/iommu_rpcclient_defs.h>

#include "debug.h"

struct iommu_vnode_l2
{
    enum objtype           vnode_type;
    struct capref          vnode;
    dmem_daddr_t           address_start;
    dmem_daddr_t           address_end;
    size_t                 num_frames;
    struct capref          frames[];
};


struct iommu_vnode_l3
{
    enum objtype           vnode_type;
    struct capref          vnode;
    dmem_daddr_t           address_start;
    dmem_daddr_t           address_end;
    size_t                 num_children;
    struct iommu_vnode_l2 *children[];
};


struct iommu_client
{
    ///< whether the IOMMU is enabled or not
    bool enabled;

    ///< the binding to the IOMMU service
    struct iommu_binding *binding;

    ///< endpoint to the iommu service
    struct capref endpoint;

    ///< the waitset to be used
    struct waitset *waitset;

    ///< the maximum supported page size
    size_t max_page_size;

    ///< error value for async errors
    errval_t error;

    ///< the policy to be employed by the mapping function
    iommu_vspace_policy_t policy;

    ///< ObjType of the root vnode
    enum objtype root_vnode_type;

    ///< the capability to the root vnode
    struct capref rootvnode;

    ///< slot used in the root vnode
    uint16_t rootvnode_slot;

    ///< pointer to the vnode information
    struct iommu_vnode_l3 *vnode_l3;

    ///< model node of the protected device
    int32_t nodeid;
};

///< the default iommu client
static struct iommu_client *default_client;

///< the default policy to be used
static iommu_vspace_policy_t iomm_vspace_default_policy = IOMMU_VSPACE_POLICY_MIRROR;


/*
 * TODO: proper implementation of this
 */

static inline bool iommu_vnode_type_supported(struct iommu_client *st,
                                              enum objtype type)
{
    return type_is_vnode(type);
}

/*
 * allocates a piece of RAM that is suitable for holdings page tables.
 * ideally this should be in the region of "protected" or reserved memory.
 * (see IOMMU. this region can be above 4G, there are two regions)\
 *
 * The ram should be accessible by the IOMMU and/or the device driver
 */
static inline errval_t iommu_alloc_ram_for_vnode(struct iommu_client *st,
                                                 enum objtype type,
                                                 struct capref *retcap)
{
    return ram_alloc(retcap, vnode_objbits(type));
}

/*
 *  Returns this process nodeid. It lazily adds the process' model node
 *  and returns it's identifier.
 */
static int32_t get_own_nodeid(void) {
    static int32_t nodeid = -1;
    errval_t err;
    if(nodeid == -1){
        err = skb_execute_query("add_process_alloc(X), write(X)");
        assert(err_is_ok(err));
        err = skb_read_output("%d", &nodeid);
        assert(err_is_ok(err));
        DRIVERKIT_DEBUG("Instantiated new process model node, nodeid=%"PRIi32"\n",
                nodeid);
    }
    return nodeid;
}

#define MODE_ALLOC_COMMON 0 
#define MODE_MAP_COMMON 1 

static errval_t alloc_common(int mode, int bits,
    int32_t nodeid1, uint64_t *node1addr,
    int32_t nodeid2, uint64_t *node2addr,
    uint64_t *physaddr) {

    debug_printf("alloc_common for mode=%d, bits=%d, nodeid1=%d, nodeid2=%d",
            mode, bits, nodeid1, nodeid2);

    errval_t err;
    if(mode == MODE_ALLOC_COMMON){
        err = skb_execute_query("alloc_common(%d,%"PRIi32",%"PRIi32").",
                bits, nodeid1, nodeid2);
    } else {
        assert(mode == MODE_MAP_COMMON);
        // We need physaddr to be passed
        assert(physaddr != NULL);
        assert(*physaddr != 0);
        err = skb_execute_query("map_common(%d,%"PRIu64",%"PRIi32",%"PRIi32").",
                bits, *physaddr, nodeid1, nodeid2);
    }

    DEBUG_SKB_ERR(err,"in alloc_common");
    if(err_is_fail(err)){
        skb_execute_query("dec_net_debug");
        return err;
    }
    char * skb_list_output =  strdup(skb_get_output());
    assert(skb_list_output);
    struct list_parser_status status;
    skb_read_list_init_offset(&status, skb_list_output, 0);

    uint64_t address = 0, nodeid=0;
    int list_idx = 0;
    while(skb_read_list(&status, "name(%"SCNu64", %"SCNu64")", &address, &nodeid)) {
        uint64_t *dest = NULL;
        switch(list_idx){
            case 0: dest = node1addr; break;
            case 1: dest = node2addr; break;
            case 2: dest = physaddr; break;
        }
        if(dest) *dest = address;
        list_idx++;
    }
    free(skb_list_output);
    if(list_idx != 3){
        return SKB_ERR_CONVERSION_ERROR;
    }

    return SYS_ERR_OK;
}

/*
 * allocates a piece of ram to be mapped into the driver and the devices
 * address spaces
 */
static inline errval_t iommu_alloc_ram(struct iommu_client *st,
                                                 size_t bytes,
                                                 struct capref *retcap)
{
    // TODO: This function has to pass the rootvnodeslot from iommu_get_mapping
    // region to the allocator function to get mem from that region.
    errval_t err, msgerr;

    if (bytes < (LARGE_PAGE_SIZE)) {
        bytes = LARGE_PAGE_SIZE;
    }
    int bits = log2ceil(bytes);
    bytes = 1 << bits;

    debug_printf("iommu_alloc_ram bytes=%lu\n", bytes);

    // Alloc cap slot
    err = slot_alloc(retcap);
    if (err_is_fail(err)) {
        return err_push(err, LIB_ERR_SLOT_ALLOC);
    }

    struct mem_binding * b = get_mem_client();

    int32_t device_nodeid = driverkit_iommu_get_nodeid(st);
    int32_t my_nodeid = get_own_nodeid();
    uint64_t base_addr=0;

    err = alloc_common(MODE_ALLOC_COMMON, bits, device_nodeid, NULL, my_nodeid,
            NULL, &base_addr);
    if(err_is_fail(err)){
        DEBUG_ERR(err, "allocate_common");
        return err;
    }

    debug_printf("Determined addr=0x%"PRIx64" as base address for bits=%d request\n",
            base_addr, bits);

    err = b->rpc_tx_vtbl.allocate(b, bits, base_addr, base_addr + bytes,
            &msgerr, retcap);
    if(err_is_fail(err)){
        DEBUG_ERR(err, "allocate RPC");
        return err;
    }
    if(err_is_fail(msgerr)){
        DEBUG_ERR(msgerr, "allocate");
        return msgerr;
    }
    debug_printf("alloc_ram_for_frame success\n");
    return SYS_ERR_OK;
}

static inline errval_t iommu_alloc_frame(struct iommu_client *cl,
                                                 size_t bytes,
                                                 struct capref *retcap)
{
    if (bytes < (LARGE_PAGE_SIZE)) {
        bytes = LARGE_PAGE_SIZE;
    }

    // Allocate RAM cap
    struct capref ramcap;
    errval_t err = iommu_alloc_ram(cl, bytes, &ramcap);
    if(err_is_fail(err)){
        return err;            
    }

    // Alloc cap slot
    err = slot_alloc(retcap);
    if (err_is_fail(err)) {
        return err_push(err, LIB_ERR_SLOT_ALLOC);
    }

    // Get bits
    assert(bytes > 0);
    uint8_t bits = log2ceil(bytes);
    assert((1UL << bits) >= bytes);

    // This is doing what "create_ram_descendant" in
    // lib/barrelfish/capabilities.c is doing.
    err = cap_retype(*retcap, ramcap, 0, ObjType_Frame, (1UL << bits), 1);
    if (err_is_fail(err)) {
        return err_push(err, LIB_ERR_CAP_RETYPE);
    }

    err = cap_destroy(ramcap);
    if (err_is_fail(err)) {
        return err_push(err, LIB_ERR_CAP_DESTROY);
    }

    return SYS_ERR_OK;
}

#define MAPPING_REGION_START (512UL << 31)
#define MAPPING_REGION_SIZE (512UL << 30)

/*
 * returns a region of memory
 */
static inline errval_t iommu_alloc_vregion(struct iommu_client *st,
                                           struct capref mem,
                                           lvaddr_t *driver,
                                           dmem_daddr_t *device)
{
    errval_t err;
    struct frame_identity id;
    err = invoke_frame_identify(mem, &id);
    if (err_is_fail(err)) {
        return err;
    }

    if(st == NULL){
        *device = id.base;   
        *driver = 0;
        return SYS_ERR_OK;
    }

    assert(id.bytes >= LARGE_PAGE_SIZE);
    assert(st != NULL);

    int bits = log2ceil(id.bytes);
    int32_t device_nodeid = driverkit_iommu_get_nodeid(st);
    int32_t my_nodeid = get_own_nodeid();

    err = alloc_common(MODE_MAP_COMMON, bits, device_nodeid, device, 
            my_nodeid, driver, &id.base);

    if(err_is_fail(err)){
        DEBUG_ERR(err,"alloc_common"); 
        return err;
    }

    return SYS_ERR_OK;
}

static inline errval_t iommu_free_vregion(struct iommu_client *st,
                                          lvaddr_t driver,
                                          dmem_daddr_t device)
{
    errval_t err;

    // Free device region
    int32_t device_nodeid = driverkit_iommu_get_nodeid(st);
    err = skb_execute_query("enum_node_id(%d, Id), mark_range_free(Id, %lu)",
           device_nodeid, device);
    if (err_is_fail(err)) {
        DEBUG_SKB_ERR(err, "free device mem");
        return err;
    }

    // Free my region
    int32_t my_nodeid = get_own_nodeid();
    err = skb_execute_query("enum_node_id(%d, Id), mark_range_free(Id, %lu)",
           my_nodeid, driver);
    if (err_is_fail(err)) {
        DEBUG_SKB_ERR(err, "free process mem");
        return err;
    }
    // TODO: Actually free the own VSPACE

    return SYS_ERR_OK;
}

static inline errval_t iommu_free_ram(struct capref ram)
{
    struct frame_identity id;
    errval_t err = invoke_frame_identify(ram, &id);
    if (err_is_fail(err)) {
        return err;
    }
    err = skb_execute_query("dram_nodeid(X), mark_range_free(X, %lu)", id.base);
    if (err_is_fail(err)) {
        DEBUG_SKB_ERR(err, "free ram");
        return err;
    }

    err = cap_revoke(ram);
    if(err_is_fail(err)) return err;

    err = cap_destroy(ram);
    if(err_is_fail(err)) return err;

    return SYS_ERR_OK;
}

#define ROOT_SLOT_MEM_SIZE (512UL << 30)

static inline errval_t iommu_get_mapping_region(struct iommu_client *cl,
                                                dmem_daddr_t *start,
                                                dmem_daddr_t *end,
                                                uint16_t *rootvnodeslot)
{
    assert(rootvnodeslot != NULL);
    int32_t device_nodeid = driverkit_iommu_get_nodeid(cl);
    errval_t err = skb_execute_query(
        "enum_node_id(%d,Id),alloc_root_vnodeslot(Id, Slot),write(Slot)",
        device_nodeid);

    if(err_is_fail(err)){
        DEBUG_SKB_ERR(err, "get root_vnodeslot");<commit_msg>iommu_client: removed some inline from function headers<commit_after>/**
 * \file
 * \brief DriverKit IOMMU Client
 *
 * Contains functions to request mappings from the IOMMU service
 */
/*
 * Copyright (c) 2018, ETH Zurich.
 * All rights reserved.
 *
 * This file is distributed under the terms in the attached LICENSE file.
 * If you do not find this file, copies can be found by writing to:
 * ETH Zurich D-INFK, Universitaetstr. 6, CH-8092 Zurich.
 * Attn: Systems Group.
 */
#include <stdlib.h>

#include <barrelfish/barrelfish.h>
#include <target/x86_64/barrelfish_kpi/paging_target.h>
#include <driverkit/driverkit.h>
#include <driverkit/iommu.h>
#include <skb/skb.h>

#include <if/iommu_defs.h>
#include <if/mem_defs.h>
#include <if/iommu_rpcclient_defs.h>

#include "debug.h"

struct iommu_vnode_l2
{
    enum objtype           vnode_type;
    struct capref          vnode;
    dmem_daddr_t           address_start;
    dmem_daddr_t           address_end;
    size_t                 num_frames;
    struct capref          frames[];
};


struct iommu_vnode_l3
{
    enum objtype           vnode_type;
    struct capref          vnode;
    dmem_daddr_t           address_start;
    dmem_daddr_t           address_end;
    size_t                 num_children;
    struct iommu_vnode_l2 *children[];
};


struct iommu_client
{
    ///< whether the IOMMU is enabled or not
    bool enabled;

    ///< the binding to the IOMMU service
    struct iommu_binding *binding;

    ///< endpoint to the iommu service
    struct capref endpoint;

    ///< the waitset to be used
    struct waitset *waitset;

    ///< the maximum supported page size
    size_t max_page_size;

    ///< error value for async errors
    errval_t error;

    ///< the policy to be employed by the mapping function
    iommu_vspace_policy_t policy;

    ///< ObjType of the root vnode
    enum objtype root_vnode_type;

    ///< the capability to the root vnode
    struct capref rootvnode;

    ///< slot used in the root vnode
    uint16_t rootvnode_slot;

    ///< pointer to the vnode information
    struct iommu_vnode_l3 *vnode_l3;

    ///< model node of the protected device
    int32_t nodeid;
};

///< the default iommu client
static struct iommu_client *default_client;

///< the default policy to be used
static iommu_vspace_policy_t iomm_vspace_default_policy = IOMMU_VSPACE_POLICY_MIRROR;


/*
 * TODO: proper implementation of this
 */

static inline bool iommu_vnode_type_supported(struct iommu_client *st,
                                              enum objtype type)
{
    return type_is_vnode(type);
}

/*
 * allocates a piece of RAM that is suitable for holdings page tables.
 * ideally this should be in the region of "protected" or reserved memory.
 * (see IOMMU. this region can be above 4G, there are two regions)\
 *
 * The ram should be accessible by the IOMMU and/or the device driver
 */
static inline errval_t iommu_alloc_ram_for_vnode(struct iommu_client *st,
                                                 enum objtype type,
                                                 struct capref *retcap)
{
    return ram_alloc(retcap, vnode_objbits(type));
}

/*
 *  Returns this process nodeid. It lazily adds the process' model node
 *  and returns it's identifier.
 */
static int32_t get_own_nodeid(void) {
    static int32_t nodeid = -1;
    errval_t err;
    if(nodeid == -1){
        err = skb_execute_query("add_process_alloc(X), write(X)");
        assert(err_is_ok(err));
        err = skb_read_output("%d", &nodeid);
        assert(err_is_ok(err));
        DRIVERKIT_DEBUG("Instantiated new process model node, nodeid=%"PRIi32"\n",
                nodeid);
    }
    return nodeid;
}

#define MODE_ALLOC_COMMON 0 
#define MODE_MAP_COMMON 1 

static errval_t alloc_common(int mode, int bits,
    int32_t nodeid1, uint64_t *node1addr,
    int32_t nodeid2, uint64_t *node2addr,
    uint64_t *physaddr) {

    debug_printf("alloc_common for mode=%d, bits=%d, nodeid1=%d, nodeid2=%d",
            mode, bits, nodeid1, nodeid2);

    errval_t err;
    if(mode == MODE_ALLOC_COMMON){
        err = skb_execute_query("alloc_common(%d,%"PRIi32",%"PRIi32").",
                bits, nodeid1, nodeid2);
    } else {
        assert(mode == MODE_MAP_COMMON);
        // We need physaddr to be passed
        assert(physaddr != NULL);
        assert(*physaddr != 0);
        err = skb_execute_query("map_common(%d,%"PRIu64",%"PRIi32",%"PRIi32").",
                bits, *physaddr, nodeid1, nodeid2);
    }

    DEBUG_SKB_ERR(err,"in alloc_common");
    if(err_is_fail(err)){
        skb_execute_query("dec_net_debug");
        return err;
    }
    char * skb_list_output =  strdup(skb_get_output());
    assert(skb_list_output);
    struct list_parser_status status;
    skb_read_list_init_offset(&status, skb_list_output, 0);

    uint64_t address = 0, nodeid=0;
    int list_idx = 0;
    while(skb_read_list(&status, "name(%"SCNu64", %"SCNu64")", &address, &nodeid)) {
        uint64_t *dest = NULL;
        switch(list_idx){
            case 0: dest = node1addr; break;
            case 1: dest = node2addr; break;
            case 2: dest = physaddr; break;
        }
        if(dest) *dest = address;
        list_idx++;
    }
    free(skb_list_output);
    if(list_idx != 3){
        return SKB_ERR_CONVERSION_ERROR;
    }

    return SYS_ERR_OK;
}

/*
 * allocates a piece of ram to be mapped into the driver and the devices
 * address spaces
 */
static errval_t iommu_alloc_ram(struct iommu_client *st,
                                 size_t bytes,
                                 struct capref *retcap)
{
    // TODO: This function has to pass the rootvnodeslot from iommu_get_mapping
    // region to the allocator function to get mem from that region.
    errval_t err, msgerr;

    if (bytes < (LARGE_PAGE_SIZE)) {
        bytes = LARGE_PAGE_SIZE;
    }
    int bits = log2ceil(bytes);
    bytes = 1 << bits;

    debug_printf("iommu_alloc_ram bytes=%lu\n", bytes);

    // Alloc cap slot
    err = slot_alloc(retcap);
    if (err_is_fail(err)) {
        return err_push(err, LIB_ERR_SLOT_ALLOC);
    }

    struct mem_binding * b = get_mem_client();

    int32_t device_nodeid = driverkit_iommu_get_nodeid(st);
    int32_t my_nodeid = get_own_nodeid();
    uint64_t base_addr=0;

    err = alloc_common(MODE_ALLOC_COMMON, bits, device_nodeid, NULL, my_nodeid,
            NULL, &base_addr);
    if(err_is_fail(err)){
        DEBUG_ERR(err, "allocate_common");
        return err;
    }

    debug_printf("Determined addr=0x%"PRIx64" as base address for bits=%d request\n",
            base_addr, bits);

    err = b->rpc_tx_vtbl.allocate(b, bits, base_addr, base_addr + bytes,
            &msgerr, retcap);
    if(err_is_fail(err)){
        DEBUG_ERR(err, "allocate RPC");
        return err;
    }
    if(err_is_fail(msgerr)){
        DEBUG_ERR(msgerr, "allocate");
        return msgerr;
    }
    //debug_printf("alloc_ram_for_frame success\n");
    return SYS_ERR_OK;
}

static errval_t iommu_alloc_frame(struct iommu_client *cl,
                                  size_t bytes,
                                  struct capref *retcap)
{
    if (bytes < (LARGE_PAGE_SIZE)) {
        bytes = LARGE_PAGE_SIZE;
    }

    // Allocate RAM cap
    struct capref ramcap;
    errval_t err = iommu_alloc_ram(cl, bytes, &ramcap);
    if(err_is_fail(err)){
        return err;            
    }

    // Alloc cap slot
    err = slot_alloc(retcap);
    if (err_is_fail(err)) {
        return err_push(err, LIB_ERR_SLOT_ALLOC);
    }

    // Get bits
    assert(bytes > 0);
    uint8_t bits = log2ceil(bytes);
    assert((1UL << bits) >= bytes);

    // This is doing what "create_ram_descendant" in
    // lib/barrelfish/capabilities.c is doing.
    err = cap_retype(*retcap, ramcap, 0, ObjType_Frame, (1UL << bits), 1);
    if (err_is_fail(err)) {
        return err_push(err, LIB_ERR_CAP_RETYPE);
    }

    err = cap_destroy(ramcap);
    if (err_is_fail(err)) {
        return err_push(err, LIB_ERR_CAP_DESTROY);
    }

    return SYS_ERR_OK;
}

#define MAPPING_REGION_START (512UL << 31)
#define MAPPING_REGION_SIZE (512UL << 30)

#ifdef DISABLE_MODEL
static lvaddr_t vregion_map_base = MAPPING_REGION_START;
#endif
/*
 * returns a region of memory
 */
static errval_t iommu_alloc_vregion(struct iommu_client *st,
                                    struct capref mem,
                                    lvaddr_t *driver,
                                    dmem_daddr_t *device)
{
    errval_t err;
    struct frame_identity id;
    err = invoke_frame_identify(mem, &id);
    if (err_is_fail(err)) {
        return err;
    }

    if(st == NULL){
        *device = id.base;   
        *driver = 0;
        return SYS_ERR_OK;
    }

    assert(id.bytes >= LARGE_PAGE_SIZE);
    assert(st != NULL);

    int bits = log2ceil(id.bytes);
    int32_t device_nodeid = driverkit_iommu_get_nodeid(st);
    int32_t my_nodeid = get_own_nodeid();

    err = alloc_common(MODE_MAP_COMMON, bits, device_nodeid, device, 
            my_nodeid, driver, &id.base);

    if(err_is_fail(err)){
        DEBUG_ERR(err,"alloc_common"); 
        return err;
    }

    return SYS_ERR_OK;
}

static errval_t iommu_free_vregion(struct iommu_client *st,
                                   lvaddr_t driver,
                                   dmem_daddr_t device)
{
    errval_t err;

    // Free device region
    int32_t device_nodeid = driverkit_iommu_get_nodeid(st);
    err = skb_execute_query("enum_node_id(%d, Id), mark_range_free(Id, %lu)",
           device_nodeid, device);
    if (err_is_fail(err)) {
        DEBUG_SKB_ERR(err, "free device mem");
        return err;
    }

    // Free my region
    int32_t my_nodeid = get_own_nodeid();
    err = skb_execute_query("enum_node_id(%d, Id), mark_range_free(Id, %lu)",
           my_nodeid, driver);
    if (err_is_fail(err)) {
        DEBUG_SKB_ERR(err, "free process mem");
        return err;
    }
    // TODO: Actually free the own VSPACE

    return SYS_ERR_OK;
}

static errval_t iommu_free_ram(struct capref ram)
{
    struct frame_identity id;
    errval_t err = invoke_frame_identify(ram, &id);
    if (err_is_fail(err)) {
        return err;
    }
    err = skb_execute_query("dram_nodeid(X), mark_range_free(X, %lu)", id.base);
    if (err_is_fail(err)) {
        DEBUG_SKB_ERR(err, "free ram");
        return err;
    }

    err = cap_revoke(ram);
    if(err_is_fail(err)) return err;

    err = cap_destroy(ram);
    if(err_is_fail(err)) return err;

    return SYS_ERR_OK;
}

#define ROOT_SLOT_MEM_SIZE (512UL << 30)

static errval_t iommu_get_mapping_region(struct iommu_client *cl,
                                                dmem_daddr_t *start,
                                                dmem_daddr_t *end,
                                                uint16_t *rootvnodeslot)
{
    assert(rootvnodeslot != NULL);
    int32_t device_nodeid = driverkit_iommu_get_nodeid(cl);
    errval_t err = skb_execute_query(
        "enum_node_id(%d,Id),alloc_root_vnodeslot(Id, Slot),write(Slot)",
        device_nodeid);

    if(err_is_fail(err)){
        DEBUG_SKB_ERR(err, "get root_vnodeslot");
{'fraction_non_alphanumeric': 0.09953434225844005, 'fraction_numerical': 0.011820542670368049, 'mean_word_length': 2.3718297101449277, 'pattern_counts': {'":': 0, '<': 77, '<?xml version=': 0, '>': 29, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 51, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7292497', 'n_tokens_mistral': 7804, 'n_tokens_neox': 7542, 'n_words': 1794}