mirror of
https://github.com/armbian/linux-cix.git
synced 2026-01-06 12:30:45 -08:00
drivers/staging: Gasket driver framework + Apex driver
The Gasket (Google ASIC Software, Kernel Extensions, and Tools) kernel framework is a generic, flexible system that supports thin kernel drivers. Gasket kernel drivers are expected to handle opening and closing devices, mmap'ing BAR space as requested, a small selection of ioctls, and handling page table translation (covered below). Any other functions should be handled by userspace code. The Gasket common module is not enough to run a device. In order to customize the Gasket code for a given piece of hardware, a device specific module must be created. At a minimum, this module must define a struct gasket_driver_desc containing the device-specific data for use by the framework; in addition, the module must declare an __init function that calls gasket_register_device with the module's gasket_driver_desc struct. Finally, the driver must define an exit function that calls gasket_unregister_device with the module's gasket_driver_desc struct. One of the core assumptions of the Gasket framework is that precisely one process is allowed to have an open write handle to the device node at any given time. (That process may, once it has one write handle, open any number of additional write handles.) This is accomplished by tracking open and close data for each driver instance. Signed-off-by: Rob Springer <rspringer@google.com> Signed-off-by: John Joseph <jnjoseph@google.com> Signed-off-by: Simon Que <sque@chromium.org> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
committed by
Greg Kroah-Hartman
parent
ee55fe552f
commit
9a69f5087c
@@ -5928,6 +5928,13 @@ F: scripts/gcc-plugin.sh
|
||||
F: scripts/Makefile.gcc-plugins
|
||||
F: Documentation/gcc-plugins.txt
|
||||
|
||||
GASKET DRIVER FRAMEWORK
|
||||
M: Rob Springer <rspringer@google.com>
|
||||
M: John Joseph <jnjoseph@google.com>
|
||||
M: Ben Chan <benchan@chromium.org>
|
||||
S: Maintained
|
||||
F: drivers/staging/gasket/
|
||||
|
||||
GCOV BASED KERNEL PROFILING
|
||||
M: Peter Oberparleiter <oberpar@linux.ibm.com>
|
||||
S: Maintained
|
||||
|
||||
@@ -124,4 +124,6 @@ source "drivers/staging/mt7621-eth/Kconfig"
|
||||
|
||||
source "drivers/staging/mt7621-dts/Kconfig"
|
||||
|
||||
source "drivers/staging/gasket/Kconfig"
|
||||
|
||||
endif # STAGING
|
||||
|
||||
@@ -53,3 +53,4 @@ obj-$(CONFIG_SOC_MT7621) += mt7621-dma/
|
||||
obj-$(CONFIG_SOC_MT7621) += mt7621-mmc/
|
||||
obj-$(CONFIG_SOC_MT7621) += mt7621-eth/
|
||||
obj-$(CONFIG_SOC_MT7621) += mt7621-dts/
|
||||
obj-$(CONFIG_STAGING_GASKET_FRAMEWORK) += gasket/
|
||||
|
||||
23
drivers/staging/gasket/Kconfig
Normal file
23
drivers/staging/gasket/Kconfig
Normal file
@@ -0,0 +1,23 @@
|
||||
menu "Gasket devices"
|
||||
|
||||
config STAGING_GASKET_FRAMEWORK
|
||||
tristate "Gasket framework"
|
||||
depends on PCI && X86_64
|
||||
help
|
||||
This framework supports Gasket-compatible devices, such as Apex.
|
||||
It is required for any of the following module(s).
|
||||
|
||||
To compile this driver as a module, choose M here. The module
|
||||
will be called "gasket".
|
||||
|
||||
config STAGING_APEX_DRIVER
|
||||
tristate "Apex Driver"
|
||||
depends on STAGING_GASKET_FRAMEWORK
|
||||
help
|
||||
This driver supports the Apex device. Say Y if you want to
|
||||
include this driver in the kernel.
|
||||
|
||||
To compile this driver as a module, choose M here. The module
|
||||
will be called "apex".
|
||||
|
||||
endmenu
|
||||
9
drivers/staging/gasket/Makefile
Normal file
9
drivers/staging/gasket/Makefile
Normal file
@@ -0,0 +1,9 @@
|
||||
#
|
||||
# Makefile for Gasket framework and dependent drivers.
|
||||
#
|
||||
|
||||
obj-$(CONFIG_STAGING_GASKET_FRAMEWORK) += gasket.o
|
||||
obj-$(CONFIG_STAGING_APEX_DRIVER) += apex.o
|
||||
|
||||
gasket-objs := gasket_core.o gasket_ioctl.o gasket_interrupt.o gasket_page_table.o gasket_sysfs.o
|
||||
apex-objs := apex_driver.o
|
||||
17
drivers/staging/gasket/TODO
Normal file
17
drivers/staging/gasket/TODO
Normal file
@@ -0,0 +1,17 @@
|
||||
This is a list of things that need to be done to get this driver out of the
|
||||
staging directory.
|
||||
- Use SPDX tags to show the license of the file, and no more "boiler-plate"
|
||||
license text is needed.
|
||||
- Remove static function declarations.
|
||||
- Document sysfs files with Documentation/ABI/ entries.
|
||||
- Use misc interface instead of major number for driver version description.
|
||||
- Add descriptions of module_param's
|
||||
- Remove gasket-specific logging functions.
|
||||
- apex_get_status() should actually check status.
|
||||
- Static functions don't need kernel doc formatting, can be simplified.
|
||||
- Fix multi-line alignment formatting to look like:
|
||||
int ret = long_function_name(device, VARIABLE1, VARIABLE2,
|
||||
VARIABLE3, VARIABLE4);
|
||||
- "drivers" should never be dealing with "raw" sysfs calls or mess around with
|
||||
kobjects at all. The driver core should handle all of this for you
|
||||
automaically. There should not be a need for raw attribute macros.
|
||||
95
drivers/staging/gasket/apex.h
Normal file
95
drivers/staging/gasket/apex.h
Normal file
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Apex kernel-userspace interface definition(s).
|
||||
*
|
||||
* Copyright (C) 2018 Google, Inc.
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
#ifndef __APEX_H__
|
||||
#define __APEX_H__
|
||||
|
||||
#include <linux/ioctl.h>
|
||||
#include <linux/bitops.h>
|
||||
|
||||
#include "gasket.h"
|
||||
|
||||
/* Structural definitions/macros. */
|
||||
/* The number of PCI BARs. */
|
||||
#define APEX_NUM_BARS 3
|
||||
|
||||
/* Size of a memory page in bytes, and the related number of bits to shift. */
|
||||
#define APEX_PAGE_SHIFT 12
|
||||
#define APEX_PAGE_SIZE BIT(APEX_PAGE_SHIFT)
|
||||
|
||||
#define APEX_EXTENDED_SHIFT 63 /* Extended address bit position. */
|
||||
|
||||
/* Addresses are 2^3=8 bytes each. */
|
||||
/* page in second level page table */
|
||||
/* holds APEX_PAGE_SIZE/8 addresses */
|
||||
#define APEX_ADDR_SHIFT 3
|
||||
#define APEX_LEVEL_SHIFT (APEX_PAGE_SHIFT - APEX_ADDR_SHIFT)
|
||||
#define APEX_LEVEL_SIZE BIT(APEX_LEVEL_SHIFT)
|
||||
|
||||
#define APEX_PAGE_TABLE_MAX 65536
|
||||
#define APEX_SIMPLE_PAGE_MAX APEX_PAGE_TABLE_MAX
|
||||
#define APEX_EXTENDED_PAGE_MAX (APEX_PAGE_TABLE_MAX << APEX_LEVEL_SHIFT)
|
||||
|
||||
/* Check reset 120 times */
|
||||
#define APEX_RESET_RETRY 120
|
||||
/* Wait 100 ms between checks. Total 12 sec wait maximum. */
|
||||
#define APEX_RESET_DELAY 100
|
||||
|
||||
#define APEX_CHIP_INIT_DONE 2
|
||||
#define APEX_RESET_ACCEPTED 0
|
||||
|
||||
enum apex_reset_types {
|
||||
APEX_CHIP_REINIT_RESET = 3,
|
||||
};
|
||||
|
||||
/* Interrupt defines */
|
||||
/* Gasket device interrupts enums must be dense (i.e., no empty slots). */
|
||||
enum apex_interrupt {
|
||||
APEX_INTERRUPT_INSTR_QUEUE = 0,
|
||||
APEX_INTERRUPT_INPUT_ACTV_QUEUE = 1,
|
||||
APEX_INTERRUPT_PARAM_QUEUE = 2,
|
||||
APEX_INTERRUPT_OUTPUT_ACTV_QUEUE = 3,
|
||||
APEX_INTERRUPT_SC_HOST_0 = 4,
|
||||
APEX_INTERRUPT_SC_HOST_1 = 5,
|
||||
APEX_INTERRUPT_SC_HOST_2 = 6,
|
||||
APEX_INTERRUPT_SC_HOST_3 = 7,
|
||||
APEX_INTERRUPT_TOP_LEVEL_0 = 8,
|
||||
APEX_INTERRUPT_TOP_LEVEL_1 = 9,
|
||||
APEX_INTERRUPT_TOP_LEVEL_2 = 10,
|
||||
APEX_INTERRUPT_TOP_LEVEL_3 = 11,
|
||||
APEX_INTERRUPT_FATAL_ERR = 12,
|
||||
APEX_INTERRUPT_COUNT = 13,
|
||||
};
|
||||
|
||||
/*
|
||||
* Clock Gating ioctl.
|
||||
*/
|
||||
struct apex_gate_clock_ioctl {
|
||||
/* Enter or leave clock gated state. */
|
||||
u64 enable;
|
||||
|
||||
/* If set, enter clock gating state, regardless of custom block's
|
||||
* internal idle state
|
||||
*/
|
||||
u64 force_idle;
|
||||
};
|
||||
|
||||
/* Base number for all Apex-common IOCTLs */
|
||||
#define APEX_IOCTL_BASE 0x7F
|
||||
|
||||
/* Enable/Disable clock gating. */
|
||||
#define APEX_IOCTL_GATE_CLOCK \
|
||||
_IOW(APEX_IOCTL_BASE, 0, struct apex_gate_clock_ioctl)
|
||||
|
||||
#endif /* __APEX_H__ */
|
||||
771
drivers/staging/gasket/apex_driver.c
Normal file
771
drivers/staging/gasket/apex_driver.c
Normal file
File diff suppressed because it is too large
Load Diff
129
drivers/staging/gasket/gasket.h
Normal file
129
drivers/staging/gasket/gasket.h
Normal file
@@ -0,0 +1,129 @@
|
||||
/* Common Gasket device kernel and user space declarations.
|
||||
*
|
||||
* Copyright (C) 2018 Google, Inc.
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
#ifndef __GASKET_H__
|
||||
#define __GASKET_H__
|
||||
|
||||
#include <linux/ioctl.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
/* ioctl structure declarations */
|
||||
|
||||
/* Ioctl structures are padded to a multiple of 64 bits */
|
||||
/* and padded to put 64 bit values on 64 bit boundaries. */
|
||||
/* Unsigned 64 bit integers are used to hold pointers. */
|
||||
/* This helps compatibility between 32 and 64 bits. */
|
||||
|
||||
/*
|
||||
* Common structure for ioctls associating an eventfd with a device interrupt,
|
||||
* when using the Gasket interrupt module.
|
||||
*/
|
||||
struct gasket_interrupt_eventfd {
|
||||
u64 interrupt;
|
||||
u64 event_fd;
|
||||
};
|
||||
|
||||
/*
|
||||
* Common structure for ioctls mapping and unmapping buffers when using the
|
||||
* Gasket page_table module.
|
||||
*/
|
||||
struct gasket_page_table_ioctl {
|
||||
u64 page_table_index;
|
||||
u64 size;
|
||||
u64 host_address;
|
||||
u64 device_address;
|
||||
};
|
||||
|
||||
/*
|
||||
* Common structure for ioctls mapping and unmapping buffers when using the
|
||||
* Gasket page_table module.
|
||||
* dma_address: phys addr start of coherent memory, allocated by kernel
|
||||
*/
|
||||
struct gasket_coherent_alloc_config_ioctl {
|
||||
u64 page_table_index;
|
||||
u64 enable;
|
||||
u64 size;
|
||||
u64 dma_address;
|
||||
};
|
||||
|
||||
/* Base number for all Gasket-common IOCTLs */
|
||||
#define GASKET_IOCTL_BASE 0xDC
|
||||
|
||||
/* Reset the device using the specified reset type. */
|
||||
#define GASKET_IOCTL_RESET _IOW(GASKET_IOCTL_BASE, 0, unsigned long)
|
||||
|
||||
/* Associate the specified [event]fd with the specified interrupt. */
|
||||
#define GASKET_IOCTL_SET_EVENTFD \
|
||||
_IOW(GASKET_IOCTL_BASE, 1, struct gasket_interrupt_eventfd)
|
||||
|
||||
/*
|
||||
* Clears any eventfd associated with the specified interrupt. The (ulong)
|
||||
* argument is the interrupt number to clear.
|
||||
*/
|
||||
#define GASKET_IOCTL_CLEAR_EVENTFD _IOW(GASKET_IOCTL_BASE, 2, unsigned long)
|
||||
|
||||
/*
|
||||
* [Loopbacks only] Requests that the loopback device send the specified
|
||||
* interrupt to the host. The (ulong) argument is the number of the interrupt to
|
||||
* send.
|
||||
*/
|
||||
#define GASKET_IOCTL_LOOPBACK_INTERRUPT \
|
||||
_IOW(GASKET_IOCTL_BASE, 3, unsigned long)
|
||||
|
||||
/* Queries the kernel for the number of page tables supported by the device. */
|
||||
#define GASKET_IOCTL_NUMBER_PAGE_TABLES _IOR(GASKET_IOCTL_BASE, 4, u64)
|
||||
|
||||
/*
|
||||
* Queries the kernel for the maximum size of the page table. Only the size and
|
||||
* page_table_index fields are used from the struct gasket_page_table_ioctl.
|
||||
*/
|
||||
#define GASKET_IOCTL_PAGE_TABLE_SIZE \
|
||||
_IOWR(GASKET_IOCTL_BASE, 5, struct gasket_page_table_ioctl)
|
||||
|
||||
/*
|
||||
* Queries the kernel for the current simple page table size. Only the size and
|
||||
* page_table_index fields are used from the struct gasket_page_table_ioctl.
|
||||
*/
|
||||
#define GASKET_IOCTL_SIMPLE_PAGE_TABLE_SIZE \
|
||||
_IOWR(GASKET_IOCTL_BASE, 6, struct gasket_page_table_ioctl)
|
||||
|
||||
/*
|
||||
* Tells the kernel to change the split between the number of simple and
|
||||
* extended entries in the given page table. Only the size and page_table_index
|
||||
* fields are used from the struct gasket_page_table_ioctl.
|
||||
*/
|
||||
#define GASKET_IOCTL_PARTITION_PAGE_TABLE \
|
||||
_IOW(GASKET_IOCTL_BASE, 7, struct gasket_page_table_ioctl)
|
||||
|
||||
/*
|
||||
* Tells the kernel to map size bytes at host_address to device_address in
|
||||
* page_table_index page table.
|
||||
*/
|
||||
#define GASKET_IOCTL_MAP_BUFFER \
|
||||
_IOW(GASKET_IOCTL_BASE, 8, struct gasket_page_table_ioctl)
|
||||
|
||||
/*
|
||||
* Tells the kernel to unmap size bytes at host_address from device_address in
|
||||
* page_table_index page table.
|
||||
*/
|
||||
#define GASKET_IOCTL_UNMAP_BUFFER \
|
||||
_IOW(GASKET_IOCTL_BASE, 9, struct gasket_page_table_ioctl)
|
||||
|
||||
/* Clear the interrupt counts stored for this device. */
|
||||
#define GASKET_IOCTL_CLEAR_INTERRUPT_COUNTS _IO(GASKET_IOCTL_BASE, 10)
|
||||
|
||||
/* Enable/Disable and configure the coherent allocator. */
|
||||
#define GASKET_IOCTL_CONFIG_COHERENT_ALLOCATOR \
|
||||
_IOWR(GASKET_IOCTL_BASE, 11, struct gasket_coherent_alloc_config_ioctl)
|
||||
|
||||
#endif /* __GASKET_H__ */
|
||||
56
drivers/staging/gasket/gasket_constants.h
Normal file
56
drivers/staging/gasket/gasket_constants.h
Normal file
@@ -0,0 +1,56 @@
|
||||
/* Copyright (C) 2018 Google, Inc.
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
#ifndef __GASKET_CONSTANTS_H__
|
||||
#define __GASKET_CONSTANTS_H__
|
||||
|
||||
#define GASKET_FRAMEWORK_VERSION "1.1.1"
|
||||
|
||||
/*
|
||||
* The maximum number of simultaneous device types supported by the framework.
|
||||
*/
|
||||
#define GASKET_FRAMEWORK_DESC_MAX 2
|
||||
|
||||
/* The maximum devices per each type. */
|
||||
#define GASKET_DEV_MAX 256
|
||||
|
||||
/* The number of supported (and possible) PCI BARs. */
|
||||
#define GASKET_NUM_BARS 6
|
||||
|
||||
/* The number of supported Gasket page tables per device. */
|
||||
#define GASKET_MAX_NUM_PAGE_TABLES 1
|
||||
|
||||
/* Maximum length of device names (driver name + minor number suffix + NULL). */
|
||||
#define GASKET_NAME_MAX 32
|
||||
|
||||
/* Device status enumeration. */
|
||||
enum gasket_status {
|
||||
/*
|
||||
* A device is DEAD if it has not been initialized or has had an error.
|
||||
*/
|
||||
GASKET_STATUS_DEAD = 0,
|
||||
/*
|
||||
* A device is LAMED if the hardware is healthy but the kernel was
|
||||
* unable to enable some functionality (e.g. interrupts).
|
||||
*/
|
||||
GASKET_STATUS_LAMED,
|
||||
|
||||
/* A device is ALIVE if it is ready for operation. */
|
||||
GASKET_STATUS_ALIVE,
|
||||
|
||||
/*
|
||||
* This status is set when the driver is exiting and waiting for all
|
||||
* handles to be closed.
|
||||
*/
|
||||
GASKET_STATUS_DRIVER_EXIT,
|
||||
};
|
||||
|
||||
#endif
|
||||
2157
drivers/staging/gasket/gasket_core.c
Normal file
2157
drivers/staging/gasket/gasket_core.c
Normal file
File diff suppressed because it is too large
Load Diff
719
drivers/staging/gasket/gasket_core.h
Normal file
719
drivers/staging/gasket/gasket_core.h
Normal file
File diff suppressed because it is too large
Load Diff
638
drivers/staging/gasket/gasket_interrupt.c
Normal file
638
drivers/staging/gasket/gasket_interrupt.c
Normal file
File diff suppressed because it is too large
Load Diff
172
drivers/staging/gasket/gasket_interrupt.h
Normal file
172
drivers/staging/gasket/gasket_interrupt.h
Normal file
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* Gasket common interrupt module. Defines functions for enabling
|
||||
* eventfd-triggered interrupts between a Gasket device and a host process.
|
||||
*
|
||||
* Copyright (C) 2018 Google, Inc.
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
#ifndef __GASKET_INTERRUPT_H__
|
||||
#define __GASKET_INTERRUPT_H__
|
||||
|
||||
#include <linux/eventfd.h>
|
||||
#include <linux/pci.h>
|
||||
|
||||
#include "gasket_core.h"
|
||||
|
||||
/* Note that this currently assumes that device interrupts are a dense set,
|
||||
* numbered from 0 - (num_interrupts - 1). Should this have to change, these
|
||||
* APIs will have to be updated.
|
||||
*/
|
||||
|
||||
/* Opaque type used to hold interrupt subsystem data. */
|
||||
struct gasket_interrupt_data;
|
||||
|
||||
/*
|
||||
* Initialize the interrupt module.
|
||||
* @gasket_dev: The Gasket device structure for the device to be initted.
|
||||
* @type: Type of the interrupt. (See gasket_interrupt_type).
|
||||
* @name: The name to associate with these interrupts.
|
||||
* @interrupts: An array of all interrupt descriptions for this device.
|
||||
* @num_interrupts: The length of the @interrupts array.
|
||||
* @pack_width: The width, in bits, of a single field in a packed interrupt reg.
|
||||
* @bar_index: The bar containing all interrupt registers.
|
||||
*
|
||||
* Allocates and initializes data to track interrupt state for a device.
|
||||
* After this call, no interrupts will be configured/delivered; call
|
||||
* gasket_interrupt_set_vector[_packed] to associate each interrupt with an
|
||||
* __iomem location, then gasket_interrupt_set_eventfd to associate an eventfd
|
||||
* with an interrupt.
|
||||
*
|
||||
* If num_interrupts interrupts are not available, this call will return a
|
||||
* negative error code. In that case, gasket_interrupt_cleanup should still be
|
||||
* called. Returns 0 on success (which can include a device where interrupts
|
||||
* are not possible to set up, but is otherwise OK; that device will report
|
||||
* status LAMED.)
|
||||
*/
|
||||
int gasket_interrupt_init(
|
||||
struct gasket_dev *gasket_dev, const char *name, int type,
|
||||
const struct gasket_interrupt_desc *interrupts,
|
||||
int num_interrupts, int pack_width, int bar_index,
|
||||
const struct gasket_wire_interrupt_offsets *wire_int_offsets);
|
||||
|
||||
/*
|
||||
* Clean up a device's interrupt structure.
|
||||
* @gasket_dev: The Gasket information structure for this device.
|
||||
*
|
||||
* Cleans up the device's interrupts and deallocates data.
|
||||
*/
|
||||
void gasket_interrupt_cleanup(struct gasket_dev *gasket_dev);
|
||||
|
||||
/*
|
||||
* Clean up and re-initialize the MSI-x subsystem.
|
||||
* @gasket_dev: The Gasket information structure for this device.
|
||||
*
|
||||
* Performs a teardown of the MSI-x subsystem and re-initializes it. Does not
|
||||
* free the underlying data structures. Returns 0 on success and an error code
|
||||
* on error.
|
||||
*/
|
||||
int gasket_interrupt_reinit(struct gasket_dev *gasket_dev);
|
||||
|
||||
/*
|
||||
* Reset the counts stored in the interrupt subsystem.
|
||||
* @gasket_dev: The Gasket information structure for this device.
|
||||
*
|
||||
* Sets the counts of all interrupts in the subsystem to 0.
|
||||
*/
|
||||
int gasket_interrupt_reset_counts(struct gasket_dev *gasket_dev);
|
||||
|
||||
/*
|
||||
* Associates an eventfd with a device interrupt.
|
||||
* @data: Pointer to device interrupt data.
|
||||
* @interrupt: The device interrupt to configure.
|
||||
* @event_fd: The eventfd to associate with the interrupt.
|
||||
*
|
||||
* Prepares the host to receive notification of device interrupts by associating
|
||||
* event_fd with interrupt. Upon receipt of a device interrupt, event_fd will be
|
||||
* signaled, after successful configuration.
|
||||
*
|
||||
* Returns 0 on success, a negative error code otherwise.
|
||||
*/
|
||||
int gasket_interrupt_set_eventfd(
|
||||
struct gasket_interrupt_data *interrupt_data, int interrupt,
|
||||
int event_fd);
|
||||
|
||||
/*
|
||||
* Removes an interrupt-eventfd association.
|
||||
* @data: Pointer to device interrupt data.
|
||||
* @interrupt: The device interrupt to de-associate.
|
||||
*
|
||||
* Removes any eventfd associated with the specified interrupt, if any.
|
||||
*/
|
||||
int gasket_interrupt_clear_eventfd(
|
||||
struct gasket_interrupt_data *interrupt_data, int interrupt);
|
||||
|
||||
/*
|
||||
* Signals the eventfd associated with interrupt.
|
||||
* @data: Pointer to device interrupt data.
|
||||
* @interrupt: The device interrupt to signal for.
|
||||
*
|
||||
* Simulates a device interrupt by signaling the eventfd associated with
|
||||
* interrupt, if any.
|
||||
* Returns 0 if the eventfd was successfully triggered, a negative error code
|
||||
* otherwise (if, for example, no eventfd was associated with interrupt).
|
||||
*/
|
||||
int gasket_interrupt_trigger_eventfd(
|
||||
struct gasket_interrupt_data *interrupt_data, int interrupt);
|
||||
|
||||
/*
|
||||
* The below functions exist for backwards compatibility.
|
||||
* No new uses should be written.
|
||||
*/
|
||||
/*
|
||||
* Retrieve a pointer to data's MSI-X
|
||||
* entries.
|
||||
* @data: The interrupt data from which to extract.
|
||||
*
|
||||
* Returns the internal pointer to data's MSI-X entries.
|
||||
*/
|
||||
struct msix_entry *gasket_interrupt_get_msix_entries(
|
||||
struct gasket_interrupt_data *interrupt_data);
|
||||
|
||||
/*
|
||||
* Get a pointer to data's eventfd contexts.
|
||||
* @data: The interrupt data from which to extract.
|
||||
*
|
||||
* Returns the internal pointer to data's eventfd contexts.
|
||||
*
|
||||
* This function exists for backwards compatibility with older drivers.
|
||||
* No new uses should be written.
|
||||
*/
|
||||
struct eventfd_ctx **gasket_interrupt_get_eventfd_ctxs(
|
||||
struct gasket_interrupt_data *interrupt_data);
|
||||
|
||||
/*
|
||||
* Get the health of the interrupt subsystem.
|
||||
* @gasket_dev: The Gasket device struct.
|
||||
*
|
||||
* Returns DEAD if not set up, LAMED if initialization failed, and ALIVE
|
||||
* otherwise.
|
||||
*/
|
||||
|
||||
int gasket_interrupt_system_status(struct gasket_dev *gasket_dev);
|
||||
|
||||
/*
|
||||
* Masks interrupts and de-register the handler.
|
||||
* After an interrupt pause it is not guaranteed that the chip registers will
|
||||
* be accessible anymore, since the chip may be in a power save mode,
|
||||
* which means that the interrupt handler (if it were to happen) may not
|
||||
* have a way to clear the interrupt condition.
|
||||
* @gasket_dev: The Gasket device struct
|
||||
* @enable_pause: Whether to pause or unpause the interrupts.
|
||||
*/
|
||||
void gasket_interrupt_pause(struct gasket_dev *gasket_dev, int enable_pause);
|
||||
|
||||
#endif
|
||||
449
drivers/staging/gasket/gasket_ioctl.c
Normal file
449
drivers/staging/gasket/gasket_ioctl.c
Normal file
@@ -0,0 +1,449 @@
|
||||
/* Copyright (C) 2018 Google, Inc.
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
#include "gasket.h"
|
||||
#include "gasket_ioctl.h"
|
||||
#include "gasket_constants.h"
|
||||
#include "gasket_core.h"
|
||||
#include "gasket_interrupt.h"
|
||||
#include "gasket_logging.h"
|
||||
#include "gasket_page_table.h"
|
||||
#include <linux/fs.h>
|
||||
#include <linux/uaccess.h>
|
||||
|
||||
#ifdef GASKET_KERNEL_TRACE_SUPPORT
|
||||
#define CREATE_TRACE_POINTS
|
||||
#include <trace/events/gasket_ioctl.h>
|
||||
#else
|
||||
#define trace_gasket_ioctl_entry(x, ...)
|
||||
#define trace_gasket_ioctl_exit(x)
|
||||
#define trace_gasket_ioctl_integer_data(x)
|
||||
#define trace_gasket_ioctl_eventfd_data(x, ...)
|
||||
#define trace_gasket_ioctl_page_table_data(x, ...)
|
||||
#define trace_gasket_ioctl_config_coherent_allocator(x, ...)
|
||||
#endif
|
||||
|
||||
static uint gasket_ioctl_check_permissions(struct file *filp, uint cmd);
|
||||
static int gasket_set_event_fd(struct gasket_dev *dev, ulong arg);
|
||||
static int gasket_read_page_table_size(
|
||||
struct gasket_dev *gasket_dev, ulong arg);
|
||||
static int gasket_read_simple_page_table_size(
|
||||
struct gasket_dev *gasket_dev, ulong arg);
|
||||
static int gasket_partition_page_table(
|
||||
struct gasket_dev *gasket_dev, ulong arg);
|
||||
static int gasket_map_buffers(struct gasket_dev *gasket_dev, ulong arg);
|
||||
static int gasket_unmap_buffers(struct gasket_dev *gasket_dev, ulong arg);
|
||||
static int gasket_config_coherent_allocator(
|
||||
struct gasket_dev *gasket_dev, ulong arg);
|
||||
|
||||
/*
|
||||
* standard ioctl dispatch function.
|
||||
* @filp: File structure pointer describing this node usage session.
|
||||
* @cmd: ioctl number to handle.
|
||||
* @arg: ioctl-specific data pointer.
|
||||
*
|
||||
* Standard ioctl dispatcher; forwards operations to individual handlers.
|
||||
*/
|
||||
long gasket_handle_ioctl(struct file *filp, uint cmd, ulong arg)
|
||||
{
|
||||
struct gasket_dev *gasket_dev;
|
||||
int retval;
|
||||
|
||||
gasket_dev = (struct gasket_dev *)filp->private_data;
|
||||
trace_gasket_ioctl_entry(gasket_dev->dev_info.name, cmd);
|
||||
|
||||
if (gasket_get_ioctl_permissions_cb(gasket_dev)) {
|
||||
retval = gasket_get_ioctl_permissions_cb(gasket_dev)(
|
||||
filp, cmd, arg);
|
||||
if (retval < 0) {
|
||||
trace_gasket_ioctl_exit(-EPERM);
|
||||
return retval;
|
||||
} else if (retval == 0) {
|
||||
trace_gasket_ioctl_exit(-EPERM);
|
||||
return -EPERM;
|
||||
}
|
||||
} else if (!gasket_ioctl_check_permissions(filp, cmd)) {
|
||||
trace_gasket_ioctl_exit(-EPERM);
|
||||
gasket_log_error(gasket_dev, "ioctl cmd=%x noperm.", cmd);
|
||||
return -EPERM;
|
||||
}
|
||||
|
||||
/* Tracing happens in this switch statement for all ioctls with
|
||||
* an integer argrument, but ioctls with a struct argument
|
||||
* that needs copying and decoding, that tracing is done within
|
||||
* the handler call.
|
||||
*/
|
||||
switch (cmd) {
|
||||
case GASKET_IOCTL_RESET:
|
||||
trace_gasket_ioctl_integer_data(arg);
|
||||
retval = gasket_reset(gasket_dev, arg);
|
||||
break;
|
||||
case GASKET_IOCTL_SET_EVENTFD:
|
||||
retval = gasket_set_event_fd(gasket_dev, arg);
|
||||
break;
|
||||
case GASKET_IOCTL_CLEAR_EVENTFD:
|
||||
trace_gasket_ioctl_integer_data(arg);
|
||||
retval = gasket_interrupt_clear_eventfd(
|
||||
gasket_dev->interrupt_data, (int)arg);
|
||||
break;
|
||||
case GASKET_IOCTL_PARTITION_PAGE_TABLE:
|
||||
trace_gasket_ioctl_integer_data(arg);
|
||||
retval = gasket_partition_page_table(gasket_dev, arg);
|
||||
break;
|
||||
case GASKET_IOCTL_NUMBER_PAGE_TABLES:
|
||||
trace_gasket_ioctl_integer_data(gasket_dev->num_page_tables);
|
||||
if (copy_to_user((void __user *)arg,
|
||||
&gasket_dev->num_page_tables,
|
||||
sizeof(uint64_t)))
|
||||
retval = -EFAULT;
|
||||
else
|
||||
retval = 0;
|
||||
break;
|
||||
case GASKET_IOCTL_PAGE_TABLE_SIZE:
|
||||
retval = gasket_read_page_table_size(gasket_dev, arg);
|
||||
break;
|
||||
case GASKET_IOCTL_SIMPLE_PAGE_TABLE_SIZE:
|
||||
retval = gasket_read_simple_page_table_size(gasket_dev, arg);
|
||||
break;
|
||||
case GASKET_IOCTL_MAP_BUFFER:
|
||||
retval = gasket_map_buffers(gasket_dev, arg);
|
||||
break;
|
||||
case GASKET_IOCTL_CONFIG_COHERENT_ALLOCATOR:
|
||||
retval = gasket_config_coherent_allocator(gasket_dev, arg);
|
||||
break;
|
||||
case GASKET_IOCTL_UNMAP_BUFFER:
|
||||
retval = gasket_unmap_buffers(gasket_dev, arg);
|
||||
break;
|
||||
case GASKET_IOCTL_CLEAR_INTERRUPT_COUNTS:
|
||||
/* Clear interrupt counts doesn't take an arg, so use 0. */
|
||||
trace_gasket_ioctl_integer_data(0);
|
||||
retval = gasket_interrupt_reset_counts(gasket_dev);
|
||||
break;
|
||||
default:
|
||||
/* If we don't understand the ioctl, the best we can do is trace
|
||||
* the arg.
|
||||
*/
|
||||
trace_gasket_ioctl_integer_data(arg);
|
||||
gasket_log_warn(
|
||||
gasket_dev,
|
||||
"Unknown ioctl cmd=0x%x not caught by "
|
||||
"gasket_is_supported_ioctl",
|
||||
cmd);
|
||||
retval = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
trace_gasket_ioctl_exit(retval);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*
|
||||
* Determines if an ioctl is part of the standard Gasket framework.
|
||||
* @cmd: The ioctl number to handle.
|
||||
*
|
||||
* Returns 1 if the ioctl is supported and 0 otherwise.
|
||||
*/
|
||||
long gasket_is_supported_ioctl(uint cmd)
|
||||
{
|
||||
switch (cmd) {
|
||||
case GASKET_IOCTL_RESET:
|
||||
case GASKET_IOCTL_SET_EVENTFD:
|
||||
case GASKET_IOCTL_CLEAR_EVENTFD:
|
||||
case GASKET_IOCTL_PARTITION_PAGE_TABLE:
|
||||
case GASKET_IOCTL_NUMBER_PAGE_TABLES:
|
||||
case GASKET_IOCTL_PAGE_TABLE_SIZE:
|
||||
case GASKET_IOCTL_SIMPLE_PAGE_TABLE_SIZE:
|
||||
case GASKET_IOCTL_MAP_BUFFER:
|
||||
case GASKET_IOCTL_UNMAP_BUFFER:
|
||||
case GASKET_IOCTL_CLEAR_INTERRUPT_COUNTS:
|
||||
case GASKET_IOCTL_CONFIG_COHERENT_ALLOCATOR:
|
||||
return 1;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Permission checker for Gasket ioctls.
|
||||
* @filp: File structure pointer describing this node usage session.
|
||||
* @cmd: ioctl number to handle.
|
||||
*
|
||||
* Standard permissions checker.
|
||||
*/
|
||||
static uint gasket_ioctl_check_permissions(struct file *filp, uint cmd)
|
||||
{
|
||||
uint alive, root, device_owner;
|
||||
fmode_t read, write;
|
||||
struct gasket_dev *gasket_dev = (struct gasket_dev *)filp->private_data;
|
||||
|
||||
alive = (gasket_dev->status == GASKET_STATUS_ALIVE);
|
||||
if (!alive) {
|
||||
gasket_nodev_error(
|
||||
"gasket_ioctl_check_permissions alive %d status %d.",
|
||||
alive, gasket_dev->status);
|
||||
}
|
||||
|
||||
root = capable(CAP_SYS_ADMIN);
|
||||
read = filp->f_mode & FMODE_READ;
|
||||
write = filp->f_mode & FMODE_WRITE;
|
||||
device_owner = (gasket_dev->dev_info.ownership.is_owned &&
|
||||
current->tgid == gasket_dev->dev_info.ownership.owner);
|
||||
|
||||
switch (cmd) {
|
||||
case GASKET_IOCTL_RESET:
|
||||
case GASKET_IOCTL_CLEAR_INTERRUPT_COUNTS:
|
||||
return root || (write && device_owner);
|
||||
|
||||
case GASKET_IOCTL_PAGE_TABLE_SIZE:
|
||||
case GASKET_IOCTL_SIMPLE_PAGE_TABLE_SIZE:
|
||||
case GASKET_IOCTL_NUMBER_PAGE_TABLES:
|
||||
return root || read;
|
||||
|
||||
case GASKET_IOCTL_PARTITION_PAGE_TABLE:
|
||||
case GASKET_IOCTL_CONFIG_COHERENT_ALLOCATOR:
|
||||
return alive && (root || (write && device_owner));
|
||||
|
||||
case GASKET_IOCTL_MAP_BUFFER:
|
||||
case GASKET_IOCTL_UNMAP_BUFFER:
|
||||
return alive && (root || (write && device_owner));
|
||||
|
||||
case GASKET_IOCTL_CLEAR_EVENTFD:
|
||||
case GASKET_IOCTL_SET_EVENTFD:
|
||||
return alive && (root || (write && device_owner));
|
||||
}
|
||||
|
||||
return 0; /* unknown permissions */
|
||||
}
|
||||
|
||||
/*
|
||||
* Associate an eventfd with an interrupt.
|
||||
* @gasket_dev: Pointer to the current gasket_dev we're using.
|
||||
* @arg: Pointer to gasket_interrupt_eventfd struct in userspace.
|
||||
*/
|
||||
static int gasket_set_event_fd(struct gasket_dev *gasket_dev, ulong arg)
|
||||
{
|
||||
struct gasket_interrupt_eventfd die;
|
||||
|
||||
if (copy_from_user(&die, (void __user *)arg,
|
||||
sizeof(struct gasket_interrupt_eventfd))) {
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
trace_gasket_ioctl_eventfd_data(die.interrupt, die.event_fd);
|
||||
|
||||
return gasket_interrupt_set_eventfd(
|
||||
gasket_dev->interrupt_data, die.interrupt, die.event_fd);
|
||||
}
|
||||
|
||||
/*
|
||||
* Reads the size of the page table.
|
||||
* @gasket_dev: Pointer to the current gasket_dev we're using.
|
||||
* @arg: Pointer to gasket_page_table_ioctl struct in userspace.
|
||||
*/
|
||||
static int gasket_read_page_table_size(struct gasket_dev *gasket_dev, ulong arg)
|
||||
{
|
||||
int ret = 0;
|
||||
struct gasket_page_table_ioctl ibuf;
|
||||
|
||||
if (copy_from_user(&ibuf, (void __user *)arg,
|
||||
sizeof(struct gasket_page_table_ioctl)))
|
||||
return -EFAULT;
|
||||
|
||||
if (ibuf.page_table_index >= gasket_dev->num_page_tables)
|
||||
return -EFAULT;
|
||||
|
||||
ibuf.size = gasket_page_table_num_entries(
|
||||
gasket_dev->page_table[ibuf.page_table_index]);
|
||||
|
||||
trace_gasket_ioctl_page_table_data(
|
||||
ibuf.page_table_index, ibuf.size, ibuf.host_address,
|
||||
ibuf.device_address);
|
||||
|
||||
if (copy_to_user((void __user *)arg, &ibuf, sizeof(ibuf)))
|
||||
return -EFAULT;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Reads the size of the simple page table.
|
||||
* @gasket_dev: Pointer to the current gasket_dev we're using.
|
||||
* @arg: Pointer to gasket_page_table_ioctl struct in userspace.
|
||||
*/
|
||||
static int gasket_read_simple_page_table_size(
|
||||
struct gasket_dev *gasket_dev, ulong arg)
|
||||
{
|
||||
int ret = 0;
|
||||
struct gasket_page_table_ioctl ibuf;
|
||||
|
||||
if (copy_from_user(&ibuf, (void __user *)arg,
|
||||
sizeof(struct gasket_page_table_ioctl)))
|
||||
return -EFAULT;
|
||||
|
||||
if (ibuf.page_table_index >= gasket_dev->num_page_tables)
|
||||
return -EFAULT;
|
||||
|
||||
ibuf.size = gasket_page_table_num_simple_entries(
|
||||
gasket_dev->page_table[ibuf.page_table_index]);
|
||||
|
||||
trace_gasket_ioctl_page_table_data(
|
||||
ibuf.page_table_index, ibuf.size, ibuf.host_address,
|
||||
ibuf.device_address);
|
||||
|
||||
if (copy_to_user((void __user *)arg, &ibuf, sizeof(ibuf)))
|
||||
return -EFAULT;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Sets the boundary between the simple and extended page tables.
|
||||
* @gasket_dev: Pointer to the current gasket_dev we're using.
|
||||
* @arg: Pointer to gasket_page_table_ioctl struct in userspace.
|
||||
*/
|
||||
static int gasket_partition_page_table(struct gasket_dev *gasket_dev, ulong arg)
|
||||
{
|
||||
int ret;
|
||||
struct gasket_page_table_ioctl ibuf;
|
||||
uint max_page_table_size;
|
||||
|
||||
if (copy_from_user(&ibuf, (void __user *)arg,
|
||||
sizeof(struct gasket_page_table_ioctl)))
|
||||
return -EFAULT;
|
||||
|
||||
trace_gasket_ioctl_page_table_data(
|
||||
ibuf.page_table_index, ibuf.size, ibuf.host_address,
|
||||
ibuf.device_address);
|
||||
|
||||
if (ibuf.page_table_index >= gasket_dev->num_page_tables)
|
||||
return -EFAULT;
|
||||
max_page_table_size = gasket_page_table_max_size(
|
||||
gasket_dev->page_table[ibuf.page_table_index]);
|
||||
|
||||
if (ibuf.size > max_page_table_size) {
|
||||
gasket_log_error(
|
||||
gasket_dev,
|
||||
"Partition request 0x%llx too large, max is 0x%x.",
|
||||
ibuf.size, max_page_table_size);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mutex_lock(&gasket_dev->mutex);
|
||||
|
||||
ret = gasket_page_table_partition(
|
||||
gasket_dev->page_table[ibuf.page_table_index], ibuf.size);
|
||||
mutex_unlock(&gasket_dev->mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Maps a userspace buffer to a device virtual address.
|
||||
* @gasket_dev: Pointer to the current gasket_dev we're using.
|
||||
* @arg: Pointer to a gasket_page_table_ioctl struct in userspace.
|
||||
*/
|
||||
static int gasket_map_buffers(struct gasket_dev *gasket_dev, ulong arg)
|
||||
{
|
||||
struct gasket_page_table_ioctl ibuf;
|
||||
|
||||
if (copy_from_user(&ibuf, (void __user *)arg,
|
||||
sizeof(struct gasket_page_table_ioctl)))
|
||||
return -EFAULT;
|
||||
|
||||
trace_gasket_ioctl_page_table_data(
|
||||
ibuf.page_table_index, ibuf.size, ibuf.host_address,
|
||||
ibuf.device_address);
|
||||
|
||||
if (ibuf.page_table_index >= gasket_dev->num_page_tables)
|
||||
return -EFAULT;
|
||||
|
||||
if (gasket_page_table_are_addrs_bad(
|
||||
gasket_dev->page_table[ibuf.page_table_index],
|
||||
ibuf.host_address, ibuf.device_address, ibuf.size))
|
||||
return -EINVAL;
|
||||
|
||||
return gasket_page_table_map(
|
||||
gasket_dev->page_table[ibuf.page_table_index],
|
||||
ibuf.host_address, ibuf.device_address, ibuf.size / PAGE_SIZE);
|
||||
}
|
||||
|
||||
/*
|
||||
* Unmaps a userspace buffer from a device virtual address.
|
||||
* @gasket_dev: Pointer to the current gasket_dev we're using.
|
||||
* @arg: Pointer to a gasket_page_table_ioctl struct in userspace.
|
||||
*/
|
||||
static int gasket_unmap_buffers(struct gasket_dev *gasket_dev, ulong arg)
|
||||
{
|
||||
struct gasket_page_table_ioctl ibuf;
|
||||
|
||||
if (copy_from_user(&ibuf, (void __user *)arg,
|
||||
sizeof(struct gasket_page_table_ioctl)))
|
||||
return -EFAULT;
|
||||
|
||||
trace_gasket_ioctl_page_table_data(
|
||||
ibuf.page_table_index, ibuf.size, ibuf.host_address,
|
||||
ibuf.device_address);
|
||||
|
||||
if (ibuf.page_table_index >= gasket_dev->num_page_tables)
|
||||
return -EFAULT;
|
||||
|
||||
if (gasket_page_table_is_dev_addr_bad(
|
||||
gasket_dev->page_table[ibuf.page_table_index],
|
||||
ibuf.device_address, ibuf.size))
|
||||
return -EINVAL;
|
||||
|
||||
gasket_page_table_unmap(gasket_dev->page_table[ibuf.page_table_index],
|
||||
ibuf.device_address, ibuf.size / PAGE_SIZE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Tell the driver to reserve structures for coherent allocation, and allocate
|
||||
* or free the corresponding memory.
|
||||
* @dev: Pointer to the current gasket_dev we're using.
|
||||
* @arg: Pointer to a gasket_coherent_alloc_config_ioctl struct in userspace.
|
||||
*/
|
||||
static int gasket_config_coherent_allocator(
|
||||
struct gasket_dev *gasket_dev, ulong arg)
|
||||
{
|
||||
int ret;
|
||||
struct gasket_coherent_alloc_config_ioctl ibuf;
|
||||
|
||||
if (copy_from_user(&ibuf, (void __user *)arg,
|
||||
sizeof(struct gasket_coherent_alloc_config_ioctl)))
|
||||
return -EFAULT;
|
||||
|
||||
trace_gasket_ioctl_config_coherent_allocator(
|
||||
ibuf.enable, ibuf.size, ibuf.dma_address);
|
||||
|
||||
if (ibuf.page_table_index >= gasket_dev->num_page_tables)
|
||||
return -EFAULT;
|
||||
|
||||
if (ibuf.size > PAGE_SIZE * MAX_NUM_COHERENT_PAGES) {
|
||||
ibuf.size = PAGE_SIZE * MAX_NUM_COHERENT_PAGES;
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
if (ibuf.enable == 0) {
|
||||
ret = gasket_free_coherent_memory(
|
||||
gasket_dev, ibuf.size, ibuf.dma_address,
|
||||
ibuf.page_table_index);
|
||||
} else {
|
||||
ret = gasket_alloc_coherent_memory(
|
||||
gasket_dev, ibuf.size, &ibuf.dma_address,
|
||||
ibuf.page_table_index);
|
||||
}
|
||||
if (copy_to_user((void __user *)arg, &ibuf, sizeof(ibuf)))
|
||||
return -EFAULT;
|
||||
|
||||
return ret;
|
||||
}
|
||||
35
drivers/staging/gasket/gasket_ioctl.h
Normal file
35
drivers/staging/gasket/gasket_ioctl.h
Normal file
@@ -0,0 +1,35 @@
|
||||
/* Copyright (C) 2018 Google, Inc.
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
#ifndef __GASKET_IOCTL_H__
|
||||
#define __GASKET_IOCTL_H__
|
||||
|
||||
#include "gasket_core.h"
|
||||
|
||||
/*
|
||||
* Handle Gasket common ioctls.
|
||||
* @filp: Pointer to the ioctl's file.
|
||||
* @cmd: Ioctl command.
|
||||
* @arg: Ioctl argument pointer.
|
||||
*
|
||||
* Returns 0 on success and nonzero on failure.
|
||||
*/
|
||||
long gasket_handle_ioctl(struct file *filp, uint cmd, ulong arg);
|
||||
|
||||
/*
|
||||
* Determines if an ioctl is part of the standard Gasket framework.
|
||||
* @cmd: The ioctl number to handle.
|
||||
*
|
||||
* Returns 1 if the ioctl is supported and 0 otherwise.
|
||||
*/
|
||||
long gasket_is_supported_ioctl(uint cmd);
|
||||
|
||||
#endif
|
||||
71
drivers/staging/gasket/gasket_logging.h
Normal file
71
drivers/staging/gasket/gasket_logging.h
Normal file
@@ -0,0 +1,71 @@
|
||||
/* Common logging utilities for the Gasket driver framework.
|
||||
*
|
||||
* Copyright (C) 2018 Google, Inc.
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/pci.h>
|
||||
#include <linux/printk.h>
|
||||
|
||||
#ifndef _GASKET_LOGGING_H_
|
||||
#define _GASKET_LOGGING_H_
|
||||
|
||||
/* Base macro; other logging can/should be built on top of this. */
|
||||
#define gasket_dev_log(level, device, pci_dev, format, arg...) \
|
||||
if (false) { \
|
||||
if (pci_dev) { \
|
||||
dev_##level(&(pci_dev)->dev, "%s: " format "\n", \
|
||||
__func__, ##arg); \
|
||||
} else { \
|
||||
gasket_nodev_log(level, format, ##arg); \
|
||||
} \
|
||||
}
|
||||
|
||||
/* "No-device" logging macros. */
|
||||
#define gasket_nodev_log(level, format, arg...) \
|
||||
if (false) pr_##level("gasket: %s: " format "\n", __func__, ##arg)
|
||||
|
||||
#define gasket_nodev_debug(format, arg...) \
|
||||
if (false) gasket_nodev_log(debug, format, ##arg)
|
||||
|
||||
#define gasket_nodev_info(format, arg...) \
|
||||
if (false) gasket_nodev_log(info, format, ##arg)
|
||||
|
||||
#define gasket_nodev_warn(format, arg...) \
|
||||
if (false) gasket_nodev_log(warn, format, ##arg)
|
||||
|
||||
#define gasket_nodev_error(format, arg...) \
|
||||
if (false) gasket_nodev_log(err, format, ##arg)
|
||||
|
||||
/* gasket_dev logging macros */
|
||||
#define gasket_log_info(gasket_dev, format, arg...) \
|
||||
if (false) gasket_dev_log(info, (gasket_dev)->dev_info.device, \
|
||||
(gasket_dev)->pci_dev, format, ##arg)
|
||||
|
||||
#define gasket_log_warn(gasket_dev, format, arg...) \
|
||||
if (false) gasket_dev_log(warn, (gasket_dev)->dev_info.device, \
|
||||
(gasket_dev)->pci_dev, format, ##arg)
|
||||
|
||||
#define gasket_log_error(gasket_dev, format, arg...) \
|
||||
if (false) gasket_dev_log(err, (gasket_dev)->dev_info.device, \
|
||||
(gasket_dev)->pci_dev, format, ##arg)
|
||||
|
||||
#define gasket_log_debug(gasket_dev, format, arg...) \
|
||||
if (false){ \
|
||||
if ((gasket_dev)->pci_dev) { \
|
||||
dev_dbg(&((gasket_dev)->pci_dev)->dev, "%s: " format \
|
||||
"\n", __func__, ##arg); \
|
||||
} else { \
|
||||
gasket_nodev_log(debug, format, ##arg); \
|
||||
} \
|
||||
}
|
||||
|
||||
#endif
|
||||
1771
drivers/staging/gasket/gasket_page_table.c
Normal file
1771
drivers/staging/gasket/gasket_page_table.c
Normal file
File diff suppressed because it is too large
Load Diff
265
drivers/staging/gasket/gasket_page_table.h
Normal file
265
drivers/staging/gasket/gasket_page_table.h
Normal file
@@ -0,0 +1,265 @@
|
||||
/* Gasket Page Table functionality. This file describes the address
|
||||
* translation/paging functionality supported by the Gasket driver framework.
|
||||
* As much as possible, internal details are hidden to simplify use -
|
||||
* all calls are thread-safe (protected by an internal mutex) except where
|
||||
* indicated otherwise.
|
||||
*
|
||||
* Copyright (C) 2018 Google, Inc.
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#ifndef __GASKET_ADDR_TRNSL_H__
|
||||
#define __GASKET_ADDR_TRNSL_H__
|
||||
|
||||
#include <linux/pci.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#include "gasket_constants.h"
|
||||
#include "gasket_core.h"
|
||||
|
||||
/*
|
||||
* Structure used for managing address translation on a device. All details are
|
||||
* internal to the implementation.
|
||||
*/
|
||||
struct gasket_page_table;
|
||||
|
||||
/*
|
||||
* Allocate and init address translation data.
|
||||
* @ppage_table: Pointer to Gasket page table pointer. Set by this call.
|
||||
* @att_base_reg: [Mapped] pointer to the first entry in the device's address
|
||||
* translation table.
|
||||
* @extended_offset_reg: [Mapped] pointer to the device's register containing
|
||||
* the starting index of the extended translation table.
|
||||
* @extended_bit_location: The index of the bit indicating whether an address
|
||||
* is extended.
|
||||
* @total_entries: The total number of entries in the device's address
|
||||
* translation table.
|
||||
* @device: Device structure for the underlying device. Only used for logging.
|
||||
* @pci_dev: PCI system descriptor for the underlying device.
|
||||
* @bool has_dma_ops: Whether the page table uses arch specific dma_ops or
|
||||
* whether the driver will supply its own.
|
||||
*
|
||||
* Description: Allocates and initializes data to track address translation -
|
||||
* simple and extended page table metadata. Initially, the page table is
|
||||
* partitioned such that all addresses are "simple" (single-level lookup).
|
||||
* gasket_partition_page_table can be called to change this paritioning.
|
||||
*
|
||||
* Returns 0 on success, a negative error code otherwise.
|
||||
*/
|
||||
int gasket_page_table_init(
|
||||
struct gasket_page_table **ppg_tbl,
|
||||
const struct gasket_bar_data *bar_data,
|
||||
const struct gasket_page_table_config *page_table_config,
|
||||
struct device *device, struct pci_dev *pci_dev, bool dma_ops);
|
||||
|
||||
/*
|
||||
* Deallocate and cleanup page table data.
|
||||
* @page_table: Gasket page table pointer.
|
||||
*
|
||||
* Description: The inverse of gasket_init; frees page_table and its contained
|
||||
* elements.
|
||||
*
|
||||
* Because this call destroys the page table, it cannot be
|
||||
* thread-safe (mutex-protected)!
|
||||
*/
|
||||
void gasket_page_table_cleanup(struct gasket_page_table *page_table);
|
||||
|
||||
/*
|
||||
* Sets the size of the simple page table.
|
||||
* @page_table: Gasket page table pointer.
|
||||
* @num_simple_entries: Desired size of the simple page table (in entries).
|
||||
*
|
||||
* Description: gasket_partition_page_table checks to see if the simple page
|
||||
* size can be changed (i.e., if there are no active extended
|
||||
* mappings in the new simple size range), and, if so,
|
||||
* sets the new simple and extended page table sizes.
|
||||
*
|
||||
* Returns 0 if successful, or non-zero if the page table entries
|
||||
* are not free.
|
||||
*/
|
||||
int gasket_page_table_partition(
|
||||
struct gasket_page_table *page_table, uint num_simple_entries);
|
||||
|
||||
/*
|
||||
* Get and map [host] user space pages into device memory.
|
||||
* @page_table: Gasket page table pointer.
|
||||
* @host_addr: Starting host virtual memory address of the pages.
|
||||
* @dev_addr: Starting device address of the pages.
|
||||
* @num_pages: Number of [4kB] pages to map.
|
||||
*
|
||||
* Description: Maps the "num_pages" pages of host memory pointed to by
|
||||
* host_addr to the address "dev_addr" in device memory.
|
||||
*
|
||||
* The caller is responsible for checking the addresses ranges.
|
||||
*
|
||||
* Returns 0 if successful or a non-zero error number otherwise.
|
||||
* If there is an error, no pages are mapped.
|
||||
*/
|
||||
int gasket_page_table_map(struct gasket_page_table *page_table, ulong host_addr,
|
||||
ulong dev_addr, uint num_pages);
|
||||
|
||||
/*
|
||||
* Un-map host pages from device memory.
|
||||
* @page_table: Gasket page table pointer.
|
||||
* @dev_addr: Starting device address of the pages to unmap.
|
||||
* @num_pages: The number of [4kB] pages to unmap.
|
||||
*
|
||||
* Description: The inverse of gasket_map_pages. Unmaps pages from the device.
|
||||
*/
|
||||
void gasket_page_table_unmap(
|
||||
struct gasket_page_table *page_table, ulong dev_addr, uint num_pages);
|
||||
|
||||
/*
|
||||
* Unmap ALL host pages from device memory.
|
||||
* @page_table: Gasket page table pointer.
|
||||
*/
|
||||
void gasket_page_table_unmap_all(struct gasket_page_table *page_table);
|
||||
|
||||
/*
|
||||
* Unmap all host pages from device memory and reset the table to fully simple
|
||||
* addressing.
|
||||
* @page_table: Gasket page table pointer.
|
||||
*/
|
||||
void gasket_page_table_reset(struct gasket_page_table *page_table);
|
||||
|
||||
/*
|
||||
* Reclaims unused page table memory.
|
||||
* @page_table: Gasket page table pointer.
|
||||
*
|
||||
* Description: Examines the page table and frees any currently-unused
|
||||
* allocations. Called internally on gasket_cleanup().
|
||||
*/
|
||||
void gasket_page_table_garbage_collect(struct gasket_page_table *page_table);
|
||||
|
||||
/*
|
||||
* Retrieve the backing page for a device address.
|
||||
* @page_table: Gasket page table pointer.
|
||||
* @dev_addr: Gasket device address.
|
||||
* @ppage: Pointer to a page pointer for the returned page.
|
||||
* @poffset: Pointer to an unsigned long for the returned offset.
|
||||
*
|
||||
* Description: Interprets the address and looks up the corresponding page
|
||||
* in the page table and the offset in that page. (We need an
|
||||
* offset because the host page may be larger than the Gasket chip
|
||||
* page it contains.)
|
||||
*
|
||||
* Returns 0 if successful, -1 for an error. The page pointer
|
||||
* and offset are returned through the pointers, if successful.
|
||||
*/
|
||||
int gasket_page_table_lookup_page(
|
||||
struct gasket_page_table *page_table, ulong dev_addr,
|
||||
struct page **page, ulong *poffset);
|
||||
|
||||
/*
|
||||
* Checks validity for input addrs and size.
|
||||
* @page_table: Gasket page table pointer.
|
||||
* @host_addr: Host address to check.
|
||||
* @dev_addr: Gasket device address.
|
||||
* @bytes: Size of the range to check (in bytes).
|
||||
*
|
||||
* Description: This call performs a number of checks to verify that the ranges
|
||||
* specified by both addresses and the size are valid for mapping pages into
|
||||
* device memory.
|
||||
*
|
||||
* Returns 1 if true - if the mapping is bad, 0 otherwise.
|
||||
*/
|
||||
int gasket_page_table_are_addrs_bad(
|
||||
struct gasket_page_table *page_table, ulong host_addr, ulong dev_addr,
|
||||
ulong bytes);
|
||||
|
||||
/*
|
||||
* Checks validity for input dev addr and size.
|
||||
* @page_table: Gasket page table pointer.
|
||||
* @dev_addr: Gasket device address.
|
||||
* @bytes: Size of the range to check (in bytes).
|
||||
*
|
||||
* Description: This call performs a number of checks to verify that the range
|
||||
* specified by the device address and the size is valid for mapping pages into
|
||||
* device memory.
|
||||
*
|
||||
* Returns 1 if true - if the address is bad, 0 otherwise.
|
||||
*/
|
||||
int gasket_page_table_is_dev_addr_bad(
|
||||
struct gasket_page_table *page_table, ulong dev_addr, ulong bytes);
|
||||
|
||||
/*
|
||||
* Gets maximum size for the given page table.
|
||||
* @page_table: Gasket page table pointer.
|
||||
*/
|
||||
uint gasket_page_table_max_size(struct gasket_page_table *page_table);
|
||||
|
||||
/*
|
||||
* Gets the total number of entries in the arg.
|
||||
* @page_table: Gasket page table pointer.
|
||||
*/
|
||||
uint gasket_page_table_num_entries(struct gasket_page_table *page_table);
|
||||
|
||||
/*
|
||||
* Gets the number of simple entries.
|
||||
* @page_table: Gasket page table pointer.
|
||||
*/
|
||||
uint gasket_page_table_num_simple_entries(struct gasket_page_table *page_table);
|
||||
|
||||
/*
|
||||
* Gets the number of extended entries.
|
||||
* @page_table: Gasket page table pointer.
|
||||
*/
|
||||
uint gasket_page_table_num_extended_entries(
|
||||
struct gasket_page_table *page_table);
|
||||
|
||||
/*
|
||||
* Gets the number of actively pinned pages.
|
||||
* @page_table: Gasket page table pointer.
|
||||
*/
|
||||
uint gasket_page_table_num_active_pages(struct gasket_page_table *page_table);
|
||||
|
||||
/*
|
||||
* Get status of page table managed by @page_table.
|
||||
* @page_table: Gasket page table pointer.
|
||||
*/
|
||||
int gasket_page_table_system_status(struct gasket_page_table *page_table);
|
||||
|
||||
/*
|
||||
* Allocate a block of coherent memory.
|
||||
* @gasket_dev: Gasket Device.
|
||||
* @size: Size of the memory block.
|
||||
* @dma_address: Dma address allocated by the kernel.
|
||||
* @index: Index of the gasket_page_table within this Gasket device
|
||||
*
|
||||
* Description: Allocate a contiguous coherent memory block, DMA'ble
|
||||
* by this device.
|
||||
*/
|
||||
int gasket_alloc_coherent_memory(struct gasket_dev *gasket_dev, uint64_t size,
|
||||
dma_addr_t *dma_address, uint64_t index);
|
||||
/* Release a block of contiguous coherent memory, in use by a device. */
|
||||
int gasket_free_coherent_memory(struct gasket_dev *gasket_dev, uint64_t size,
|
||||
dma_addr_t dma_address, uint64_t index);
|
||||
|
||||
/* Release all coherent memory. */
|
||||
void gasket_free_coherent_memory_all(struct gasket_dev *gasket_dev,
|
||||
uint64_t index);
|
||||
|
||||
/*
|
||||
* Records the host_addr to coherent dma memory mapping.
|
||||
* @gasket_dev: Gasket Device.
|
||||
* @size: Size of the virtual address range to map.
|
||||
* @dma_address: Dma address within the coherent memory range.
|
||||
* @vma: Virtual address we wish to map to coherent memory.
|
||||
*
|
||||
* Description: For each page in the virtual address range, record the
|
||||
* coherent page mapping.
|
||||
*
|
||||
* Does not perform validity checking.
|
||||
*/
|
||||
int gasket_set_user_virt(struct gasket_dev *gasket_dev, uint64_t size,
|
||||
dma_addr_t dma_address, ulong vma);
|
||||
|
||||
#endif
|
||||
497
drivers/staging/gasket/gasket_sysfs.c
Normal file
497
drivers/staging/gasket/gasket_sysfs.c
Normal file
@@ -0,0 +1,497 @@
|
||||
/* Copyright (C) 2018 Google, Inc.
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
#include "gasket_sysfs.h"
|
||||
|
||||
#include "gasket_core.h"
|
||||
#include "gasket_logging.h"
|
||||
|
||||
/*
|
||||
* Pair of kernel device and user-specified pointer. Used in lookups in sysfs
|
||||
* "show" functions to return user data.
|
||||
*/
|
||||
|
||||
struct gasket_sysfs_mapping {
|
||||
/*
|
||||
* The device bound to this mapping. If this is NULL, then this mapping
|
||||
* is free.
|
||||
*/
|
||||
struct device *device;
|
||||
|
||||
/* Legacy device struct, if used by this mapping's driver. */
|
||||
struct device *legacy_device;
|
||||
|
||||
/* The Gasket descriptor for this device. */
|
||||
struct gasket_dev *gasket_dev;
|
||||
|
||||
/* This device's set of sysfs attributes/nodes. */
|
||||
struct gasket_sysfs_attribute *attributes;
|
||||
|
||||
/* The number of live elements in "attributes". */
|
||||
int attribute_count;
|
||||
|
||||
/* Protects structure from simultaneous access. */
|
||||
struct mutex mutex;
|
||||
|
||||
/* Tracks active users of this mapping. */
|
||||
struct kref refcount;
|
||||
};
|
||||
|
||||
/*
|
||||
* Data needed to manage users of this sysfs utility.
|
||||
* Currently has a fixed size; if space is a concern, this can be dynamically
|
||||
* allocated.
|
||||
*/
|
||||
/*
|
||||
* 'Global' (file-scoped) list of mappings between devices and gasket_data
|
||||
* pointers. This removes the requirement to have a gasket_sysfs_data
|
||||
* handle in all files.
|
||||
*/
|
||||
static struct gasket_sysfs_mapping dev_mappings[GASKET_SYSFS_NUM_MAPPINGS];
|
||||
|
||||
/*
|
||||
* Callback when a mapping's refcount goes to zero.
|
||||
* @ref: The reference count of the containing sysfs mapping.
|
||||
*/
|
||||
static void release_entry(struct kref *ref)
|
||||
{
|
||||
/* All work is done after the return from kref_put. */
|
||||
}
|
||||
|
||||
/*
|
||||
* Looks up mapping information for the given device.
|
||||
* @device: The device whose mapping to look for.
|
||||
*
|
||||
* Looks up the requested device and takes a reference and returns it if found,
|
||||
* and returns NULL otherwise.
|
||||
*/
|
||||
static struct gasket_sysfs_mapping *get_mapping(struct device *device)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (!device) {
|
||||
gasket_nodev_error("Received NULL device!");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (i = 0; i < GASKET_SYSFS_NUM_MAPPINGS; i++) {
|
||||
mutex_lock(&dev_mappings[i].mutex);
|
||||
if (dev_mappings[i].device == device ||
|
||||
dev_mappings[i].legacy_device == device) {
|
||||
kref_get(&dev_mappings[i].refcount);
|
||||
mutex_unlock(&dev_mappings[i].mutex);
|
||||
return &dev_mappings[i];
|
||||
}
|
||||
mutex_unlock(&dev_mappings[i].mutex);
|
||||
}
|
||||
|
||||
gasket_nodev_info("Mapping to device %s not found.", device->kobj.name);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns a reference to a mapping.
|
||||
* @mapping: The mapping we're returning.
|
||||
*
|
||||
* Decrements the refcount for the given mapping (if valid). If the refcount is
|
||||
* zero, then it cleans up the mapping - in this function as opposed to the
|
||||
* kref_put callback, due to a potential deadlock.
|
||||
*
|
||||
* Although put_mapping_n exists, this function is left here (as an implicit
|
||||
* put_mapping_n(..., 1) for convenience.
|
||||
*/
|
||||
static void put_mapping(struct gasket_sysfs_mapping *mapping)
|
||||
{
|
||||
int i;
|
||||
int num_files_to_remove = 0;
|
||||
struct device_attribute *files_to_remove;
|
||||
struct device *device;
|
||||
struct device *legacy_device;
|
||||
|
||||
if (!mapping) {
|
||||
gasket_nodev_info("Mapping should not be NULL.");
|
||||
return;
|
||||
}
|
||||
|
||||
mutex_lock(&mapping->mutex);
|
||||
if (mapping->refcount.refcount.refs.counter == 0)
|
||||
gasket_nodev_error("Refcount is already 0!");
|
||||
if (kref_put(&mapping->refcount, release_entry)) {
|
||||
gasket_nodev_info("Removing Gasket sysfs mapping, device %s",
|
||||
mapping->device->kobj.name);
|
||||
/*
|
||||
* We can't remove the sysfs nodes in the kref callback, since
|
||||
* device_remove_file() blocks until the node is free.
|
||||
* Readers/writers of sysfs nodes, though, will be blocked on
|
||||
* the mapping mutex, resulting in deadlock. To fix this, the
|
||||
* sysfs nodes are removed outside the lock.
|
||||
*/
|
||||
device = mapping->device;
|
||||
legacy_device = mapping->legacy_device;
|
||||
num_files_to_remove = mapping->attribute_count;
|
||||
files_to_remove = kzalloc(
|
||||
num_files_to_remove * sizeof(*files_to_remove),
|
||||
GFP_KERNEL);
|
||||
for (i = 0; i < num_files_to_remove; i++)
|
||||
files_to_remove[i] = mapping->attributes[i].attr;
|
||||
|
||||
kfree(mapping->attributes);
|
||||
mapping->attributes = NULL;
|
||||
mapping->attribute_count = 0;
|
||||
mapping->device = NULL;
|
||||
mapping->gasket_dev = NULL;
|
||||
}
|
||||
mutex_unlock(&mapping->mutex);
|
||||
|
||||
if (num_files_to_remove != 0) {
|
||||
for (i = 0; i < num_files_to_remove; ++i) {
|
||||
device_remove_file(device, &files_to_remove[i]);
|
||||
if (legacy_device)
|
||||
device_remove_file(
|
||||
legacy_device, &files_to_remove[i]);
|
||||
}
|
||||
kfree(files_to_remove);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns a reference N times.
|
||||
* @mapping: The mapping to return.
|
||||
*
|
||||
* In higher-level resource acquire/release function pairs, the release function
|
||||
* will need to release a mapping 2x - once for the refcount taken in the
|
||||
* release function itself, and once for the count taken in the acquire call.
|
||||
*/
|
||||
static void put_mapping_n(struct gasket_sysfs_mapping *mapping, int times)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < times; i++)
|
||||
put_mapping(mapping);
|
||||
}
|
||||
|
||||
void gasket_sysfs_init(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < GASKET_SYSFS_NUM_MAPPINGS; i++) {
|
||||
dev_mappings[i].device = NULL;
|
||||
mutex_init(&dev_mappings[i].mutex);
|
||||
}
|
||||
}
|
||||
|
||||
int gasket_sysfs_create_mapping(
|
||||
struct device *device, struct gasket_dev *gasket_dev)
|
||||
{
|
||||
struct gasket_sysfs_mapping *mapping;
|
||||
int map_idx = -1;
|
||||
|
||||
/*
|
||||
* We need a function-level mutex to protect against the same device
|
||||
* being added [multiple times] simultaneously.
|
||||
*/
|
||||
static DEFINE_MUTEX(function_mutex);
|
||||
|
||||
mutex_lock(&function_mutex);
|
||||
|
||||
gasket_nodev_info(
|
||||
"Creating sysfs entries for device pointer 0x%p.", device);
|
||||
|
||||
/* Check that the device we're adding hasn't already been added. */
|
||||
mapping = get_mapping(device);
|
||||
if (mapping) {
|
||||
gasket_nodev_error(
|
||||
"Attempting to re-initialize sysfs mapping for device "
|
||||
"0x%p.", device);
|
||||
put_mapping(mapping);
|
||||
mutex_unlock(&function_mutex);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Find the first empty entry in the array. */
|
||||
for (map_idx = 0; map_idx < GASKET_SYSFS_NUM_MAPPINGS; ++map_idx) {
|
||||
mutex_lock(&dev_mappings[map_idx].mutex);
|
||||
if (!dev_mappings[map_idx].device)
|
||||
/* Break with the mutex held! */
|
||||
break;
|
||||
mutex_unlock(&dev_mappings[map_idx].mutex);
|
||||
}
|
||||
|
||||
if (map_idx == GASKET_SYSFS_NUM_MAPPINGS) {
|
||||
gasket_nodev_error("All mappings have been exhausted!");
|
||||
mutex_unlock(&function_mutex);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
gasket_nodev_info(
|
||||
"Creating sysfs mapping for device %s.", device->kobj.name);
|
||||
|
||||
mapping = &dev_mappings[map_idx];
|
||||
kref_init(&mapping->refcount);
|
||||
mapping->device = device;
|
||||
mapping->gasket_dev = gasket_dev;
|
||||
mapping->attributes = kzalloc(
|
||||
GASKET_SYSFS_MAX_NODES * sizeof(*mapping->attributes),
|
||||
GFP_KERNEL);
|
||||
mapping->attribute_count = 0;
|
||||
if (!mapping->attributes) {
|
||||
gasket_nodev_error("Unable to allocate sysfs attribute array.");
|
||||
mutex_unlock(&mapping->mutex);
|
||||
mutex_unlock(&function_mutex);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
mutex_unlock(&mapping->mutex);
|
||||
mutex_unlock(&function_mutex);
|
||||
|
||||
/* Don't decrement the refcount here! One open count keeps it alive! */
|
||||
return 0;
|
||||
}
|
||||
|
||||
int gasket_sysfs_create_entries(
|
||||
struct device *device, const struct gasket_sysfs_attribute *attrs)
|
||||
{
|
||||
int i;
|
||||
int ret;
|
||||
struct gasket_sysfs_mapping *mapping = get_mapping(device);
|
||||
|
||||
if (!mapping) {
|
||||
gasket_nodev_error(
|
||||
"Creating entries for device 0x%p without first "
|
||||
"initializing mapping.",
|
||||
device);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mutex_lock(&mapping->mutex);
|
||||
for (i = 0; strcmp(attrs[i].attr.attr.name, GASKET_ARRAY_END_MARKER);
|
||||
i++) {
|
||||
if (mapping->attribute_count == GASKET_SYSFS_MAX_NODES) {
|
||||
gasket_nodev_error(
|
||||
"Maximum number of sysfs nodes reached for "
|
||||
"device.");
|
||||
mutex_unlock(&mapping->mutex);
|
||||
put_mapping(mapping);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
ret = device_create_file(device, &attrs[i].attr);
|
||||
if (ret) {
|
||||
gasket_nodev_error("Unable to create device entries");
|
||||
mutex_unlock(&mapping->mutex);
|
||||
put_mapping(mapping);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (mapping->legacy_device) {
|
||||
ret = device_create_file(mapping->legacy_device,
|
||||
&attrs[i].attr);
|
||||
if (ret) {
|
||||
gasket_log_error(
|
||||
mapping->gasket_dev,
|
||||
"Unable to create legacy sysfs entries;"
|
||||
" rc: %d",
|
||||
ret);
|
||||
mutex_unlock(&mapping->mutex);
|
||||
put_mapping(mapping);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
mapping->attributes[mapping->attribute_count] = attrs[i];
|
||||
++mapping->attribute_count;
|
||||
}
|
||||
|
||||
mutex_unlock(&mapping->mutex);
|
||||
put_mapping(mapping);
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(gasket_sysfs_create_entries);
|
||||
|
||||
void gasket_sysfs_remove_mapping(struct device *device)
|
||||
{
|
||||
struct gasket_sysfs_mapping *mapping = get_mapping(device);
|
||||
|
||||
if (!mapping) {
|
||||
gasket_nodev_error(
|
||||
"Attempted to remove non-existent sysfs mapping to "
|
||||
"device 0x%p",
|
||||
device);
|
||||
return;
|
||||
}
|
||||
|
||||
put_mapping_n(mapping, 2);
|
||||
}
|
||||
|
||||
struct gasket_dev *gasket_sysfs_get_device_data(struct device *device)
|
||||
{
|
||||
struct gasket_sysfs_mapping *mapping = get_mapping(device);
|
||||
|
||||
if (!mapping) {
|
||||
gasket_nodev_error("device %p not registered.", device);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return mapping->gasket_dev;
|
||||
}
|
||||
EXPORT_SYMBOL(gasket_sysfs_get_device_data);
|
||||
|
||||
void gasket_sysfs_put_device_data(struct device *device, struct gasket_dev *dev)
|
||||
{
|
||||
struct gasket_sysfs_mapping *mapping = get_mapping(device);
|
||||
|
||||
if (!mapping)
|
||||
return;
|
||||
|
||||
/* See comment of put_mapping_n() for why the '2' is necessary. */
|
||||
put_mapping_n(mapping, 2);
|
||||
}
|
||||
EXPORT_SYMBOL(gasket_sysfs_put_device_data);
|
||||
|
||||
struct gasket_sysfs_attribute *gasket_sysfs_get_attr(
|
||||
struct device *device, struct device_attribute *attr)
|
||||
{
|
||||
int i;
|
||||
int num_attrs;
|
||||
struct gasket_sysfs_mapping *mapping = get_mapping(device);
|
||||
struct gasket_sysfs_attribute *attrs = NULL;
|
||||
|
||||
if (!mapping)
|
||||
return NULL;
|
||||
|
||||
attrs = mapping->attributes;
|
||||
num_attrs = mapping->attribute_count;
|
||||
for (i = 0; i < num_attrs; ++i) {
|
||||
if (!strcmp(attrs[i].attr.attr.name, attr->attr.name))
|
||||
return &attrs[i];
|
||||
}
|
||||
|
||||
gasket_nodev_error("Unable to find match for device_attribute %s",
|
||||
attr->attr.name);
|
||||
return NULL;
|
||||
}
|
||||
EXPORT_SYMBOL(gasket_sysfs_get_attr);
|
||||
|
||||
void gasket_sysfs_put_attr(
|
||||
struct device *device, struct gasket_sysfs_attribute *attr)
|
||||
{
|
||||
int i;
|
||||
int num_attrs;
|
||||
struct gasket_sysfs_mapping *mapping = get_mapping(device);
|
||||
struct gasket_sysfs_attribute *attrs = NULL;
|
||||
|
||||
if (!mapping)
|
||||
return;
|
||||
|
||||
attrs = mapping->attributes;
|
||||
num_attrs = mapping->attribute_count;
|
||||
for (i = 0; i < num_attrs; ++i) {
|
||||
if (&attrs[i] == attr) {
|
||||
put_mapping_n(mapping, 2);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
gasket_nodev_error(
|
||||
"Unable to put unknown attribute: %s", attr->attr.attr.name);
|
||||
}
|
||||
EXPORT_SYMBOL(gasket_sysfs_put_attr);
|
||||
|
||||
ssize_t gasket_sysfs_register_show(
|
||||
struct device *device, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
ulong reg_address, reg_bar, reg_value;
|
||||
struct gasket_sysfs_mapping *mapping;
|
||||
struct gasket_dev *gasket_dev;
|
||||
struct gasket_sysfs_attribute *gasket_attr;
|
||||
|
||||
mapping = get_mapping(device);
|
||||
if (!mapping) {
|
||||
gasket_nodev_info("Device driver may have been removed.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
gasket_dev = mapping->gasket_dev;
|
||||
if (!gasket_dev) {
|
||||
gasket_nodev_error(
|
||||
"No sysfs mapping found for device 0x%p", device);
|
||||
put_mapping(mapping);
|
||||
return 0;
|
||||
}
|
||||
|
||||
gasket_attr = gasket_sysfs_get_attr(device, attr);
|
||||
if (!gasket_attr) {
|
||||
put_mapping(mapping);
|
||||
return 0;
|
||||
}
|
||||
|
||||
reg_address = gasket_attr->data.bar_address.offset;
|
||||
reg_bar = gasket_attr->data.bar_address.bar;
|
||||
reg_value = gasket_dev_read_64(gasket_dev, reg_bar, reg_address);
|
||||
|
||||
gasket_sysfs_put_attr(device, gasket_attr);
|
||||
put_mapping(mapping);
|
||||
return snprintf(buf, PAGE_SIZE, "0x%lX\n", reg_value);
|
||||
}
|
||||
EXPORT_SYMBOL(gasket_sysfs_register_show);
|
||||
|
||||
ssize_t gasket_sysfs_register_store(
|
||||
struct device *device, struct device_attribute *attr, const char *buf,
|
||||
size_t count)
|
||||
{
|
||||
ulong parsed_value = 0;
|
||||
struct gasket_sysfs_mapping *mapping;
|
||||
struct gasket_dev *gasket_dev;
|
||||
struct gasket_sysfs_attribute *gasket_attr;
|
||||
|
||||
if (count < 3 || buf[0] != '0' || buf[1] != 'x') {
|
||||
gasket_nodev_error(
|
||||
"sysfs register write format: \"0x<hex value>\".");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (kstrtoul(buf, 16, &parsed_value) != 0) {
|
||||
gasket_nodev_error(
|
||||
"Unable to parse input as 64-bit hex value: %s.", buf);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mapping = get_mapping(device);
|
||||
if (!mapping) {
|
||||
gasket_nodev_info("Device driver may have been removed.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
gasket_dev = mapping->gasket_dev;
|
||||
if (!gasket_dev) {
|
||||
gasket_nodev_info("Device driver may have been removed.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
gasket_attr = gasket_sysfs_get_attr(device, attr);
|
||||
if (!gasket_attr) {
|
||||
put_mapping(mapping);
|
||||
return count;
|
||||
}
|
||||
|
||||
gasket_dev_write_64(gasket_dev, parsed_value,
|
||||
gasket_attr->data.bar_address.bar,
|
||||
gasket_attr->data.bar_address.offset);
|
||||
|
||||
if (gasket_attr->write_callback)
|
||||
gasket_attr->write_callback(
|
||||
gasket_dev, gasket_attr, parsed_value);
|
||||
|
||||
gasket_sysfs_put_attr(device, gasket_attr);
|
||||
put_mapping(mapping);
|
||||
return count;
|
||||
}
|
||||
EXPORT_SYMBOL(gasket_sysfs_register_store);
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user