root/sys/dev/qat/qat_common/adf_freebsd_uio.c
/* SPDX-License-Identifier: BSD-3-Clause */
/* Copyright(c) 2007-2022 Intel Corporation */
#include "qat_freebsd.h"
#include "adf_cfg.h"
#include "adf_common_drv.h"
#include "adf_accel_devices.h"
#include "icp_qat_uclo.h"
#include "icp_qat_fw.h"
#include "icp_qat_fw_init_admin.h"
#include "adf_cfg_strings.h"
#include "adf_uio_control.h"
#include "adf_uio_cleanup.h"
#include "adf_uio.h"
#include "adf_transport_access_macros.h"
#include "adf_transport_internal.h"
#include <sys/conf.h>
#include <sys/capsicum.h>
#include <sys/kdb.h>
#include <sys/condvar.h>
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/proc.h>
#include <sys/file.h>
#include <sys/lock.h>
#include <sys/rwlock.h>
#include <sys/sglist.h>
#include <vm/vm.h>
#include <vm/vm_object.h>
#include <vm/vm_page.h>
#include <vm/vm_pager.h>

#define ADF_UIO_GET_NAME(accel_dev) (GET_HW_DATA(accel_dev)->dev_class->name)
#define ADF_UIO_GET_TYPE(accel_dev) (GET_HW_DATA(accel_dev)->dev_class->type)
#define ADF_UIO_GET_BAR(accel_dev)                                             \
        (GET_HW_DATA(accel_dev)->get_etr_bar_id(GET_HW_DATA(accel_dev)))

static d_ioctl_t adf_uio_ioctl;
static d_mmap_single_t adf_uio_mmap_single;

static struct cdevsw adf_uio_cdevsw = { .d_ioctl = adf_uio_ioctl,
                                        .d_mmap_single = adf_uio_mmap_single,
                                        .d_version = D_VERSION,
                                        .d_name = "qat" };

struct adf_uio_open_bundle {
        struct adf_uio_control_accel *accel;
        int bundle;
        struct file **mem_files;
        int num_mem_files;
};

static void
adf_release_bundle(void *arg)
{
        struct adf_uio_control_accel *accel = NULL;
        struct adf_uio_open_bundle *handle = NULL;
        struct adf_uio_control_bundle *bundle = NULL;
        struct adf_uio_instance_rings *instance_rings, *tmp;
        int i = 0;

        handle = arg;
        accel = handle->accel;
        bundle = &accel->bundle[handle->bundle];

        mutex_lock(&bundle->lock);
        adf_uio_do_cleanup_orphan(bundle->hardware_bundle_number, accel);
        mutex_unlock(&bundle->lock);

        for (i = 0; i < handle->num_mem_files; i++) {
                /*
                 * Similar to the garbage collection of orphaned file
                 * descriptor references in UNIX domain socket control
                 * messages, the current thread isn't relevant to the
                 * the file descriptor reference being released.  In
                 * particular, the current thread does not hold any
                 * advisory file locks on these file descriptors.
                 */
                fdrop(handle->mem_files[i], NULL);
        }
        free(handle->mem_files, M_QAT);

        mtx_lock(&accel->lock);

        mutex_lock(&bundle->list_lock);
        list_for_each_entry_safe(instance_rings, tmp, &bundle->list, list)
        {
                if (instance_rings->user_pid == curproc->p_pid) {
                        list_del(&instance_rings->list);
                        free(instance_rings, M_QAT);
                        break;
                }
        }
        mutex_unlock(&bundle->list_lock);

        adf_dev_put(accel->accel_dev);
        accel->num_handles--;
        free(handle, M_QAT);
        if (!accel->num_handles) {
                cv_broadcast(&accel->cleanup_ok);
                /* the broadcasting effect happens after releasing accel->lock
                 */
        }
        mtx_unlock(&accel->lock);
}

static int
adf_add_mem_fd(struct adf_accel_dev *accel_dev, int mem_fd)
{
        struct adf_uio_control_accel *accel = NULL;
        struct adf_uio_open_bundle *handle = NULL;
        struct file *fp, **new_files;
        cap_rights_t rights;
        int error = -1, old_count = 0;

        error = devfs_get_cdevpriv((void **)&handle);
        if (error)
                return (error);

        error = fget(curthread, mem_fd, cap_rights_init(&rights), &fp);
        if (error) {
                printf(
                    "Failed to fetch file pointer from current process %d \n",
                    __LINE__);
                return (error);
        }

        accel = accel_dev->accel;
        mtx_lock(&accel->lock);
        for (;;) {
                old_count = handle->num_mem_files;
                mtx_unlock(&accel->lock);
                new_files = malloc((old_count + 1) * sizeof(*new_files),
                                   M_QAT,
                                   M_WAITOK);
                mtx_lock(&accel->lock);
                if (old_count == handle->num_mem_files) {
                        if (old_count != 0) {
                                memcpy(new_files,
                                       handle->mem_files,
                                       old_count * sizeof(*new_files));
                                free(handle->mem_files, M_QAT);
                        }
                        handle->mem_files = new_files;
                        new_files[old_count] = fp;
                        handle->num_mem_files++;
                        break;
                } else
                        free(new_files, M_QAT);
        }
        mtx_unlock(&accel->lock);
        return (0);
}

