// SPDX-License-Identifier: GPL-2.0+ 
 | 
// Copyright 2019 IBM Corp. 
 | 
#include <linux/idr.h> 
 | 
#include "ocxl_internal.h" 
 | 
  
 | 
static struct ocxl_fn *ocxl_fn_get(struct ocxl_fn *fn) 
 | 
{ 
 | 
    return (get_device(&fn->dev) == NULL) ? NULL : fn; 
 | 
} 
 | 
  
 | 
static void ocxl_fn_put(struct ocxl_fn *fn) 
 | 
{ 
 | 
    put_device(&fn->dev); 
 | 
} 
 | 
  
 | 
static struct ocxl_afu *alloc_afu(struct ocxl_fn *fn) 
 | 
{ 
 | 
    struct ocxl_afu *afu; 
 | 
  
 | 
    afu = kzalloc(sizeof(struct ocxl_afu), GFP_KERNEL); 
 | 
    if (!afu) 
 | 
        return NULL; 
 | 
  
 | 
    kref_init(&afu->kref); 
 | 
    mutex_init(&afu->contexts_lock); 
 | 
    mutex_init(&afu->afu_control_lock); 
 | 
    idr_init(&afu->contexts_idr); 
 | 
    afu->fn = fn; 
 | 
    ocxl_fn_get(fn); 
 | 
    return afu; 
 | 
} 
 | 
  
 | 
static void free_afu(struct kref *kref) 
 | 
{ 
 | 
    struct ocxl_afu *afu = container_of(kref, struct ocxl_afu, kref); 
 | 
  
 | 
    idr_destroy(&afu->contexts_idr); 
 | 
    ocxl_fn_put(afu->fn); 
 | 
    kfree(afu); 
 | 
} 
 | 
  
 | 
void ocxl_afu_get(struct ocxl_afu *afu) 
 | 
{ 
 | 
    kref_get(&afu->kref); 
 | 
} 
 | 
EXPORT_SYMBOL_GPL(ocxl_afu_get); 
 | 
  
 | 
void ocxl_afu_put(struct ocxl_afu *afu) 
 | 
{ 
 | 
    kref_put(&afu->kref, free_afu); 
 | 
} 
 | 
EXPORT_SYMBOL_GPL(ocxl_afu_put); 
 | 
  
 | 
