diff options
Diffstat (limited to 'drivers/staging/gasket/gasket_core.c')
-rw-r--r-- | drivers/staging/gasket/gasket_core.c | 1815 |
1 files changed, 0 insertions, 1815 deletions
diff --git a/drivers/staging/gasket/gasket_core.c b/drivers/staging/gasket/gasket_core.c deleted file mode 100644 index 28dab302183b..000000000000 --- a/drivers/staging/gasket/gasket_core.c +++ /dev/null @@ -1,1815 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Gasket generic driver framework. This file contains the implementation - * for the Gasket generic driver framework - the functionality that is common - * across Gasket devices. - * - * Copyright (C) 2018 Google, Inc. - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include "gasket_core.h" - -#include "gasket_interrupt.h" -#include "gasket_ioctl.h" -#include "gasket_page_table.h" -#include "gasket_sysfs.h" - -#include <linux/capability.h> -#include <linux/compiler.h> -#include <linux/delay.h> -#include <linux/device.h> -#include <linux/fs.h> -#include <linux/init.h> -#include <linux/of.h> -#include <linux/pid_namespace.h> -#include <linux/printk.h> -#include <linux/sched.h> - -#ifdef GASKET_KERNEL_TRACE_SUPPORT -#define CREATE_TRACE_POINTS -#include <trace/events/gasket_mmap.h> -#else -#define trace_gasket_mmap_exit(x) -#define trace_gasket_mmap_entry(x, ...) -#endif - -/* - * "Private" members of gasket_driver_desc. - * - * Contains internal per-device type tracking data, i.e., data not appropriate - * as part of the public interface for the generic framework. - */ -struct gasket_internal_desc { - /* Device-specific-driver-provided configuration information. */ - const struct gasket_driver_desc *driver_desc; - - /* Protects access to per-driver data (i.e. this structure). */ - struct mutex mutex; - - /* Kernel-internal device class. */ - struct class *class; - - /* Instantiated / present devices of this type. */ - struct gasket_dev *devs[GASKET_DEV_MAX]; -}; - -/* do_map_region() needs be able to return more than just true/false. */ -enum do_map_region_status { - /* The region was successfully mapped. */ - DO_MAP_REGION_SUCCESS, - - /* Attempted to map region and failed. */ - DO_MAP_REGION_FAILURE, - - /* The requested region to map was not part of a mappable region. */ - DO_MAP_REGION_INVALID, -}; - -/* Global data definitions. */ -/* Mutex - only for framework-wide data. Other data should be protected by - * finer-grained locks. - */ -static DEFINE_MUTEX(g_mutex); - -/* List of all registered device descriptions & their supporting data. */ -static struct gasket_internal_desc g_descs[GASKET_FRAMEWORK_DESC_MAX]; - -/* Mapping of statuses to human-readable strings. Must end with {0,NULL}. */ -static const struct gasket_num_name gasket_status_name_table[] = { - { GASKET_STATUS_DEAD, "DEAD" }, - { GASKET_STATUS_ALIVE, "ALIVE" }, - { GASKET_STATUS_LAMED, "LAMED" }, - { GASKET_STATUS_DRIVER_EXIT, "DRIVER_EXITING" }, - { 0, NULL }, -}; - -/* Enumeration of the automatic Gasket framework sysfs nodes. */ -enum gasket_sysfs_attribute_type { - ATTR_BAR_OFFSETS, - ATTR_BAR_SIZES, - ATTR_DRIVER_VERSION, - ATTR_FRAMEWORK_VERSION, - ATTR_DEVICE_TYPE, - ATTR_HARDWARE_REVISION, - ATTR_PCI_ADDRESS, - ATTR_STATUS, - ATTR_IS_DEVICE_OWNED, - ATTR_DEVICE_OWNER, - ATTR_WRITE_OPEN_COUNT, - ATTR_RESET_COUNT, - ATTR_USER_MEM_RANGES -}; - -/* Perform a standard Gasket callback. */ -static inline int -check_and_invoke_callback(struct gasket_dev *gasket_dev, - int (*cb_function)(struct gasket_dev *)) -{ - int ret = 0; - - if (cb_function) { - mutex_lock(&gasket_dev->mutex); - ret = cb_function(gasket_dev); - mutex_unlock(&gasket_dev->mutex); - } - return ret; -} - -/* Perform a standard Gasket callback without grabbing gasket_dev->mutex. */ -static inline int -gasket_check_and_invoke_callback_nolock(struct gasket_dev *gasket_dev, - int (*cb_function)(struct gasket_dev *)) -{ - int ret = 0; - - if (cb_function) - ret = cb_function(gasket_dev); - return ret; -} - -/* - * Return nonzero if the gasket_cdev_info is owned by the current thread group - * ID. - */ -static int gasket_owned_by_current_tgid(struct gasket_cdev_info *info) -{ - return (info->ownership.is_owned && - (info->ownership.owner == current->tgid)); -} - -/* - * Find the next free gasket_internal_dev slot. - * - * Returns the located slot number on success or a negative number on failure. - */ -static int gasket_find_dev_slot(struct gasket_internal_desc *internal_desc, - const char *kobj_name) -{ - int i; - - mutex_lock(&internal_desc->mutex); - - /* Search for a previous instance of this device. */ - for (i = 0; i < GASKET_DEV_MAX; i++) { - if (internal_desc->devs[i] && - strcmp(internal_desc->devs[i]->kobj_name, kobj_name) == 0) { - pr_err("Duplicate device %s\n", kobj_name); - mutex_unlock(&internal_desc->mutex); - return -EBUSY; - } - } - - /* Find a free device slot. */ - for (i = 0; i < GASKET_DEV_MAX; i++) { - if (!internal_desc->devs[i]) - break; - } - - if (i == GASKET_DEV_MAX) { - pr_err("Too many registered devices; max %d\n", GASKET_DEV_MAX); - mutex_unlock(&internal_desc->mutex); - return -EBUSY; - } - - mutex_unlock(&internal_desc->mutex); - return i; -} - -/* - * Allocate and initialize a Gasket device structure, add the device to the - * device list. - * - * Returns 0 if successful, a negative error code otherwise. - */ -static int gasket_alloc_dev(struct gasket_internal_desc *internal_desc, - struct device *parent, struct gasket_dev **pdev) -{ - int dev_idx; - const struct gasket_driver_desc *driver_desc = - internal_desc->driver_desc; - struct gasket_dev *gasket_dev; - struct gasket_cdev_info *dev_info; - const char *parent_name = dev_name(parent); - - pr_debug("Allocating a Gasket device, parent %s.\n", parent_name); - - *pdev = NULL; - - dev_idx = gasket_find_dev_slot(internal_desc, parent_name); - if (dev_idx < 0) - return dev_idx; - - gasket_dev = *pdev = kzalloc(sizeof(*gasket_dev), GFP_KERNEL); - if (!gasket_dev) { - pr_err("no memory for device, parent %s\n", parent_name); - return -ENOMEM; - } - internal_desc->devs[dev_idx] = gasket_dev; - - mutex_init(&gasket_dev->mutex); - - gasket_dev->internal_desc = internal_desc; - gasket_dev->dev_idx = dev_idx; - snprintf(gasket_dev->kobj_name, GASKET_NAME_MAX, "%s", parent_name); - gasket_dev->dev = get_device(parent); - /* gasket_bar_data is uninitialized. */ - gasket_dev->num_page_tables = driver_desc->num_page_tables; - /* max_page_table_size and *page table are uninit'ed */ - /* interrupt_data is not initialized. */ - /* status is 0, or GASKET_STATUS_DEAD */ - - dev_info = &gasket_dev->dev_info; - snprintf(dev_info->name, GASKET_NAME_MAX, "%s_%u", driver_desc->name, - gasket_dev->dev_idx); - dev_info->devt = - MKDEV(driver_desc->major, driver_desc->minor + - gasket_dev->dev_idx); - dev_info->device = - device_create(internal_desc->class, parent, dev_info->devt, - gasket_dev, dev_info->name); - - /* cdev has not yet been added; cdev_added is 0 */ - dev_info->gasket_dev_ptr = gasket_dev; - /* ownership is all 0, indicating no owner or opens. */ - - return 0; -} - -/* Free a Gasket device. */ -static void gasket_free_dev(struct gasket_dev *gasket_dev) -{ - struct gasket_internal_desc *internal_desc = gasket_dev->internal_desc; - - mutex_lock(&internal_desc->mutex); - internal_desc->devs[gasket_dev->dev_idx] = NULL; - mutex_unlock(&internal_desc->mutex); - put_device(gasket_dev->dev); - kfree(gasket_dev); -} - -/* - * Maps the specified bar into kernel space. - * - * Returns 0 on success, a negative error code otherwise. - * A zero-sized BAR will not be mapped, but is not an error. - */ -static int gasket_map_pci_bar(struct gasket_dev *gasket_dev, int bar_num) -{ - struct gasket_internal_desc *internal_desc = gasket_dev->internal_desc; - const struct gasket_driver_desc *driver_desc = - internal_desc->driver_desc; - ulong desc_bytes = driver_desc->bar_descriptions[bar_num].size; - struct gasket_bar_data *data; - int ret; - - if (desc_bytes == 0) - return 0; - - if (driver_desc->bar_descriptions[bar_num].type != PCI_BAR) { - /* not PCI: skip this entry */ - return 0; - } - - data = &gasket_dev->bar_data[bar_num]; - - /* - * pci_resource_start and pci_resource_len return a "resource_size_t", - * which is safely castable to ulong (which itself is the arg to - * request_mem_region). - */ - data->phys_base = - (ulong)pci_resource_start(gasket_dev->pci_dev, bar_num); - if (!data->phys_base) { - dev_err(gasket_dev->dev, "Cannot get BAR%u base address\n", - bar_num); - return -EINVAL; - } - - data->length_bytes = - (ulong)pci_resource_len(gasket_dev->pci_dev, bar_num); - if (data->length_bytes < desc_bytes) { - dev_err(gasket_dev->dev, - "PCI BAR %u space is too small: %lu; expected >= %lu\n", - bar_num, data->length_bytes, desc_bytes); - return -ENOMEM; - } - - if (!request_mem_region(data->phys_base, data->length_bytes, - gasket_dev->dev_info.name)) { - dev_err(gasket_dev->dev, - "Cannot get BAR %d memory region %p\n", - bar_num, &gasket_dev->pci_dev->resource[bar_num]); - return -EINVAL; - } - - data->virt_base = ioremap(data->phys_base, data->length_bytes); - if (!data->virt_base) { - dev_err(gasket_dev->dev, - "Cannot remap BAR %d memory region %p\n", - bar_num, &gasket_dev->pci_dev->resource[bar_num]); - ret = -ENOMEM; - goto fail; - } - - dma_set_mask(&gasket_dev->pci_dev->dev, DMA_BIT_MASK(64)); - dma_set_coherent_mask(&gasket_dev->pci_dev->dev, DMA_BIT_MASK(64)); - - return 0; - -fail: - iounmap(data->virt_base); - release_mem_region(data->phys_base, data->length_bytes); - return ret; -} - -/* - * Releases PCI BAR mapping. - * - * A zero-sized or not-mapped BAR will not be unmapped, but is not an error. - */ -static void gasket_unmap_pci_bar(struct gasket_dev *dev, int bar_num) -{ - ulong base, bytes; - struct gasket_internal_desc *internal_desc = dev->internal_desc; - const struct gasket_driver_desc *driver_desc = - internal_desc->driver_desc; - - if (driver_desc->bar_descriptions[bar_num].size == 0 || - !dev->bar_data[bar_num].virt_base) - return; - - if (driver_desc->bar_descriptions[bar_num].type != PCI_BAR) - return; - - iounmap(dev->bar_data[bar_num].virt_base); - dev->bar_data[bar_num].virt_base = NULL; - - base = pci_resource_start(dev->pci_dev, bar_num); - if (!base) { - dev_err(dev->dev, "cannot get PCI BAR%u base address\n", - bar_num); - return; - } - - bytes = pci_resource_len(dev->pci_dev, bar_num); - release_mem_region(base, bytes); -} - -/* - * Setup PCI memory mapping for the specified device. - * - * Reads the BAR registers and sets up pointers to the device's memory mapped - * IO space. - * - * Returns 0 on success and a negative value otherwise. - */ -static int gasket_setup_pci(struct pci_dev *pci_dev, - struct gasket_dev *gasket_dev) -{ - int i, mapped_bars, ret; - - for (i = 0; i < PCI_STD_NUM_BARS; i++) { - ret = gasket_map_pci_bar(gasket_dev, i); - if (ret) { - mapped_bars = i; - goto fail; - } - } - - return 0; - -fail: - for (i = 0; i < mapped_bars; i++) - gasket_unmap_pci_bar(gasket_dev, i); - - return -ENOMEM; -} - -/* Unmaps memory for the specified device. */ -static void gasket_cleanup_pci(struct gasket_dev *gasket_dev) -{ - int i; - - for (i = 0; i < PCI_STD_NUM_BARS; i++) - gasket_unmap_pci_bar(gasket_dev, i); -} - -/* Determine the health of the Gasket device. */ -static int gasket_get_hw_status(struct gasket_dev *gasket_dev) -{ - int status; - int i; - const struct gasket_driver_desc *driver_desc = - gasket_dev->internal_desc->driver_desc; - - status = gasket_check_and_invoke_callback_nolock(gasket_dev, - driver_desc->device_status_cb); - if (status != GASKET_STATUS_ALIVE) { - dev_dbg(gasket_dev->dev, "Hardware reported status %d.\n", - status); - return status; - } - - status = gasket_interrupt_system_status(gasket_dev); - if (status != GASKET_STATUS_ALIVE) { - dev_dbg(gasket_dev->dev, - "Interrupt system reported status %d.\n", status); - return status; - } - - for (i = 0; i < driver_desc->num_page_tables; ++i) { - status = gasket_page_table_system_status(gasket_dev->page_table[i]); - if (status != GASKET_STATUS_ALIVE) { - dev_dbg(gasket_dev->dev, - "Page table %d reported status %d.\n", - i, status); - return status; - } - } - - return GASKET_STATUS_ALIVE; -} - -static ssize_t -gasket_write_mappable_regions(char *buf, - const struct gasket_driver_desc *driver_desc, - int bar_index) -{ - int i; - ssize_t written; - ssize_t total_written = 0; - ulong min_addr, max_addr; - struct gasket_bar_desc bar_desc = - driver_desc->bar_descriptions[bar_index]; - - if (bar_desc.permissions == GASKET_NOMAP) - return 0; - for (i = 0; - i < bar_desc.num_mappable_regions && total_written < PAGE_SIZE; - i++) { - min_addr = bar_desc.mappable_regions[i].start - - driver_desc->legacy_mmap_address_offset; - max_addr = bar_desc.mappable_regions[i].start - - driver_desc->legacy_mmap_address_offset + - bar_desc.mappable_regions[i].length_bytes; - written = scnprintf(buf, PAGE_SIZE - total_written, - "0x%08lx-0x%08lx\n", min_addr, max_addr); - total_written += written; - buf += written; - } - return total_written; -} - -static ssize_t gasket_sysfs_data_show(struct device *device, - struct device_attribute *attr, char *buf) -{ - int i, ret = 0; - ssize_t current_written = 0; - const struct gasket_driver_desc *driver_desc; - struct gasket_dev *gasket_dev; - struct gasket_sysfs_attribute *gasket_attr; - const struct gasket_bar_desc *bar_desc; - enum gasket_sysfs_attribute_type sysfs_type; - - gasket_dev = gasket_sysfs_get_device_data(device); - if (!gasket_dev) { - dev_err(device, "No sysfs mapping found for device\n"); - return 0; - } - - gasket_attr = gasket_sysfs_get_attr(device, attr); - if (!gasket_attr) { - dev_err(device, "No sysfs attr found for device\n"); - gasket_sysfs_put_device_data(device, gasket_dev); - return 0; - } - - driver_desc = gasket_dev->internal_desc->driver_desc; - - sysfs_type = - (enum gasket_sysfs_attribute_type)gasket_attr->data.attr_type; - switch (sysfs_type) { - case ATTR_BAR_OFFSETS: - for (i = 0; i < PCI_STD_NUM_BARS; i++) { - bar_desc = &driver_desc->bar_descriptions[i]; - if (bar_desc->size == 0) - continue; - current_written = - snprintf(buf, PAGE_SIZE - ret, "%d: 0x%lx\n", i, - (ulong)bar_desc->base); - buf += current_written; - ret += current_written; - } - break; - case ATTR_BAR_SIZES: - for (i = 0; i < PCI_STD_NUM_BARS; i++) { - bar_desc = &driver_desc->bar_descriptions[i]; - if (bar_desc->size == 0) - continue; - current_written = - snprintf(buf, PAGE_SIZE - ret, "%d: 0x%lx\n", i, - (ulong)bar_desc->size); - buf += current_written; - ret += current_written; - } - break; - case ATTR_DRIVER_VERSION: - ret = snprintf(buf, PAGE_SIZE, "%s\n", - gasket_dev->internal_desc->driver_desc->driver_version); - break; - case ATTR_FRAMEWORK_VERSION: - ret = snprintf(buf, PAGE_SIZE, "%s\n", - GASKET_FRAMEWORK_VERSION); - break; - case ATTR_DEVICE_TYPE: - ret = snprintf(buf, PAGE_SIZE, "%s\n", - gasket_dev->internal_desc->driver_desc->name); - break; - case ATTR_HARDWARE_REVISION: - ret = snprintf(buf, PAGE_SIZE, "%d\n", - gasket_dev->hardware_revision); - break; - case ATTR_PCI_ADDRESS: - ret = snprintf(buf, PAGE_SIZE, "%s\n", gasket_dev->kobj_name); - break; - case ATTR_STATUS: - ret = snprintf(buf, PAGE_SIZE, "%s\n", - gasket_num_name_lookup(gasket_dev->status, - gasket_status_name_table)); - break; - case ATTR_IS_DEVICE_OWNED: - ret = snprintf(buf, PAGE_SIZE, "%d\n", - gasket_dev->dev_info.ownership.is_owned); - break; - case ATTR_DEVICE_OWNER: - ret = snprintf(buf, PAGE_SIZE, "%d\n", - gasket_dev->dev_info.ownership.owner); - break; - case ATTR_WRITE_OPEN_COUNT: - ret = snprintf(buf, PAGE_SIZE, "%d\n", - gasket_dev->dev_info.ownership.write_open_count); - break; - case ATTR_RESET_COUNT: - ret = snprintf(buf, PAGE_SIZE, "%d\n", gasket_dev->reset_count); - break; - case ATTR_USER_MEM_RANGES: - for (i = 0; i < PCI_STD_NUM_BARS; ++i) { - current_written = - gasket_write_mappable_regions(buf, driver_desc, - i); - buf += current_written; - ret += current_written; - } - break; - default: - dev_dbg(gasket_dev->dev, "Unknown attribute: %s\n", - attr->attr.name); - ret = 0; - break; - } - - gasket_sysfs_put_attr(device, gasket_attr); - gasket_sysfs_put_device_data(device, gasket_dev); - return ret; -} - -/* These attributes apply to all Gasket driver instances. */ -static const struct gasket_sysfs_attribute gasket_sysfs_generic_attrs[] = { - GASKET_SYSFS_RO(bar_offsets, gasket_sysfs_data_show, ATTR_BAR_OFFSETS), - GASKET_SYSFS_RO(bar_sizes, gasket_sysfs_data_show, ATTR_BAR_SIZES), - GASKET_SYSFS_RO(driver_version, gasket_sysfs_data_show, - ATTR_DRIVER_VERSION), - GASKET_SYSFS_RO(framework_version, gasket_sysfs_data_show, - ATTR_FRAMEWORK_VERSION), - GASKET_SYSFS_RO(device_type, gasket_sysfs_data_show, ATTR_DEVICE_TYPE), - GASKET_SYSFS_RO(revision, gasket_sysfs_data_show, - ATTR_HARDWARE_REVISION), - GASKET_SYSFS_RO(pci_address, gasket_sysfs_data_show, ATTR_PCI_ADDRESS), - GASKET_SYSFS_RO(status, gasket_sysfs_data_show, ATTR_STATUS), - GASKET_SYSFS_RO(is_device_owned, gasket_sysfs_data_show, - ATTR_IS_DEVICE_OWNED), - GASKET_SYSFS_RO(device_owner, gasket_sysfs_data_show, - ATTR_DEVICE_OWNER), - GASKET_SYSFS_RO(write_open_count, gasket_sysfs_data_show, - ATTR_WRITE_OPEN_COUNT), - GASKET_SYSFS_RO(reset_count, gasket_sysfs_data_show, ATTR_RESET_COUNT), - GASKET_SYSFS_RO(user_mem_ranges, gasket_sysfs_data_show, - ATTR_USER_MEM_RANGES), - GASKET_END_OF_ATTR_ARRAY -}; - -/* Add a char device and related info. */ -static int gasket_add_cdev(struct gasket_cdev_info *dev_info, - const struct file_operations *file_ops, - struct module *owner) -{ - int ret; - - cdev_init(&dev_info->cdev, file_ops); - dev_info->cdev.owner = owner; - ret = cdev_add(&dev_info->cdev, dev_info->devt, 1); - if (ret) { - dev_err(dev_info->gasket_dev_ptr->dev, - "cannot add char device [ret=%d]\n", ret); - return ret; - } - dev_info->cdev_added = 1; - - return 0; -} - -/* Disable device operations. */ -void gasket_disable_device(struct gasket_dev *gasket_dev) -{ - const struct gasket_driver_desc *driver_desc = - gasket_dev->internal_desc->driver_desc; - int i; - - /* Only delete the device if it has been successfully added. */ - if (gasket_dev->dev_info.cdev_added) - cdev_del(&gasket_dev->dev_info.cdev); - - gasket_dev->status = GASKET_STATUS_DEAD; - - gasket_interrupt_cleanup(gasket_dev); - - for (i = 0; i < driver_desc->num_page_tables; ++i) { - if (gasket_dev->page_table[i]) { - gasket_page_table_reset(gasket_dev->page_table[i]); - gasket_page_table_cleanup(gasket_dev->page_table[i]); - } - } -} -EXPORT_SYMBOL(gasket_disable_device); - -/* - * Registered driver descriptor lookup for PCI devices. - * - * Precondition: Called with g_mutex held (to avoid a race on return). - * Returns NULL if no matching device was found. - */ -static struct gasket_internal_desc * -lookup_pci_internal_desc(struct pci_dev *pci_dev) -{ - int i; - - __must_hold(&g_mutex); - for (i = 0; i < GASKET_FRAMEWORK_DESC_MAX; i++) { - if (g_descs[i].driver_desc && - g_descs[i].driver_desc->pci_id_table && - pci_match_id(g_descs[i].driver_desc->pci_id_table, pci_dev)) - return &g_descs[i]; - } - - return NULL; -} - -/* - * Verifies that the user has permissions to perform the requested mapping and - * that the provided descriptor/range is of adequate size to hold the range to - * be mapped. - */ -static bool gasket_mmap_has_permissions(struct gasket_dev *gasket_dev, - struct vm_area_struct *vma, - int bar_permissions) -{ - int requested_permissions; - /* Always allow sysadmin to access. */ - if (capable(CAP_SYS_ADMIN)) - return true; - - /* Never allow non-sysadmins to access to a dead device. */ - if (gasket_dev->status != GASKET_STATUS_ALIVE) { - dev_dbg(gasket_dev->dev, "Device is dead.\n"); - return false; - } - - /* Make sure that no wrong flags are set. */ - requested_permissions = - (vma->vm_flags & VM_ACCESS_FLAGS); - if (requested_permissions & ~(bar_permissions)) { - dev_dbg(gasket_dev->dev, - "Attempting to map a region with requested permissions 0x%x, but region has permissions 0x%x.\n", - requested_permissions, bar_permissions); - return false; - } - - /* Do not allow a non-owner to write. */ - if ((vma->vm_flags & VM_WRITE) && - !gasket_owned_by_current_tgid(&gasket_dev->dev_info)) { - dev_dbg(gasket_dev->dev, - "Attempting to mmap a region for write without owning device.\n"); - return false; - } - - return true; -} - -/* - * Verifies that the input address is within the region allocated to coherent - * buffer. - */ -static bool -gasket_is_coherent_region(const struct gasket_driver_desc *driver_desc, - ulong address) -{ - struct gasket_coherent_buffer_desc coh_buff_desc = - driver_desc->coherent_buffer_description; - - if (coh_buff_desc.permissions != GASKET_NOMAP) { - if ((address >= coh_buff_desc.base) && - (address < coh_buff_desc.base + coh_buff_desc.size)) { - return true; - } - } - return false; -} - -static int gasket_get_bar_index(const struct gasket_dev *gasket_dev, - ulong phys_addr) -{ - int i; - const struct gasket_driver_desc *driver_desc; - - driver_desc = gasket_dev->internal_desc->driver_desc; - for (i = 0; i < PCI_STD_NUM_BARS; ++i) { - struct gasket_bar_desc bar_desc = - driver_desc->bar_descriptions[i]; - - if (bar_desc.permissions != GASKET_NOMAP) { - if (phys_addr >= bar_desc.base && - phys_addr < (bar_desc.base + bar_desc.size)) { - return i; - } - } - } - /* If we haven't found the address by now, it is invalid. */ - return -EINVAL; -} - -/* - * Sets the actual bounds to map, given the device's mappable region. - * - * Given the device's mappable region, along with the user-requested mapping - * start offset and length of the user region, determine how much of this - * mappable region can be mapped into the user's region (start/end offsets), - * and the physical offset (phys_offset) into the BAR where the mapping should - * begin (either the VMA's or region lower bound). - * - * In other words, this calculates the overlap between the VMA - * (bar_offset, requested_length) and the given gasket_mappable_region. - * - * Returns true if there's anything to map, and false otherwise. - */ -static bool -gasket_mm_get_mapping_addrs(const struct gasket_mappable_region *region, - ulong bar_offset, ulong requested_length, - struct gasket_mappable_region *mappable_region, - ulong *virt_offset) -{ - ulong range_start = region->start; - ulong range_length = region->length_bytes; - ulong range_end = range_start + range_length; - - *virt_offset = 0; - if (bar_offset + requested_length < range_start) { - /* - * If the requested region is completely below the range, - * there is nothing to map. - */ - return false; - } else if (bar_offset <= range_start) { - /* If the bar offset is below this range's start - * but the requested length continues into it: - * 1) Only map starting from the beginning of this - * range's phys. offset, so we don't map unmappable - * memory. - * 2) The length of the virtual memory to not map is the - * delta between the bar offset and the - * mappable start (and since the mappable start is - * bigger, start - req.) - * 3) The map length is the minimum of the mappable - * requested length (requested_length - virt_offset) - * and the actual mappable length of the range. - */ - mappable_region->start = range_start; - *virt_offset = range_start - bar_offset; - mappable_region->length_bytes = - min(requested_length - *virt_offset, range_length); - return true; - } else if (bar_offset > range_start && - bar_offset < range_end) { - /* - * If the bar offset is within this range: - * 1) Map starting from the bar offset. - * 2) Because there is no forbidden memory between the - * bar offset and the range start, - * virt_offset is 0. - * 3) The map length is the minimum of the requested - * length and the remaining length in the buffer - * (range_end - bar_offset) - */ - mappable_region->start = bar_offset; - *virt_offset = 0; - mappable_region->length_bytes = - min(requested_length, range_end - bar_offset); - return true; - } - - /* - * If the requested [start] offset is above range_end, - * there's nothing to map. - */ - return false; -} - -/* - * Calculates the offset where the VMA range begins in its containing BAR. - * The offset is written into bar_offset on success. - * Returns zero on success, anything else on error. - */ -static int gasket_mm_vma_bar_offset(const struct gasket_dev *gasket_dev, - const struct vm_area_struct *vma, - ulong *bar_offset) -{ - ulong raw_offset; - int bar_index; - const struct gasket_driver_desc *driver_desc = - gasket_dev->internal_desc->driver_desc; - - raw_offset = (vma->vm_pgoff << PAGE_SHIFT) + - driver_desc->legacy_mmap_address_offset; - bar_index = gasket_get_bar_index(gasket_dev, raw_offset); - if (bar_index < 0) { - dev_err(gasket_dev->dev, - "Unable to find matching bar for address 0x%lx\n", - raw_offset); - trace_gasket_mmap_exit(bar_index); - return bar_index; - } - *bar_offset = - raw_offset - driver_desc->bar_descriptions[bar_index].base; - - return 0; -} - -int gasket_mm_unmap_region(const struct gasket_dev *gasket_dev, - struct vm_area_struct *vma, - const struct gasket_mappable_region *map_region) -{ - ulong bar_offset; - ulong virt_offset; - struct gasket_mappable_region mappable_region; - int ret; - - if (map_region->length_bytes == 0) - return 0; - - ret = gasket_mm_vma_bar_offset(gasket_dev, vma, &bar_offset); - if (ret) - return ret; - - if (!gasket_mm_get_mapping_addrs(map_region, bar_offset, - vma->vm_end - vma->vm_start, - &mappable_region, &virt_offset)) - return 1; - - /* - * The length passed to zap_vma_ptes MUST BE A MULTIPLE OF - * PAGE_SIZE! Trust me. I have the scars. - * - * Next multiple of y: ceil_div(x, y) * y - */ - zap_vma_ptes(vma, vma->vm_start + virt_offset, - DIV_ROUND_UP(mappable_region.length_bytes, PAGE_SIZE) * - PAGE_SIZE); - return 0; -} -EXPORT_SYMBOL(gasket_mm_unmap_region); - -/* Maps a virtual address + range to a physical offset of a BAR. */ -static enum do_map_region_status -do_map_region(const struct gasket_dev *gasket_dev, struct vm_area_struct *vma, - struct gasket_mappable_region *mappable_region) -{ - /* Maximum size of a single call to io_remap_pfn_range. */ - /* I pulled this number out of thin air. */ - const ulong max_chunk_size = 64 * 1024 * 1024; - ulong chunk_size, mapped_bytes = 0; - - const struct gasket_driver_desc *driver_desc = - gasket_dev->internal_desc->driver_desc; - - ulong bar_offset, virt_offset; - struct gasket_mappable_region region_to_map; - ulong phys_offset, map_length; - ulong virt_base, phys_base; - int bar_index, ret; - - ret = gasket_mm_vma_bar_offset(gasket_dev, vma, &bar_offset); - if (ret) - return DO_MAP_REGION_INVALID; - - if (!gasket_mm_get_mapping_addrs(mappable_region, bar_offset, - vma->vm_end - vma->vm_start, - ®ion_to_map, &virt_offset)) - return DO_MAP_REGION_INVALID; - phys_offset = region_to_map.start; - map_length = region_to_map.length_bytes; - - virt_base = vma->vm_start + virt_offset; - bar_index = - gasket_get_bar_index(gasket_dev, - (vma->vm_pgoff << PAGE_SHIFT) + - driver_desc->legacy_mmap_address_offset); - - if (bar_index < 0) - return DO_MAP_REGION_INVALID; - - phys_base = gasket_dev->bar_data[bar_index].phys_base + phys_offset; - while (mapped_bytes < map_length) { - /* - * io_remap_pfn_range can take a while, so we chunk its - * calls and call cond_resched between each. - */ - chunk_size = min(max_chunk_size, map_length - mapped_bytes); - - cond_resched(); - ret = io_remap_pfn_range(vma, virt_base + mapped_bytes, - (phys_base + mapped_bytes) >> - PAGE_SHIFT, chunk_size, - vma->vm_page_prot); - if (ret) { - dev_err(gasket_dev->dev, - "Error remapping PFN range.\n"); - goto fail; - } - mapped_bytes += chunk_size; - } - - return DO_MAP_REGION_SUCCESS; - -fail: - /* Unmap the partial chunk we mapped. */ - mappable_region->length_bytes = mapped_bytes; - if (gasket_mm_unmap_region(gasket_dev, vma, mappable_region)) - dev_err(gasket_dev->dev, - "Error unmapping partial region 0x%lx (0x%lx bytes)\n", - (ulong)virt_offset, - (ulong)mapped_bytes); - - return DO_MAP_REGION_FAILURE; -} - -/* Map a region of coherent memory. */ -static int gasket_mmap_coherent(struct gasket_dev *gasket_dev, - struct vm_area_struct *vma) -{ - const struct gasket_driver_desc *driver_desc = - gasket_dev->internal_desc->driver_desc; - const ulong requested_length = vma->vm_end - vma->vm_start; - int ret; - ulong permissions; - - if (requested_length == 0 || requested_length > - gasket_dev->coherent_buffer.length_bytes) { - trace_gasket_mmap_exit(-EINVAL); - return -EINVAL; - } - - permissions = driver_desc->coherent_buffer_description.permissions; - if (!gasket_mmap_has_permissions(gasket_dev, vma, permissions)) { - dev_err(gasket_dev->dev, "Permission checking failed.\n"); - trace_gasket_mmap_exit(-EPERM); - return -EPERM; - } - - vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); - - ret = remap_pfn_range(vma, vma->vm_start, - (gasket_dev->coherent_buffer.phys_base) >> - PAGE_SHIFT, requested_length, vma->vm_page_prot); - if (ret) { - dev_err(gasket_dev->dev, "Error remapping PFN range err=%d.\n", - ret); - trace_gasket_mmap_exit(ret); - return ret; - } - - /* Record the user virtual to dma_address mapping that was - * created by the kernel. - */ - gasket_set_user_virt(gasket_dev, requested_length, - gasket_dev->coherent_buffer.phys_base, - vma->vm_start); - return 0; -} - -/* Map a device's BARs into user space. */ -static int gasket_mmap(struct file *filp, struct vm_area_struct *vma) -{ - int i, ret; - int bar_index; - int has_mapped_anything = 0; - ulong permissions; - ulong raw_offset, vma_size; - bool is_coherent_region; - const struct gasket_driver_desc *driver_desc; - struct gasket_dev *gasket_dev = (struct gasket_dev *)filp->private_data; - const struct gasket_bar_desc *bar_desc; - struct gasket_mappable_region *map_regions = NULL; - int num_map_regions = 0; - enum do_map_region_status map_status; - - driver_desc = gasket_dev->internal_desc->driver_desc; - - if (vma->vm_start & ~PAGE_MASK) { - dev_err(gasket_dev->dev, - "Base address not page-aligned: 0x%lx\n", - vma->vm_start); - trace_gasket_mmap_exit(-EINVAL); - return -EINVAL; - } - - /* Calculate the offset of this range into physical mem. */ - raw_offset = (vma->vm_pgoff << PAGE_SHIFT) + - driver_desc->legacy_mmap_address_offset; - vma_size = vma->vm_end - vma->vm_start; - trace_gasket_mmap_entry(gasket_dev->dev_info.name, raw_offset, - vma_size); - - /* - * Check if the raw offset is within a bar region. If not, check if it - * is a coherent region. - */ - bar_index = gasket_get_bar_index(gasket_dev, raw_offset); - is_coherent_region = gasket_is_coherent_region(driver_desc, raw_offset); - if (bar_index < 0 && !is_coherent_region) { - dev_err(gasket_dev->dev, - "Unable to find matching bar for address 0x%lx\n", - raw_offset); - trace_gasket_mmap_exit(bar_index); - return bar_index; - } - if (bar_index > 0 && is_coherent_region) { - dev_err(gasket_dev->dev, - "double matching bar and coherent buffers for address 0x%lx\n", - raw_offset); - trace_gasket_mmap_exit(bar_index); - return -EINVAL; - } - - vma->vm_private_data = gasket_dev; - - if (is_coherent_region) - return gasket_mmap_coherent(gasket_dev, vma); - - /* Everything in the rest of this function is for normal BAR mapping. */ - - /* - * Subtract the base of the bar from the raw offset to get the - * memory location within the bar to map. - */ - bar_desc = &driver_desc->bar_descriptions[bar_index]; - permissions = bar_desc->permissions; - if (!gasket_mmap_has_permissions(gasket_dev, vma, permissions)) { - dev_err(gasket_dev->dev, "Permission checking failed.\n"); - trace_gasket_mmap_exit(-EPERM); - return -EPERM; - } - - if (driver_desc->get_mappable_regions_cb) { - ret = driver_desc->get_mappable_regions_cb(gasket_dev, - bar_index, - &map_regions, - &num_map_regions); - if (ret) - return ret; - } else { - if (!gasket_mmap_has_permissions(gasket_dev, vma, - bar_desc->permissions)) { - dev_err(gasket_dev->dev, - "Permission checking failed.\n"); - trace_gasket_mmap_exit(-EPERM); - return -EPERM; - } - num_map_regions = bar_desc->num_mappable_regions; - map_regions = kcalloc(num_map_regions, - sizeof(*bar_desc->mappable_regions), - GFP_KERNEL); - if (map_regions) { - memcpy(map_regions, bar_desc->mappable_regions, - num_map_regions * - sizeof(*bar_desc->mappable_regions)); - } - } - - if (!map_regions || num_map_regions == 0) { - dev_err(gasket_dev->dev, "No mappable regions returned!\n"); - return -EINVAL; - } - - /* Marks the VMA's pages as uncacheable. */ - vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); - for (i = 0; i < num_map_regions; i++) { - map_status = do_map_region(gasket_dev, vma, &map_regions[i]); - /* Try the next region if this one was not mappable. */ - if (map_status == DO_MAP_REGION_INVALID) - continue; - if (map_status == DO_MAP_REGION_FAILURE) { - ret = -ENOMEM; - goto fail; - } - - has_mapped_anything = 1; - } - - kfree(map_regions); - - /* If we could not map any memory, the request was invalid. */ - if (!has_mapped_anything) { - dev_err(gasket_dev->dev, - "Map request did not contain a valid region.\n"); - trace_gasket_mmap_exit(-EINVAL); - return -EINVAL; - } - - trace_gasket_mmap_exit(0); - return 0; - -fail: - /* Need to unmap any mapped ranges. */ - num_map_regions = i; - for (i = 0; i < num_map_regions; i++) - if (gasket_mm_unmap_region(gasket_dev, vma, - &bar_desc->mappable_regions[i])) - dev_err(gasket_dev->dev, "Error unmapping range %d.\n", - i); - kfree(map_regions); - - return ret; -} - -/* - * Open the char device file. - * - * If the open is for writing, and the device is not owned, this process becomes - * the owner. If the open is for writing and the device is already owned by - * some other process, it is an error. If this process is the owner, increment - * the open count. - * - * Returns 0 if successful, a negative error number otherwise. - */ -static int gasket_open(struct inode *inode, struct file *filp) -{ - int ret; - struct gasket_dev *gasket_dev; - const struct gasket_driver_desc *driver_desc; - struct gasket_ownership *ownership; - char task_name[TASK_COMM_LEN]; - struct gasket_cdev_info *dev_info = - container_of(inode->i_cdev, struct gasket_cdev_info, cdev); - struct pid_namespace *pid_ns = task_active_pid_ns(current); - bool is_root = ns_capable(pid_ns->user_ns, CAP_SYS_ADMIN); - - gasket_dev = dev_info->gasket_dev_ptr; - driver_desc = gasket_dev->internal_desc->driver_desc; - ownership = &dev_info->ownership; - get_task_comm(task_name, current); - filp->private_data = gasket_dev; - inode->i_size = 0; - - dev_dbg(gasket_dev->dev, - "Attempting to open with tgid %u (%s) (f_mode: 0%03o, fmode_write: %d is_root: %u)\n", - current->tgid, task_name, filp->f_mode, - (filp->f_mode & FMODE_WRITE), is_root); - - /* Always allow non-writing accesses. */ - if (!(filp->f_mode & FMODE_WRITE)) { - dev_dbg(gasket_dev->dev, "Allowing read-only opening.\n"); - return 0; - } - - mutex_lock(&gasket_dev->mutex); - - dev_dbg(gasket_dev->dev, - "Current owner open count (owning tgid %u): %d.\n", - ownership->owner, ownership->write_open_count); - - /* Opening a node owned by another TGID is an error (unless root) */ - if (ownership->is_owned && ownership->owner != current->tgid && - !is_root) { - dev_err(gasket_dev->dev, - "Process %u is opening a node held by %u.\n", - current->tgid, ownership->owner); - mutex_unlock(&gasket_dev->mutex); - return -EPERM; - } - - /* If the node is not owned, assign it to the current TGID. */ - if (!ownership->is_owned) { - ret = gasket_check_and_invoke_callback_nolock(gasket_dev, - driver_desc->device_open_cb); - if (ret) { - dev_err(gasket_dev->dev, - "Error in device open cb: %d\n", ret); - mutex_unlock(&gasket_dev->mutex); - return ret; - } - ownership->is_owned = 1; - ownership->owner = current->tgid; - dev_dbg(gasket_dev->dev, "Device owner is now tgid %u\n", - ownership->owner); - } - - ownership->write_open_count++; - - dev_dbg(gasket_dev->dev, "New open count (owning tgid %u): %d\n", - ownership->owner, ownership->write_open_count); - - mutex_unlock(&gasket_dev->mutex); - return 0; -} - -/* - * Called on a close of the device file. If this process is the owner, - * decrement the open count. On last close by the owner, free up buffers and - * eventfd contexts, and release ownership. - * - * Returns 0 if successful, a negative error number otherwise. - */ -static int gasket_release(struct inode *inode, struct file *file) -{ - int i; - struct gasket_dev *gasket_dev; - struct gasket_ownership *ownership; - const struct gasket_driver_desc *driver_desc; - char task_name[TASK_COMM_LEN]; - struct gasket_cdev_info *dev_info = - container_of(inode->i_cdev, struct gasket_cdev_info, cdev); - struct pid_namespace *pid_ns = task_active_pid_ns(current); - bool is_root = ns_capable(pid_ns->user_ns, CAP_SYS_ADMIN); - - gasket_dev = dev_info->gasket_dev_ptr; - driver_desc = gasket_dev->internal_desc->driver_desc; - ownership = &dev_info->ownership; - get_task_comm(task_name, current); - mutex_lock(&gasket_dev->mutex); - - dev_dbg(gasket_dev->dev, - "Releasing device node. Call origin: tgid %u (%s) (f_mode: 0%03o, fmode_write: %d, is_root: %u)\n", - current->tgid, task_name, file->f_mode, - (file->f_mode & FMODE_WRITE), is_root); - dev_dbg(gasket_dev->dev, "Current open count (owning tgid %u): %d\n", - ownership->owner, ownership->write_open_count); - - if (file->f_mode & FMODE_WRITE) { - ownership->write_open_count--; - if (ownership->write_open_count == 0) { - dev_dbg(gasket_dev->dev, "Device is now free\n"); - ownership->is_owned = 0; - ownership->owner = 0; - - /* Forces chip reset before we unmap the page tables. */ - driver_desc->device_reset_cb(gasket_dev); - - for (i = 0; i < driver_desc->num_page_tables; ++i) { - gasket_page_table_unmap_all(gasket_dev->page_table[i]); - gasket_page_table_garbage_collect(gasket_dev->page_table[i]); - gasket_free_coherent_memory_all(gasket_dev, i); - } - - /* Closes device, enters power save. */ - gasket_check_and_invoke_callback_nolock(gasket_dev, - driver_desc->device_close_cb); - } - } - - dev_dbg(gasket_dev->dev, "New open count (owning tgid %u): %d\n", - ownership->owner, ownership->write_open_count); - mutex_unlock(&gasket_dev->mutex); - return 0; -} - -/* - * Gasket ioctl dispatch function. - * - * Check if the ioctl is a generic ioctl. If not, pass the ioctl to the - * ioctl_handler_cb registered in the driver description. - * If the ioctl is a generic ioctl, pass it to gasket_ioctl_handler. - */ -static long gasket_ioctl(struct file *filp, uint cmd, ulong arg) -{ - struct gasket_dev *gasket_dev; - const struct gasket_driver_desc *driver_desc; - void __user *argp = (void __user *)arg; - char path[256]; - - gasket_dev = (struct gasket_dev *)filp->private_data; - driver_desc = gasket_dev->internal_desc->driver_desc; - if (!driver_desc) { - dev_dbg(gasket_dev->dev, - "Unable to find device descriptor for file %s\n", - d_path(&filp->f_path, path, 256)); - return -ENODEV; - } - - if (!gasket_is_supported_ioctl(cmd)) { - /* - * The ioctl handler is not a standard Gasket callback, since - * it requires different arguments. This means we can't use - * check_and_invoke_callback. - */ - if (driver_desc->ioctl_handler_cb) - return driver_desc->ioctl_handler_cb(filp, cmd, argp); - - dev_dbg(gasket_dev->dev, "Received unknown ioctl 0x%x\n", cmd); - return -EINVAL; - } - - return gasket_handle_ioctl(filp, cmd, argp); -} - -/* File operations for all Gasket devices. */ -static const struct file_operations gasket_file_ops = { - .owner = THIS_MODULE, - .llseek = no_llseek, - .mmap = gasket_mmap, - .open = gasket_open, - .release = gasket_release, - .unlocked_ioctl = gasket_ioctl, -}; - -/* Perform final init and marks the device as active. */ -int gasket_enable_device(struct gasket_dev *gasket_dev) -{ - int tbl_idx; - int ret; - const struct gasket_driver_desc *driver_desc = - gasket_dev->internal_desc->driver_desc; - - ret = gasket_interrupt_init(gasket_dev); - if (ret) { - dev_err(gasket_dev->dev, - "Critical failure to allocate interrupts: %d\n", ret); - gasket_interrupt_cleanup(gasket_dev); - return ret; - } - - for (tbl_idx = 0; tbl_idx < driver_desc->num_page_tables; tbl_idx++) { - dev_dbg(gasket_dev->dev, "Initializing page table %d.\n", - tbl_idx); - ret = gasket_page_table_init(&gasket_dev->page_table[tbl_idx], - &gasket_dev->bar_data[driver_desc->page_table_bar_index], - &driver_desc->page_table_configs[tbl_idx], - gasket_dev->dev, - gasket_dev->pci_dev); - if (ret) { - dev_err(gasket_dev->dev, - "Couldn't init page table %d: %d\n", - tbl_idx, ret); - return ret; - } - /* - * Make sure that the page table is clear and set to simple - * addresses. - */ - gasket_page_table_reset(gasket_dev->page_table[tbl_idx]); - } - - /* - * hardware_revision_cb returns a positive integer (the rev) if - * successful.) - */ - ret = check_and_invoke_callback(gasket_dev, - driver_desc->hardware_revision_cb); - if (ret < 0) { - dev_err(gasket_dev->dev, - "Error getting hardware revision: %d\n", ret); - return ret; - } - gasket_dev->hardware_revision = ret; - - /* device_status_cb returns a device status, not an error code. */ - gasket_dev->status = gasket_get_hw_status(gasket_dev); - if (gasket_dev->status == GASKET_STATUS_DEAD) - dev_err(gasket_dev->dev, "Device reported as unhealthy.\n"); - - ret = gasket_add_cdev(&gasket_dev->dev_info, &gasket_file_ops, - driver_desc->module); - if (ret) - return ret; - - return 0; -} -EXPORT_SYMBOL(gasket_enable_device); - -static int __gasket_add_device(struct device *parent_dev, - struct gasket_internal_desc *internal_desc, - struct gasket_dev **gasket_devp) -{ - int ret; - struct gasket_dev *gasket_dev; - const struct gasket_driver_desc *driver_desc = - internal_desc->driver_desc; - - ret = gasket_alloc_dev(internal_desc, parent_dev, &gasket_dev); - if (ret) - return ret; - if (IS_ERR(gasket_dev->dev_info.device)) { - dev_err(parent_dev, "Cannot create %s device %s [ret = %ld]\n", - driver_desc->name, gasket_dev->dev_info.name, - PTR_ERR(gasket_dev->dev_info.device)); - ret = -ENODEV; - goto free_gasket_dev; - } - - ret = gasket_sysfs_create_mapping(gasket_dev->dev_info.device, - gasket_dev); - if (ret) - goto remove_device; - - ret = gasket_sysfs_create_entries(gasket_dev->dev_info.device, - gasket_sysfs_generic_attrs); - if (ret) - goto remove_sysfs_mapping; - - *gasket_devp = gasket_dev; - return 0; - -remove_sysfs_mapping: - gasket_sysfs_remove_mapping(gasket_dev->dev_info.device); -remove_device: - device_destroy(internal_desc->class, gasket_dev->dev_info.devt); -free_gasket_dev: - gasket_free_dev(gasket_dev); - return ret; -} - -static void __gasket_remove_device(struct gasket_internal_desc *internal_desc, - struct gasket_dev *gasket_dev) -{ - gasket_sysfs_remove_mapping(gasket_dev->dev_info.device); - device_destroy(internal_desc->class, gasket_dev->dev_info.devt); - gasket_free_dev(gasket_dev); -} - -/* - * Add PCI gasket device. - * - * Called by Gasket device probe function. - * Allocates device metadata and maps device memory. The device driver must - * call gasket_enable_device after driver init is complete to place the device - * in active use. - */ -int gasket_pci_add_device(struct pci_dev *pci_dev, - struct gasket_dev **gasket_devp) -{ - int ret; - struct gasket_internal_desc *internal_desc; - struct gasket_dev *gasket_dev; - struct device *parent; - - dev_dbg(&pci_dev->dev, "add PCI gasket device\n"); - - mutex_lock(&g_mutex); - internal_desc = lookup_pci_internal_desc(pci_dev); - mutex_unlock(&g_mutex); - if (!internal_desc) { - dev_err(&pci_dev->dev, - "PCI add device called for unknown driver type\n"); - return -ENODEV; - } - - parent = &pci_dev->dev; - ret = __gasket_add_device(parent, internal_desc, &gasket_dev); - if (ret) - return ret; - - gasket_dev->pci_dev = pci_dev; - ret = gasket_setup_pci(pci_dev, gasket_dev); - if (ret) - goto cleanup_pci; - - /* - * Once we've created the mapping structures successfully, attempt to - * create a symlink to the pci directory of this object. - */ - ret = sysfs_create_link(&gasket_dev->dev_info.device->kobj, - &pci_dev->dev.kobj, dev_name(&pci_dev->dev)); - if (ret) { - dev_err(gasket_dev->dev, - "Cannot create sysfs pci link: %d\n", ret); - goto cleanup_pci; - } - - *gasket_devp = gasket_dev; - return 0; - -cleanup_pci: - gasket_cleanup_pci(gasket_dev); - __gasket_remove_device(internal_desc, gasket_dev); - return ret; -} -EXPORT_SYMBOL(gasket_pci_add_device); - -/* Remove a PCI gasket device. */ -void gasket_pci_remove_device(struct pci_dev *pci_dev) -{ - int i; - struct gasket_internal_desc *internal_desc; - struct gasket_dev *gasket_dev = NULL; - /* Find the device desc. */ - mutex_lock(&g_mutex); - internal_desc = lookup_pci_internal_desc(pci_dev); - if (!internal_desc) { - mutex_unlock(&g_mutex); - return; - } - mutex_unlock(&g_mutex); - - /* Now find the specific device */ - mutex_lock(&internal_desc->mutex); - for (i = 0; i < GASKET_DEV_MAX; i++) { - if (internal_desc->devs[i] && - internal_desc->devs[i]->pci_dev == pci_dev) { - gasket_dev = internal_desc->devs[i]; - break; - } - } - mutex_unlock(&internal_desc->mutex); - - if (!gasket_dev) - return; - - dev_dbg(gasket_dev->dev, "remove %s PCI gasket device\n", - internal_desc->driver_desc->name); - - gasket_cleanup_pci(gasket_dev); - __gasket_remove_device(internal_desc, gasket_dev); -} -EXPORT_SYMBOL(gasket_pci_remove_device); - -/** - * Lookup a name by number in a num_name table. - * @num: Number to lookup. - * @table: Array of num_name structures, the table for the lookup. - * - * Description: Searches for num in the table. If found, the - * corresponding name is returned; otherwise NULL - * is returned. - * - * The table must have a NULL name pointer at the end. - */ -const char *gasket_num_name_lookup(uint num, - const struct gasket_num_name *table) -{ - uint i = 0; - - while (table[i].snn_name) { - if (num == table[i].snn_num) - break; - ++i; - } - - return table[i].snn_name; -} -EXPORT_SYMBOL(gasket_num_name_lookup); - -int gasket_reset(struct gasket_dev *gasket_dev) -{ - int ret; - - mutex_lock(&gasket_dev->mutex); - ret = gasket_reset_nolock(gasket_dev); - mutex_unlock(&gasket_dev->mutex); - return ret; -} -EXPORT_SYMBOL(gasket_reset); - -int gasket_reset_nolock(struct gasket_dev *gasket_dev) -{ - int ret; - int i; - const struct gasket_driver_desc *driver_desc; - - driver_desc = gasket_dev->internal_desc->driver_desc; - if (!driver_desc->device_reset_cb) - return 0; - - ret = driver_desc->device_reset_cb(gasket_dev); - if (ret) { - dev_dbg(gasket_dev->dev, "Device reset cb returned %d.\n", - ret); - return ret; - } - - /* Reinitialize the page tables and interrupt framework. */ - for (i = 0; i < driver_desc->num_page_tables; ++i) - gasket_page_table_reset(gasket_dev->page_table[i]); - - ret = gasket_interrupt_reinit(gasket_dev); - if (ret) { - dev_dbg(gasket_dev->dev, "Unable to reinit interrupts: %d.\n", - ret); - return ret; - } - - /* Get current device health. */ - gasket_dev->status = gasket_get_hw_status(gasket_dev); - if (gasket_dev->status == GASKET_STATUS_DEAD) { - dev_dbg(gasket_dev->dev, "Device reported as dead.\n"); - return -EINVAL; - } - - return 0; -} -EXPORT_SYMBOL(gasket_reset_nolock); - -gasket_ioctl_permissions_cb_t -gasket_get_ioctl_permissions_cb(struct gasket_dev *gasket_dev) -{ - return gasket_dev->internal_desc->driver_desc->ioctl_permissions_cb; -} -EXPORT_SYMBOL(gasket_get_ioctl_permissions_cb); - -/* Get the driver structure for a given gasket_dev. - * @dev: pointer to gasket_dev, implementing the requested driver. - */ -const struct gasket_driver_desc *gasket_get_driver_desc(struct gasket_dev *dev) -{ - return dev->internal_desc->driver_desc; -} - -/* Get the device structure for a given gasket_dev. - * @dev: pointer to gasket_dev, implementing the requested driver. - */ -struct device *gasket_get_device(struct gasket_dev *dev) -{ - return dev->dev; -} - -/** - * Asynchronously waits on device. - * @gasket_dev: Device struct. - * @bar: Bar - * @offset: Register offset - * @mask: Register mask - * @val: Expected value - * @max_retries: number of sleep periods - * @delay_ms: Timeout in milliseconds - * - * Description: Busy waits for a specific combination of bits to be set on a - * Gasket register. - **/ -int gasket_wait_with_reschedule(struct gasket_dev *gasket_dev, int bar, - u64 offset, u64 mask, u64 val, - uint max_retries, u64 delay_ms) -{ - uint retries = 0; - u64 tmp; - - while (retries < max_retries) { - tmp = gasket_dev_read_64(gasket_dev, bar, offset); - if ((tmp & mask) == val) - return 0; - msleep(delay_ms); - retries++; - } - dev_dbg(gasket_dev->dev, "%s timeout: reg %llx timeout (%llu ms)\n", - __func__, offset, max_retries * delay_ms); - return -ETIMEDOUT; -} -EXPORT_SYMBOL(gasket_wait_with_reschedule); - -/* See gasket_core.h for description. */ -int gasket_register_device(const struct gasket_driver_desc *driver_desc) -{ - int i, ret; - int desc_idx = -1; - struct gasket_internal_desc *internal; - - pr_debug("Loading %s driver version %s\n", driver_desc->name, - driver_desc->driver_version); - /* Check for duplicates and find a free slot. */ - mutex_lock(&g_mutex); - - for (i = 0; i < GASKET_FRAMEWORK_DESC_MAX; i++) { - if (g_descs[i].driver_desc == driver_desc) { - pr_err("%s driver already loaded/registered\n", - driver_desc->name); - mutex_unlock(&g_mutex); - return -EBUSY; - } - } - - /* This and the above loop could be combined, but this reads easier. */ - for (i = 0; i < GASKET_FRAMEWORK_DESC_MAX; i++) { - if (!g_descs[i].driver_desc) { - g_descs[i].driver_desc = driver_desc; - desc_idx = i; - break; - } - } - mutex_unlock(&g_mutex); - - if (desc_idx == -1) { - pr_err("too many drivers loaded, max %d\n", - GASKET_FRAMEWORK_DESC_MAX); - return -EBUSY; - } - - internal = &g_descs[desc_idx]; - mutex_init(&internal->mutex); - memset(internal->devs, 0, sizeof(struct gasket_dev *) * GASKET_DEV_MAX); - internal->class = - class_create(driver_desc->module, driver_desc->name); - - if (IS_ERR(internal->class)) { - pr_err("Cannot register %s class [ret=%ld]\n", - driver_desc->name, PTR_ERR(internal->class)); - ret = PTR_ERR(internal->class); - goto unregister_gasket_driver; - } - - ret = register_chrdev_region(MKDEV(driver_desc->major, - driver_desc->minor), GASKET_DEV_MAX, - driver_desc->name); - if (ret) { - pr_err("cannot register %s char driver [ret=%d]\n", - driver_desc->name, ret); - goto destroy_class; - } - - return 0; - -destroy_class: - class_destroy(internal->class); - -unregister_gasket_driver: - mutex_lock(&g_mutex); - g_descs[desc_idx].driver_desc = NULL; - mutex_unlock(&g_mutex); - return ret; -} -EXPORT_SYMBOL(gasket_register_device); - -/* See gasket_core.h for description. */ -void gasket_unregister_device(const struct gasket_driver_desc *driver_desc) -{ - int i, desc_idx; - struct gasket_internal_desc *internal_desc = NULL; - - mutex_lock(&g_mutex); - for (i = 0; i < GASKET_FRAMEWORK_DESC_MAX; i++) { - if (g_descs[i].driver_desc == driver_desc) { - internal_desc = &g_descs[i]; - desc_idx = i; - break; - } - } - - if (!internal_desc) { - mutex_unlock(&g_mutex); - pr_err("request to unregister unknown desc: %s, %d:%d\n", - driver_desc->name, driver_desc->major, - driver_desc->minor); - return; - } - - unregister_chrdev_region(MKDEV(driver_desc->major, driver_desc->minor), - GASKET_DEV_MAX); - - class_destroy(internal_desc->class); - - /* Finally, effectively "remove" the driver. */ - g_descs[desc_idx].driver_desc = NULL; - mutex_unlock(&g_mutex); - - pr_debug("removed %s driver\n", driver_desc->name); -} -EXPORT_SYMBOL(gasket_unregister_device); - -static int __init gasket_init(void) -{ - int i; - - mutex_lock(&g_mutex); - for (i = 0; i < GASKET_FRAMEWORK_DESC_MAX; i++) { - g_descs[i].driver_desc = NULL; - mutex_init(&g_descs[i].mutex); - } - - gasket_sysfs_init(); - - mutex_unlock(&g_mutex); - return 0; -} - -MODULE_DESCRIPTION("Google Gasket driver framework"); -MODULE_VERSION(GASKET_FRAMEWORK_VERSION); -MODULE_LICENSE("GPL v2"); -MODULE_AUTHOR("Rob Springer <rspringer@google.com>"); -module_init(gasket_init); |