static vm_object_t
adf_uio_map_bar(struct adf_accel_dev *accel_dev, uint8_t bank_offset)
{
        unsigned int ring_bundle_size, offset;
        struct sglist *sg = NULL;
        struct adf_uio_control_accel *accel = accel_dev->accel;
        struct adf_hw_csr_info *csr_info = &accel_dev->hw_device->csr_info;
        vm_object_t obj;

        ring_bundle_size = csr_info->ring_bundle_size;
        offset = bank_offset * ring_bundle_size;

        sg = sglist_alloc(1, M_WAITOK);

        /* Starting from new HW there is an additional offset
         * for bundle CSRs
         */
        sglist_append_phys(sg,
                           accel->bar->base_addr + offset +
                               csr_info->csr_addr_offset,
                           ring_bundle_size);

        obj = vm_pager_allocate(
            OBJT_SG, sg, ring_bundle_size, VM_PROT_RW, 0, NULL);
        if (obj != NULL) {
                VM_OBJECT_WLOCK(obj);
                vm_object_set_memattr(obj, VM_MEMATTR_UNCACHEABLE);
                VM_OBJECT_WUNLOCK(obj);
        }
        sglist_free(sg);

        return obj;
}

static int
adf_alloc_bundle(struct adf_accel_dev *accel_dev, int bundle_nr)
{
        struct adf_uio_control_accel *accel = NULL;
        struct adf_uio_open_bundle *handle = NULL;
        int error;

        if (bundle_nr < 0 || bundle_nr >= GET_MAX_BANKS(accel_dev)) {
                printf("ERROR in %s (%d) %d\n", __func__, bundle_nr, __LINE__);
                return EINVAL;
        }

        accel = accel_dev->accel;
        handle = malloc(sizeof(*handle), M_QAT, M_WAITOK | M_ZERO);
        handle->accel = accel;
        handle->bundle = bundle_nr;

        mtx_lock(&accel->lock);
        adf_dev_get(accel_dev);
        accel->num_handles++;
        mtx_unlock(&accel->lock);

        error = devfs_set_cdevpriv(handle, adf_release_bundle);
        if (error) {
                adf_release_bundle(handle);
                device_printf(GET_DEV(accel_dev),
                              "ERROR in adf_alloc_bundle %d\n",
                              __LINE__);
                return (error);
        }

        return (0);
}

static int
adf_uio_ioctl(struct cdev *dev,
              u_long cmd,
              caddr_t data,
              int fflag,
              struct thread *td)
{
        struct adf_accel_dev *accel_dev = dev->si_drv1;
        struct adf_hw_csr_info *csr_info = NULL;

        if (!accel_dev) {
                printf("%s - accel_dev is NULL\n", __func__);
                return EFAULT;
        }

        csr_info = &accel_dev->hw_device->csr_info;

        switch (cmd) {
        case IOCTL_GET_BUNDLE_SIZE:
                *(uint32_t *)data = csr_info->ring_bundle_size;
                break;
        case IOCTL_ALLOC_BUNDLE:
                return (adf_alloc_bundle(accel_dev, *(int *)data));
        case IOCTL_GET_ACCEL_TYPE:
                *(uint32_t *)data = ADF_UIO_GET_TYPE(accel_dev);
                break;
        case IOCTL_ADD_MEM_FD:
                return (adf_add_mem_fd(accel_dev, *(int *)data));
        default:
                return (ENOTTY);
        }
        return (0);
}

static int
adf_uio_mmap_single(struct cdev *dev,
                    vm_ooffset_t *offset,
                    vm_size_t size,
                    struct vm_object **object,
                    int nprot)
{
        struct adf_uio_open_bundle *handle = NULL;
        struct adf_uio_control_accel *accel = NULL;
        struct adf_uio_control_bundle *bundle = NULL;
        struct adf_uio_instance_rings *instance_rings;
        int error;

        error = devfs_get_cdevpriv((void **)&handle);
        if (error)
                return (error);

        if (!handle->accel) {
                printf("QAT: Error - no accel in handle\n");
                return EINVAL;
        }
        accel = handle->accel;

        if (!accel->accel_dev) {
                printf("QAT: Error - no accel_dev in accel\n");
                return EINVAL;
        }

        bundle = &accel->bundle[handle->bundle];
        if (!bundle->obj) {
                printf("QAT: Error no vm_object in bundle\n");
                return EINVAL;
        }

        /* Adding pid to bundle list */
        instance_rings =
            malloc(sizeof(*instance_rings), M_QAT, M_WAITOK | M_ZERO);
        instance_rings->user_pid = curproc->p_pid;
        instance_rings->ring_mask = 0;
        mutex_lock(&bundle->list_lock);
        list_add_tail(&instance_rings->list, &bundle->list);
        mutex_unlock(&bundle->list_lock);

        vm_object_reference(bundle->obj);
        *object = bundle->obj;
        return (0);
}