static int assign_afu_actag(struct ocxl_afu *afu) 
 | 
{ 
 | 
    struct ocxl_fn *fn = afu->fn; 
 | 
    int actag_count, actag_offset; 
 | 
    struct pci_dev *pci_dev = to_pci_dev(fn->dev.parent); 
 | 
  
 | 
    /* 
 | 
     * if there were not enough actags for the function, each afu 
 | 
     * reduces its count as well 
 | 
     */ 
 | 
    actag_count = afu->config.actag_supported * 
 | 
        fn->actag_enabled / fn->actag_supported; 
 | 
    actag_offset = ocxl_actag_afu_alloc(fn, actag_count); 
 | 
    if (actag_offset < 0) { 
 | 
        dev_err(&pci_dev->dev, "Can't allocate %d actags for AFU: %d\n", 
 | 
            actag_count, actag_offset); 
 | 
        return actag_offset; 
 | 
    } 
 | 
    afu->actag_base = fn->actag_base + actag_offset; 
 | 
    afu->actag_enabled = actag_count; 
 | 
  
 | 
    ocxl_config_set_afu_actag(pci_dev, afu->config.dvsec_afu_control_pos, 
 | 
                afu->actag_base, afu->actag_enabled); 
 | 
    dev_dbg(&pci_dev->dev, "actag base=%d enabled=%d\n", 
 | 
        afu->actag_base, afu->actag_enabled); 
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static void reclaim_afu_actag(struct ocxl_afu *afu) 
 | 
{ 
 | 
    struct ocxl_fn *fn = afu->fn; 
 | 
    int start_offset, size; 
 | 
  
 | 
    start_offset = afu->actag_base - fn->actag_base; 
 | 
    size = afu->actag_enabled; 
 | 
    ocxl_actag_afu_free(afu->fn, start_offset, size); 
 | 
} 
 | 
  
 | 
static int assign_afu_pasid(struct ocxl_afu *afu) 
 | 
{ 
 | 
    struct ocxl_fn *fn = afu->fn; 
 | 
    int pasid_count, pasid_offset; 
 | 
    struct pci_dev *pci_dev = to_pci_dev(fn->dev.parent); 
 | 
  
 | 
    /* 
 | 
     * We only support the case where the function configuration 
 | 
     * requested enough PASIDs to cover all AFUs. 
 | 
     */ 
 | 
    pasid_count = 1 << afu->config.pasid_supported_log; 
 | 
    pasid_offset = ocxl_pasid_afu_alloc(fn, pasid_count); 
 | 
    if (pasid_offset < 0) { 
 | 
        dev_err(&pci_dev->dev, "Can't allocate %d PASIDs for AFU: %d\n", 
 | 
            pasid_count, pasid_offset); 
 | 
        return pasid_offset; 
 | 
    } 
 | 
    afu->pasid_base = fn->pasid_base + pasid_offset; 
 | 
    afu->pasid_count = 0; 
 | 
    afu->pasid_max = pasid_count; 
 | 
  
 | 
    ocxl_config_set_afu_pasid(pci_dev, afu->config.dvsec_afu_control_pos, 
 | 
                afu->pasid_base, 
 | 
                afu->config.pasid_supported_log); 
 | 
    dev_dbg(&pci_dev->dev, "PASID base=%d, enabled=%d\n", 
 | 
        afu->pasid_base, pasid_count); 
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static void reclaim_afu_pasid(struct ocxl_afu *afu) 
 | 
{ 
 | 
    struct ocxl_fn *fn = afu->fn; 
 | 
    int start_offset, size; 
 | 
  
 | 
    start_offset = afu->pasid_base - fn->pasid_base; 
 | 
    size = 1 << afu->config.pasid_supported_log; 
 | 
    ocxl_pasid_afu_free(afu->fn, start_offset, size); 
 | 
} 
 | 
  
 | 
static int reserve_fn_bar(struct ocxl_fn *fn, int bar) 
 | 
{ 
 | 
    struct pci_dev *dev = to_pci_dev(fn->dev.parent); 
 | 
    int rc, idx; 
 | 
  
 | 
    if (bar != 0 && bar != 2 && bar != 4) 
 | 
        return -EINVAL; 
 | 
  
 | 
    idx = bar >> 1; 
 | 
    if (fn->bar_used[idx]++ == 0) { 
 | 
        rc = pci_request_region(dev, bar, "ocxl"); 
 | 
        if (rc) 
 | 
            return rc; 
 | 
    } 
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static void release_fn_bar(struct ocxl_fn *fn, int bar) 
 | 
{ 
 | 
    struct pci_dev *dev = to_pci_dev(fn->dev.parent); 
 | 
    int idx; 
 | 
  
 | 
    if (bar != 0 && bar != 2 && bar != 4) 
 | 
        return; 
 | 
  
 | 
    idx = bar >> 1; 
 | 
    if (--fn->bar_used[idx] == 0) 
 | 
        pci_release_region(dev, bar); 
 | 
    WARN_ON(fn->bar_used[idx] < 0); 
 | 
} 
 | 
  
 | 
static int map_mmio_areas(struct ocxl_afu *afu) 
 | 
{ 
 | 
    int rc; 
 | 
    struct pci_dev *pci_dev = to_pci_dev(afu->fn->dev.parent); 
 | 
  
 | 
    rc = reserve_fn_bar(afu->fn, afu->config.global_mmio_bar); 
 | 
    if (rc) 
 | 
        return rc; 
 | 
  
 | 
    rc = reserve_fn_bar(afu->fn, afu->config.pp_mmio_bar); 
 | 
    if (rc) { 
 | 
        release_fn_bar(afu->fn, afu->config.global_mmio_bar); 
 | 
        return rc; 
 | 
    } 
 | 
  
 | 
    afu->global_mmio_start = 
 | 
        pci_resource_start(pci_dev, afu->config.global_mmio_bar) + 
 | 
        afu->config.global_mmio_offset; 
 | 
    afu->pp_mmio_start = 
 | 
        pci_resource_start(pci_dev, afu->config.pp_mmio_bar) + 
 | 
        afu->config.pp_mmio_offset; 
 | 
  
 | 
    afu->global_mmio_ptr = ioremap(afu->global_mmio_start, 
 | 
                afu->config.global_mmio_size); 
 | 
    if (!afu->global_mmio_ptr) { 
 | 
        release_fn_bar(afu->fn, afu->config.pp_mmio_bar); 
 | 
        release_fn_bar(afu->fn, afu->config.global_mmio_bar); 
 | 
        dev_err(&pci_dev->dev, "Error mapping global mmio area\n"); 
 | 
        return -ENOMEM; 
 | 
    } 
 | 
  
 | 
    /* 
 | 
     * Leave an empty page between the per-process mmio area and 
 | 
     * the AFU interrupt mappings 
 | 
     */ 
 | 
    afu->irq_base_offset = afu->config.pp_mmio_stride + PAGE_SIZE; 
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static void unmap_mmio_areas(struct ocxl_afu *afu) 
 | 
{ 
 | 
    if (afu->global_mmio_ptr) { 
 | 
        iounmap(afu->global_mmio_ptr); 
 | 
        afu->global_mmio_ptr = NULL; 
 | 
    } 
 | 
    afu->global_mmio_start = 0; 
 | 
    afu->pp_mmio_start = 0; 
 | 
    release_fn_bar(afu->fn, afu->config.pp_mmio_bar); 
 | 
    release_fn_bar(afu->fn, afu->config.global_mmio_bar); 
 | 
} 
 | 
  
 | 
static int configure_afu(struct ocxl_afu *afu, u8 afu_idx, struct pci_dev *dev) 
 | 
{ 
 | 
    int rc; 
 | 
  
 | 
    rc = ocxl_config_read_afu(dev, &afu->fn->config, &afu->config, afu_idx); 
 | 
    if (rc) 
 | 
        return rc; 
 | 
  
 | 
    rc = assign_afu_actag(afu); 
 | 
    if (rc) 
 | 
        return rc; 
 | 
  
 | 
    rc = assign_afu_pasid(afu); 
 | 
    if (rc) 
 | 
        goto err_free_actag; 
 | 
  
 | 
    rc = map_mmio_areas(afu); 
 | 
    if (rc) 
 | 
        goto err_free_pasid; 
 | 
  
 | 
    return 0; 
 | 
  
 | 
err_free_pasid: 
 | 
    reclaim_afu_pasid(afu); 
 | 
err_free_actag: 
 | 
    reclaim_afu_actag(afu); 
 | 
    return rc; 
 | 
} 
 | 
  
 | 
static void deconfigure_afu(struct ocxl_afu *afu) 
 | 
{ 
 | 
    unmap_mmio_areas(afu); 
 | 
    reclaim_afu_pasid(afu); 
 | 
    reclaim_afu_actag(afu); 
 | 
} 
 | 
  
 | 
static int activate_afu(struct pci_dev *dev, struct ocxl_afu *afu) 
 | 
{ 
 | 
    ocxl_config_set_afu_state(dev, afu->config.dvsec_afu_control_pos, 1); 
 | 
  
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static void deactivate_afu(struct ocxl_afu *afu) 
 | 
{ 
 | 
    struct pci_dev *dev = to_pci_dev(afu->fn->dev.parent); 
 | 
  
 | 
    ocxl_config_set_afu_state(dev, afu->config.dvsec_afu_control_pos, 0); 
 | 
} 
 | 
  
 | 
static int init_afu(struct pci_dev *dev, struct ocxl_fn *fn, u8 afu_idx) 
 | 
{ 
 | 
    int rc; 
 | 
    struct ocxl_afu *afu; 
 | 
  
 | 
    afu = alloc_afu(fn); 
 | 
    if (!afu) 
 | 
        return -ENOMEM; 
 | 
  
 | 
    rc = configure_afu(afu, afu_idx, dev); 
 | 
    if (rc) { 
 | 
        ocxl_afu_put(afu); 
 | 
        return rc; 
 | 
    } 
 | 
  
 | 
    rc = activate_afu(dev, afu); 
 | 
    if (rc) { 
 | 
        deconfigure_afu(afu); 
 | 
        ocxl_afu_put(afu); 
 | 
        return rc; 
 | 
    } 
 | 
  
 | 
    list_add_tail(&afu->list, &fn->afu_list); 
 | 
  
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static void remove_afu(struct ocxl_afu *afu) 
 | 
{ 
 | 
    list_del(&afu->list); 
 | 
    ocxl_context_detach_all(afu); 
 | 
    deactivate_afu(afu); 
 | 
    deconfigure_afu(afu); 
 | 
    ocxl_afu_put(afu); // matches the implicit get in alloc_afu 
 | 
} 
 | 
  
 | 
static struct ocxl_fn *alloc_function(void) 
 | 
{ 
 | 
    struct ocxl_fn *fn; 
 | 
  
 | 
    fn = kzalloc(sizeof(struct ocxl_fn), GFP_KERNEL); 
 | 
    if (!fn) 
 | 
        return NULL; 
 | 
  
 | 
    INIT_LIST_HEAD(&fn->afu_list); 
 | 
    INIT_LIST_HEAD(&fn->pasid_list); 
 | 
    INIT_LIST_HEAD(&fn->actag_list); 
 | 
  
 | 
    return fn; 
 | 
} 
 | 
  
 | 
static void free_function(struct ocxl_fn *fn) 
 | 
{ 
 | 
    WARN_ON(!list_empty(&fn->afu_list)); 
 | 
    WARN_ON(!list_empty(&fn->pasid_list)); 
 | 
    kfree(fn); 
 | 
} 
 | 
  
 | 
static void free_function_dev(struct device *dev) 
 | 
{ 
 | 
    struct ocxl_fn *fn = container_of(dev, struct ocxl_fn, dev); 
 | 
  
 | 
    free_function(fn); 
 | 
} 
 | 
  
 | 
static int set_function_device(struct ocxl_fn *fn, struct pci_dev *dev) 
 | 
{ 
 | 
    fn->dev.parent = &dev->dev; 
 | 
    fn->dev.release = free_function_dev; 
 | 
    return dev_set_name(&fn->dev, "ocxlfn.%s", dev_name(&dev->dev)); 
 | 
} 
 | 
  
 | 
static int assign_function_actag(struct ocxl_fn *fn) 
 | 
{ 
 | 
    struct pci_dev *dev = to_pci_dev(fn->dev.parent); 
 | 
    u16 base, enabled, supported; 
 | 
    int rc; 
 | 
  
 | 
    rc = ocxl_config_get_actag_info(dev, &base, &enabled, &supported); 
 | 
    if (rc) 
 | 
        return rc; 
 | 
  
 | 
    fn->actag_base = base; 
 | 
    fn->actag_enabled = enabled; 
 | 
    fn->actag_supported = supported; 
 | 
  
 | 
    ocxl_config_set_actag(dev, fn->config.dvsec_function_pos, 
 | 
            fn->actag_base,    fn->actag_enabled); 
 | 
    dev_dbg(&fn->dev, "actag range starting at %d, enabled %d\n", 
 | 
        fn->actag_base, fn->actag_enabled); 
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static int set_function_pasid(struct ocxl_fn *fn) 
 | 
{ 
 | 
    struct pci_dev *dev = to_pci_dev(fn->dev.parent); 
 | 
    int rc, desired_count, max_count; 
 | 
  
 | 
    /* A function may not require any PASID */ 
 | 
    if (fn->config.max_pasid_log < 0) 
 | 
        return 0; 
 | 
  
 | 
    rc = ocxl_config_get_pasid_info(dev, &max_count); 
 | 
    if (rc) 
 | 
        return rc; 
 | 
  
 | 
    desired_count = 1 << fn->config.max_pasid_log; 
 | 
  
 | 
    if (desired_count > max_count) { 
 | 
        dev_err(&fn->dev, 
 | 
            "Function requires more PASIDs than is available (%d vs. %d)\n", 
 | 
            desired_count, max_count); 
 | 
        return -ENOSPC; 
 | 
    } 
 | 
  
 | 
    fn->pasid_base = 0; 
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static int configure_function(struct ocxl_fn *fn, struct pci_dev *dev) 
 | 
{ 
 | 
    int rc; 
 | 
  
 | 
    rc = pci_enable_device(dev); 
 | 
    if (rc) { 
 | 
        dev_err(&dev->dev, "pci_enable_device failed: %d\n", rc); 
 | 
        return rc; 
 | 
    } 
 | 
  
 | 
    /* 
 | 
     * Once it has been confirmed to work on our hardware, we 
 | 
     * should reset the function, to force the adapter to restart 
 | 
     * from scratch. 
 | 
     * A function reset would also reset all its AFUs. 
 | 
     * 
 | 
     * Some hints for implementation: 
 | 
     * 
 | 
     * - there's not status bit to know when the reset is done. We 
 | 
     *   should try reading the config space to know when it's 
 | 
     *   done. 
 | 
     * - probably something like: 
 | 
     *    Reset 
 | 
     *    wait 100ms 
 | 
     *    issue config read 
 | 
     *    allow device up to 1 sec to return success on config 
 | 
     *    read before declaring it broken 
 | 
     * 
 | 
     * Some shared logic on the card (CFG, TLX) won't be reset, so 
 | 
     * there's no guarantee that it will be enough. 
 | 
     */ 
 | 
    rc = ocxl_config_read_function(dev, &fn->config); 
 | 
    if (rc) 
 | 
        return rc; 
 | 
  
 | 
    rc = set_function_device(fn, dev); 
 | 
    if (rc) 
 | 
        return rc; 
 | 
  
 | 
    rc = assign_function_actag(fn); 
 | 
    if (rc) 
 | 
        return rc; 
 | 
  
 | 
    rc = set_function_pasid(fn); 
 | 
    if (rc) 
 | 
        return rc; 
 | 
  
 | 
    rc = ocxl_link_setup(dev, 0, &fn->link); 
 | 
    if (rc) 
 | 
        return rc; 
 | 
  
 | 
    rc = ocxl_config_set_TL(dev, fn->config.dvsec_tl_pos); 
 | 
    if (rc) { 
 | 
        ocxl_link_release(dev, fn->link); 
 | 
        return rc; 
 | 
    } 
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static void deconfigure_function(struct ocxl_fn *fn) 
 | 
{ 
 | 
    struct pci_dev *dev = to_pci_dev(fn->dev.parent); 
 | 
  
 | 
    ocxl_link_release(dev, fn->link); 
 | 
    pci_disable_device(dev); 
 | 
} 
 | 
  
 | 
static struct ocxl_fn *init_function(struct pci_dev *dev) 
 | 
{ 
 | 
    struct ocxl_fn *fn; 
 | 
    int rc; 
 | 
  
 | 
    fn = alloc_function(); 
 | 
    if (!fn) 
 | 
        return ERR_PTR(-ENOMEM); 
 | 
  
 | 
    rc = configure_function(fn, dev); 
 | 
    if (rc) { 
 | 
        free_function(fn); 
 | 
        return ERR_PTR(rc); 
 | 
    } 
 | 
  
 | 
    rc = device_register(&fn->dev); 
 | 
    if (rc) { 
 | 
        deconfigure_function(fn); 
 | 
        put_device(&fn->dev); 
 | 
        return ERR_PTR(rc); 
 | 
    } 
 | 
    return fn; 
 | 
} 
 | 
  
 | 
// Device detection & initialisation 
 | 
  
 | 
struct ocxl_fn *ocxl_function_open(struct pci_dev *dev) 
 | 
{ 
 | 
    int rc, afu_count = 0; 
 | 
    u8 afu; 
 | 
    struct ocxl_fn *fn; 
 | 
  
 | 
    if (!radix_enabled()) { 
 | 
        dev_err(&dev->dev, "Unsupported memory model (hash)\n"); 
 | 
        return ERR_PTR(-ENODEV); 
 | 
    } 
 | 
  
 | 
    fn = init_function(dev); 
 | 
    if (IS_ERR(fn)) { 
 | 
        dev_err(&dev->dev, "function init failed: %li\n", 
 | 
            PTR_ERR(fn)); 
 | 
        return fn; 
 | 
    } 
 | 
  
 | 
    for (afu = 0; afu <= fn->config.max_afu_index; afu++) { 
 | 
        rc = ocxl_config_check_afu_index(dev, &fn->config, afu); 
 | 
        if (rc > 0) { 
 | 
            rc = init_afu(dev, fn, afu); 
 | 
            if (rc) { 
 | 
                dev_err(&dev->dev, 
 | 
                    "Can't initialize AFU index %d\n", afu); 
 | 
                continue; 
 | 
            } 
 | 
            afu_count++; 
 | 
        } 
 | 
    } 
 | 
    dev_info(&dev->dev, "%d AFU(s) configured\n", afu_count); 
 | 
    return fn; 
 | 
} 
 | 
EXPORT_SYMBOL_GPL(ocxl_function_open); 
 | 
  
 | 
struct list_head *ocxl_function_afu_list(struct ocxl_fn *fn) 
 | 
{ 
 | 
    return &fn->afu_list; 
 | 
} 
 | 
EXPORT_SYMBOL_GPL(ocxl_function_afu_list); 
 | 
  
 | 
struct ocxl_afu *ocxl_function_fetch_afu(struct ocxl_fn *fn, u8 afu_idx) 
 | 
{ 
 | 
    struct ocxl_afu *afu; 
 | 
  
 | 
    list_for_each_entry(afu, &fn->afu_list, list) { 
 | 
        if (afu->config.idx == afu_idx) 
 | 
            return afu; 
 | 
    } 
 | 
  
 | 
    return NULL; 
 | 
} 
 | 
EXPORT_SYMBOL_GPL(ocxl_function_fetch_afu); 
 | 
  
 | 
const struct ocxl_fn_config *ocxl_function_config(struct ocxl_fn *fn) 
 | 
{ 
 | 
    return &fn->config; 
 | 
} 
 | 
EXPORT_SYMBOL_GPL(ocxl_function_config); 
 | 
  
 | 
void ocxl_function_close(struct ocxl_fn *fn) 
 | 
{ 
 | 
    struct ocxl_afu *afu, *tmp; 
 | 
  
 | 
    list_for_each_entry_safe(afu, tmp, &fn->afu_list, list) { 
 | 
        remove_afu(afu); 
 | 
    } 
 | 
  
 | 
    deconfigure_function(fn); 
 | 
    device_unregister(&fn->dev); 
 | 
} 
 | 
EXPORT_SYMBOL_GPL(ocxl_function_close); 
 | 
  
 | 
// AFU Metadata 
 | 
  
 | 
struct ocxl_afu_config *ocxl_afu_config(struct ocxl_afu *afu) 
 | 
{ 
 | 
    return &afu->config; 
 | 
} 
 | 
EXPORT_SYMBOL_GPL(ocxl_afu_config); 
 | 
  
 | 
void ocxl_afu_set_private(struct ocxl_afu *afu, void *private) 
 | 
{ 
 | 
    afu->private = private; 
 | 
} 
 | 
EXPORT_SYMBOL_GPL(ocxl_afu_set_private); 
 | 
  
 | 
void *ocxl_afu_get_private(struct ocxl_afu *afu) 
 | 
{ 
 | 
    if (afu) 
 | 
        return afu->private; 
 | 
  
 | 
    return NULL; 
 | 
} 
 | 
EXPORT_SYMBOL_GPL(ocxl_afu_get_private); 
 |