// SPDX-License-Identifier: GPL-2.0
/*
* Driver for FPGA Device Feature List (DFL) Support
*
* Copyright (C) 2017-2018 Intel Corporation, Inc.
*
* Authors:
* Kang Luwei <luwei.kang@intel.com>
* Zhang Yi <yi.z.zhang@intel.com>
* Wu Hao <hao.wu@intel.com>
* Xiao Guangrong <guangrong.xiao@linux.intel.com>
*/
#include <linux/module.h>
#include "dfl.h"
static DEFINE_MUTEX(dfl_id_mutex);
/*
* when adding a new feature dev support in DFL framework, it's required to
* add a new item in enum dfl_id_type and provide related information in below
* dfl_devs table which is indexed by dfl_id_type, e.g. name string used for
* platform device creation (define name strings in dfl.h, as they could be
* reused by platform device drivers).
*
* if the new feature dev needs chardev support, then it's required to add
* a new item in dfl_chardevs table and configure dfl_devs[i].devt_type as
* index to dfl_chardevs table. If no chardev support just set devt_type
* as one invalid index (DFL_FPGA_DEVT_MAX).
*/
enum dfl_id_type {
FME_ID, /* fme id allocation and mapping */
PORT_ID, /* port id allocation and mapping */
DFL_ID_MAX,
};
enum dfl_fpga_devt_type {
DFL_FPGA_DEVT_FME,
DFL_FPGA_DEVT_PORT,
DFL_FPGA_DEVT_MAX,
};
/**
* dfl_dev_info - dfl feature device information.
* @name: name string of the feature platform device.
* @dfh_id: id value in Device Feature Header (DFH) register by DFL spec.
* @id: idr id of the feature dev.
* @devt_type: index to dfl_chrdevs[].
*/
struct dfl_dev_info {
const char *name;
u32 dfh_id;
struct idr id;
enum dfl_fpga_devt_type devt_type;
};
/* it is indexed by dfl_id_type */
static struct dfl_dev_info dfl_devs[] = {
{.name = DFL_FPGA_FEATURE_DEV_FME, .dfh_id = DFH_ID_FIU_FME,
.devt_type = DFL_FPGA_DEVT_FME},
{.name = DFL_FPGA_FEATURE_DEV_PORT, .dfh_id = DFH_ID_FIU_PORT,
.devt_type = DFL_FPGA_DEVT_PORT},
};
/**
* dfl_chardev_info - chardev information of dfl feature device
* @name: nmae string of the char device.
* @devt: devt of the char device.
*/
struct dfl_chardev_info {
const char *name;
dev_t devt;
};
/* indexed by enum dfl_fpga_devt_type */
static struct dfl_chardev_info dfl_chrdevs[] = {
{.name = DFL_FPGA_FEATURE_DEV_FME},
{.name = DFL_FPGA_FEATURE_DEV_PORT},
};
static void dfl_ids_init(void)
{
int i;
for (i = 0; i < ARRAY_SIZE(dfl_devs); i++)
idr_init(&dfl_devs[i].id);
}
static void dfl_ids_destroy(void)
{
int i;
for (i = 0; i < ARRAY_SIZE(dfl_devs); i++)
idr_destroy(&dfl_devs[i].id);
}
static int dfl_id_alloc(enum dfl_id_type type, struct device *dev)
{
int id;
WARN_ON(type >= DFL_ID_MAX);
mutex_lock(&dfl_id_mutex);
id = idr_alloc(&dfl_devs[type].id, dev, 0, 0, GFP_KERNEL);
mutex_unlock(&dfl_id_mutex);
return id;
}
static void dfl_id_free(enum dfl_id_type type, int id)
{
WARN_ON(type >= DFL_ID_MAX);
mutex_lock(&dfl_id_mutex);
idr_remove(&dfl_devs[type].id, id);
mutex_unlock(&dfl_id_mutex);
}
static enum dfl_id_type feature_dev_id_type(struct platform_device *pdev)
{
int i;
for (i = 0; i < ARRAY_SIZE(dfl_devs); i++)
if (!strcmp(dfl_devs[i].name, pdev->name))
return i;
return DFL_ID_MAX;
}
static enum dfl_id_type dfh_id_to_type(u32 id)
{
int i;
for (i = 0; i < ARRAY_SIZE(dfl_devs); i++)
if (dfl_devs[i].dfh_id == id)
return i;
return DFL_ID_MAX;
}
/*
* introduce a global port_ops list, it allows port drivers to register ops
* in such list, then other feature devices (e.g. FME), could use the port
* functions even related port platform device is hidden. Below is one example,
* in virtualization case of PCIe-based FPGA DFL device, when SRIOV is
* enabled, port (and it's AFU) is turned into VF and port platform device
* is hidden from system but it's still required to access port to finish FPGA
* reconfiguration function in FME.
*/
static DEFINE_MUTEX(dfl_port_ops_mutex);
static LIST_HEAD(dfl_port_ops_list);
/**
* dfl_fpga_port_ops_get - get matched port ops from the global list
* @pdev: platform device to match with associated port ops.
* Return: matched port ops on success, NULL otherwise.
*
* Please note that must dfl_fpga_port_ops_put after use the port_ops.
*/
struct dfl_fpga_port_ops *dfl_fpga_port_ops_get(struct platform_device *pdev)
{
struct dfl_fpga_port_ops *ops = NULL;
mutex_lock(&dfl_port_ops_mutex);
if (list_empty(&dfl_port_ops_list))
goto done;
list_for_each_entry(ops, &dfl_port_ops_list, node) {
/* match port_ops using the name of platform device */
if (!strcmp(pdev->name, ops->name)) {
if (!try_module_get(ops->owner))
ops = NULL;
goto done;