static inline void
adf_uio_init_accel_ctrl(struct adf_uio_control_accel *accel,
                        struct adf_accel_dev *accel_dev,
                        unsigned int nb_bundles)
{
        struct adf_uio_control_bundle *bundle;
        struct qat_uio_bundle_dev *priv;
        unsigned int i;

        accel->nb_bundles = nb_bundles;
        accel->total_used_bundles = 0;

        for (i = 0; i < nb_bundles; i++) {
                /*initialize the bundle */
                bundle = &accel->bundle[i];
                priv = &bundle->uio_priv;
                bundle->hardware_bundle_number =
                    GET_MAX_BANKS(accel_dev) - nb_bundles + i;

                INIT_LIST_HEAD(&bundle->list);
                priv->bundle = bundle;
                priv->accel = accel;

                mutex_init(&bundle->lock);
                mutex_init(&bundle->list_lock);
                if (!accel->bar)
                        printf("ERROR: bar not defined in accel\n");
                else
                        bundle->csr_addr = (void *)accel->bar->virt_addr;
        }
}

/**
 * Initialization bars on dev start.
 */
static inline void
adf_uio_init_bundle_dev(struct adf_uio_control_accel *accel,
                        struct adf_accel_dev *accel_dev,
                        unsigned int nb_bundles)
{
        struct adf_uio_control_bundle *bundle;
        unsigned int i;

        for (i = 0; i < nb_bundles; i++) {
                bundle = &accel->bundle[i];
                bundle->obj =
                    adf_uio_map_bar(accel_dev, bundle->hardware_bundle_number);
                if (!bundle->obj) {
                        device_printf(GET_DEV(accel_dev),
                                      "ERROR in adf_alloc_bundle %d\n",
                                      __LINE__);
                }
        }
}

int
adf_uio_register(struct adf_accel_dev *accel_dev)
{
        struct adf_uio_control_accel *accel = NULL;
        char val[ADF_CFG_MAX_VAL_LEN_IN_BYTES] = { 0 };
        int nb_bundles;

        if (!accel_dev) {
                printf("%s - accel_dev is NULL\n", __func__);
                return EFAULT;
        }

        if (adf_cfg_get_param_value(
                accel_dev, ADF_GENERAL_SEC, ADF_FIRST_USER_BUNDLE, val)) {
                nb_bundles = 0;
        } else {
                nb_bundles = GET_MAX_BANKS(accel_dev);
        }

        if (nb_bundles) {
                accel = malloc(sizeof(*accel) +
                                   nb_bundles *
                                       sizeof(struct adf_uio_control_bundle),
                               M_QAT,
                               M_WAITOK | M_ZERO);
                mtx_init(&accel->lock, "qat uio", NULL, MTX_DEF);
                accel->accel_dev = accel_dev;
                accel->bar = accel_dev->accel_pci_dev.pci_bars +
                    ADF_UIO_GET_BAR(accel_dev);

                adf_uio_init_accel_ctrl(accel, accel_dev, nb_bundles);
                accel->cdev = make_dev(&adf_uio_cdevsw,
                                       0,
                                       UID_ROOT,
                                       GID_WHEEL,
                                       0600,
                                       "%s",
                                       device_get_nameunit(GET_DEV(accel_dev)));
                if (accel->cdev == NULL) {
                        mtx_destroy(&accel->lock);
                        goto fail_clean;
                }
                accel->cdev->si_drv1 = accel_dev;
                accel_dev->accel = accel;
                cv_init(&accel->cleanup_ok, "uio_accel_cv");

                adf_uio_init_bundle_dev(accel, accel_dev, nb_bundles);
        }
        return 0;
fail_clean:
        free(accel, M_QAT);
        device_printf(GET_DEV(accel_dev), "Failed to register UIO devices\n");
        return ENODEV;
}

void
adf_uio_remove(struct adf_accel_dev *accel_dev)
{
        struct adf_uio_control_accel *accel = accel_dev->accel;
        struct adf_uio_control_bundle *bundle;
        unsigned int i;

        if (accel) {
                /* Un-mapping all bars */
                for (i = 0; i < accel->nb_bundles; i++) {
                        bundle = &accel->bundle[i];
                        vm_object_deallocate(bundle->obj);
                }

                destroy_dev(accel->cdev);
                mtx_lock(&accel->lock);
                while (accel->num_handles) {
                        cv_timedwait_sig(&accel->cleanup_ok,
                                         &accel->lock,
                                         3 * hz);
                }
                mtx_unlock(&accel->lock);
                mtx_destroy(&accel->lock);
                cv_destroy(&accel->cleanup_ok);
                free(accel, M_QAT);
                accel_dev->accel = NULL;
        }
}