[PATCH v3 04/21] fpga: add device feature list support

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Device Feature List (DFL) defines a feature list structure that creates
a link list of feature headers within the MMIO space to provide an
extensible way of adding features. This patch introduces a kernel module
to provide basic infrastructure to support FPGA devices which implement
the Device Feature List.

Usually there will be different features and their sub features linked into
the DFL. This code provides common APIs for feature enumeration, it creates
a container device (FPGA base region), walks through the DFLs and creates
platform devices for feature devices (Currently it only supports two
different feature devices, FPGA Management Engine (FME) and Port which
the Accelerator Function Unit (AFU) connected to). In order to enumerate
the DFLs, the common APIs required low level driver to provide necessary
enumeration information (e.g address for each device feature list for
given device) and fill it to the fpga_enum_info data structure. Please
refer to below description for APIs added for enumeration.

Functions for enumeration information preparation:
 *fpga_enum_info_alloc
   allocate enumeration information data structure.

 *fpga_enum_info_add_dfl
   add a device feature list to fpga_enum_info data structure.

 *fpga_enum_info_free
   free fpga_enum_info data structure and related resources.

Functions for feature device enumeration:
 *fpga_enumerate_feature_devs
   enumerate feature devices and return container device.

 *fpga_remove_feature_devs
   remove feature devices under given container device.

Signed-off-by: Tim Whisonant <tim.whisonant@xxxxxxxxx>
Signed-off-by: Enno Luebbers <enno.luebbers@xxxxxxxxx>
Signed-off-by: Shiva Rao <shiva.rao@xxxxxxxxx>
Signed-off-by: Christopher Rauer <christopher.rauer@xxxxxxxxx>
Signed-off-by: Zhang Yi <yi.z.zhang@xxxxxxxxx>
Signed-off-by: Xiao Guangrong <guangrong.xiao@xxxxxxxxxxxxxxx>
Signed-off-by: Wu Hao <hao.wu@xxxxxxxxx>
----
v3: split from another patch.
    separate dfl enumeration code from original pcie driver.
    provide common data structures and APIs for enumeration.
    update device feature list parsing process according to latest hw.
    add dperf/iperf/hssi sub feature placeholder according to latest hw.
    remove build_info_add_sub_feature and other small functions.
    replace *_feature_num function with macro.
    remove writeq/readq.
---
 drivers/fpga/Kconfig    |  16 +
 drivers/fpga/Makefile   |   3 +
 drivers/fpga/fpga-dfl.c | 884 ++++++++++++++++++++++++++++++++++++++++++++++++
 drivers/fpga/fpga-dfl.h | 365 ++++++++++++++++++++
 4 files changed, 1268 insertions(+)
 create mode 100644 drivers/fpga/fpga-dfl.c
 create mode 100644 drivers/fpga/fpga-dfl.h

diff --git a/drivers/fpga/Kconfig b/drivers/fpga/Kconfig
index f47ef84..01ad31f 100644
--- a/drivers/fpga/Kconfig
+++ b/drivers/fpga/Kconfig
@@ -124,4 +124,20 @@ config OF_FPGA_REGION
 	  Support for loading FPGA images by applying a Device Tree
 	  overlay.
 
+config FPGA_DFL
+	tristate "FPGA Device Feature List (DFL) support"
+	select FPGA_BRIDGE
+	select FPGA_REGION
+	help
+	  Device Feature List (DFL) defines a feature list structure that
+	  creates a link list of feature headers within the MMIO space
+	  to provide an extensible way of adding features for FPGA.
+	  Driver can walk through the feature headers to enumerate feature
+	  devices (e.g FPGA Management Engine, Port and Accelerator
+	  Function Unit) and their private features for target FPGA devices.
+
+	  Select this option to enable common support for Field-Programmable
+	  Gate Array (FPGA) solutions which implement Device Feature List.
+	  It provides enumeration APIs, and feature device infrastructure.
+
 endif # FPGA
diff --git a/drivers/fpga/Makefile b/drivers/fpga/Makefile
index 3cb276a..447ba2b 100644
--- a/drivers/fpga/Makefile
+++ b/drivers/fpga/Makefile
@@ -27,3 +27,6 @@ obj-$(CONFIG_XILINX_PR_DECOUPLER)	+= xilinx-pr-decoupler.o
 # High Level Interfaces
 obj-$(CONFIG_FPGA_REGION)		+= fpga-region.o
 obj-$(CONFIG_OF_FPGA_REGION)		+= of-fpga-region.o
+
+# FPGA Device Feature List Support
+obj-$(CONFIG_FPGA_DFL)			+= fpga-dfl.o
diff --git a/drivers/fpga/fpga-dfl.c b/drivers/fpga/fpga-dfl.c
new file mode 100644
index 0000000..6609828
--- /dev/null
+++ b/drivers/fpga/fpga-dfl.c
@@ -0,0 +1,884 @@
+/*
+ * Driver for FPGA Device Feature List (DFL) Support
+ *
+ * Copyright (C) 2017 Intel Corporation, Inc.
+ *
+ * Authors:
+ *   Kang Luwei <luwei.kang@xxxxxxxxx>
+ *   Zhang Yi <yi.z.zhang@xxxxxxxxx>
+ *   Wu Hao <hao.wu@xxxxxxxxx>
+ *   Xiao Guangrong <guangrong.xiao@xxxxxxxxxxxxxxx>
+ *
+ * This work is licensed under the terms of the GNU GPL version 2.
+ * SPDX-License-Identifier: GPL-2.0
+ */
+#include <linux/module.h>
+
+#include "fpga-dfl.h"
+
+static DEFINE_MUTEX(fpga_id_mutex);
+
+enum fpga_id_type {
+	FME_ID,		/* fme id allocation and mapping */
+	PORT_ID,	/* port id allocation and mapping */
+	FPGA_ID_MAX,
+};
+
+/* it is protected by fpga_id_mutex */
+static struct idr fpga_ids[FPGA_ID_MAX];
+
+static void fpga_ids_init(void)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(fpga_ids); i++)
+		idr_init(fpga_ids + i);
+}
+
+static void fpga_ids_destroy(void)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(fpga_ids); i++)
+		idr_destroy(fpga_ids + i);
+}
+
+static int alloc_fpga_id(enum fpga_id_type type, struct device *dev)
+{
+	int id;
+
+	WARN_ON(type >= FPGA_ID_MAX);
+	mutex_lock(&fpga_id_mutex);
+	id = idr_alloc(fpga_ids + type, dev, 0, 0, GFP_KERNEL);
+	mutex_unlock(&fpga_id_mutex);
+
+	return id;
+}
+
+static void free_fpga_id(enum fpga_id_type type, int id)
+{
+	WARN_ON(type >= FPGA_ID_MAX);
+	mutex_lock(&fpga_id_mutex);
+	idr_remove(fpga_ids + type, id);
+	mutex_unlock(&fpga_id_mutex);
+}
+
+static enum fpga_id_type feature_dev_id_type(struct platform_device *pdev)
+{
+	if (!strcmp(pdev->name, FPGA_FEATURE_DEV_FME))
+		return FME_ID;
+
+	if (!strcmp(pdev->name, FPGA_FEATURE_DEV_PORT))
+		return PORT_ID;
+
+	WARN_ON(1);
+
+	return FPGA_ID_MAX;
+}
+
+/**
+ * build_feature_devs_info - info collected during feature dev build.
+ *
+ * @dev: device to enumerate.
+ * @cdev: the container device for all feature devices.
+ * @feature_dev: current feature device.
+ */
+struct build_feature_devs_info {
+	struct device *dev;
+	struct fpga_cdev *cdev;
+	struct platform_device *feature_dev;
+};
+
+static void fpga_cdev_add_port_dev(struct fpga_cdev *cdev,
+				   struct platform_device *port_pdev)
+{
+	struct feature_platform_data *pdata = dev_get_platdata(&port_pdev->dev);
+
+	mutex_lock(&cdev->lock);
+	list_add(&pdata->node, &cdev->port_dev_list);
+	get_device(&pdata->dev->dev);
+	mutex_unlock(&cdev->lock);
+}
+
+/*
+ * register current feature device, it is called when we need to switch to
+ * another feature parsing or we have parsed all features on given device
+ * feature list.
+ */
+static int build_info_commit_dev(struct build_feature_devs_info *binfo)
+{
+	int ret;
+
+	if (!binfo->feature_dev)
+		return 0;
+
+	ret = platform_device_add(binfo->feature_dev);
+	if (!ret) {
+		if (feature_dev_id_type(binfo->feature_dev) == PORT_ID)
+			fpga_cdev_add_port_dev(binfo->cdev, binfo->feature_dev);
+		else
+			binfo->cdev->fme_dev =
+					get_device(&binfo->feature_dev->dev);
+		/*
+		 * reset it to avoid build_info_free() freeing their resource.
+		 *
+		 * The resource of successfully registered feature devices
+		 * will be freed by platform_device_unregister(). See the
+		 * comments in build_info_create_dev().
+		 */
+		binfo->feature_dev = NULL;
+	}
+
+	return ret;
+}
+
+static int
+build_info_create_dev(struct build_feature_devs_info *binfo,
+		      enum fpga_id_type type, int feature_nr, const char *name)
+{
+	struct platform_device *fdev;
+	struct resource *res;
+	struct feature_platform_data *pdata;
+	int ret;
+
+	/* we will create a new device, commit current device first */
+	ret = build_info_commit_dev(binfo);
+	if (ret)
+		return ret;
+
+	/*
+	 * we use -ENODEV as the initialization indicator which indicates
+	 * whether the id need to be reclaimed
+	 */
+	fdev = platform_device_alloc(name, -ENODEV);
+	if (!fdev)
+		return -ENOMEM;
+
+	binfo->feature_dev = fdev;
+
+	fdev->id = alloc_fpga_id(type, &fdev->dev);
+	if (fdev->id < 0)
+		return fdev->id;
+
+	fdev->dev.parent = &binfo->cdev->region.dev;
+
+	/*
+	 * we do not need to care for the memory which is associated with
+	 * the platform device. After calling platform_device_unregister(),
+	 * it will be automatically freed by device's release() callback,
+	 * platform_device_release().
+	 */
+	pdata = kzalloc(feature_platform_data_size(feature_nr), GFP_KERNEL);
+	if (pdata) {
+		pdata->dev = fdev;
+		pdata->num = feature_nr;
+		mutex_init(&pdata->lock);
+	} else {
+		return -ENOMEM;
+	}
+
+	/*
+	 * the count should be initialized to 0 to make sure
+	 *__fpga_port_enable() following __fpga_port_disable()
+	 * works properly for port device.
+	 * and it should always be 0 for fme device.
+	 */
+	WARN_ON(pdata->disable_count);
+
+	fdev->dev.platform_data = pdata;
+	fdev->num_resources = feature_nr;
+	fdev->resource = kcalloc(feature_nr, sizeof(*res), GFP_KERNEL);
+	if (!fdev->resource)
+		return -ENOMEM;
+
+	return 0;
+}
+
+static void build_info_free(struct build_feature_devs_info *binfo)
+{
+	/*
+	 * it is a valid id, free it. See comments in
+	 * build_info_create_dev()
+	 */
+	if (binfo->feature_dev && binfo->feature_dev->id >= 0)
+		free_fpga_id(feature_dev_id_type(binfo->feature_dev),
+			     binfo->feature_dev->id);
+
+	platform_device_put(binfo->feature_dev);
+
+	devm_kfree(binfo->dev, binfo);
+}
+
+/*
+ * UAFU GUID is dynamic as it can be changed after FME downloads different
+ * Green Bitstream to the port, so we treat the unknown GUIDs which are
+ * attached on port's feature list as UAFU.
+ */
+static bool feature_is_UAFU(struct build_feature_devs_info *binfo)
+{
+	if (!binfo->feature_dev ||
+	    feature_dev_id_type(binfo->feature_dev) != PORT_ID)
+		return false;
+
+	return true;
+}
+
+struct feature_info {
+	const char *name;
+	resource_size_t resource_size;
+	int feature_index;
+};
+
+/* indexed by fme feature IDs which are defined in 'enum fme_feature_id'. */
+static struct feature_info fme_features[] = {
+	{
+		.name = FME_FEATURE_HEADER,
+		.resource_size = FME_HDR_SIZE,
+		.feature_index = FME_FEATURE_ID_HEADER,
+	},
+	{
+		.name = FME_FEATURE_THERMAL_MGMT,
+		.resource_size = FME_THERMAL_SIZE,
+		.feature_index = FME_FEATURE_ID_THERMAL_MGMT,
+	},
+	{
+		.name = FME_FEATURE_POWER_MGMT,
+		.resource_size = FME_POWER_SIZE,
+		.feature_index = FME_FEATURE_ID_POWER_MGMT,
+	},
+	{
+		.name = FME_FEATURE_GLOBAL_IPERF,
+		.resource_size = FME_IPERF_SIZE,
+		.feature_index = FME_FEATURE_ID_GLOBAL_IPERF,
+	},
+	{
+		.name = FME_FEATURE_GLOBAL_ERR,
+		.resource_size = FME_ERR_SIZE,
+		.feature_index = FME_FEATURE_ID_GLOBAL_ERR,
+	},
+	{
+		.name = FME_FEATURE_PR_MGMT,
+		.resource_size = FME_PR_SIZE,
+		.feature_index = FME_FEATURE_ID_PR_MGMT,
+	},
+	{
+		.name = FME_FEATURE_HSSI,
+		.resource_size = FME_HSSI_SIZE,
+		.feature_index = FME_FEATURE_ID_HSSI,
+	},
+	{
+		.name = FME_FEATURE_GLOBAL_DPERF,
+		.resource_size = FME_DPERF_SIZE,
+		.feature_index = FME_FEATURE_ID_GLOBAL_DPERF,
+	},
+};
+
+/* indexed by port feature IDs which are defined in 'enum port_feature_id'. */
+static struct feature_info port_features[] = {
+	{
+		.name = PORT_FEATURE_HEADER,
+		.resource_size = PORT_HDR_SIZE,
+		.feature_index = PORT_FEATURE_ID_HEADER,
+	},
+	{
+		.name = PORT_FEATURE_ERR,
+		.resource_size = PORT_ERR_SIZE,
+		.feature_index = PORT_FEATURE_ID_ERROR,
+	},
+	{
+		.name = PORT_FEATURE_UMSG,
+		.resource_size = PORT_UMSG_SIZE,
+		.feature_index = PORT_FEATURE_ID_UMSG,
+	},
+	{
+		/* This feature isn't available for now */
+		.name = PORT_FEATURE_PR,
+		.resource_size = DFH_SIZE,
+		.feature_index = PORT_FEATURE_ID_PR,
+	},
+	{
+		.name = PORT_FEATURE_STP,
+		.resource_size = PORT_STP_SIZE,
+		.feature_index = PORT_FEATURE_ID_STP,
+	},
+	{
+		/*
+		 * For User AFU feature, its region size is not fixed, but
+		 * reported by register PortCapability.mmio_size. Resource
+		 * size of UAFU will be set while parse port device.
+		 */
+		.name = PORT_FEATURE_UAFU,
+		.resource_size = 0,
+		.feature_index = PORT_FEATURE_ID_UAFU,
+	},
+};
+
+static int
+create_feature_instance(struct build_feature_devs_info *binfo,
+			struct fpga_enum_dfl *dfl, resource_size_t ofst,
+			struct feature_info *finfo)
+{
+	int index = finfo->feature_index;
+	struct platform_device *fdev = binfo->feature_dev;
+	struct feature_platform_data *pdata = dev_get_platdata(&fdev->dev);
+	struct resource *res = &fdev->resource[index];
+
+	if ((dfl->len - ofst < finfo->resource_size) || pdata->num < index)
+		return -EINVAL;
+
+	res->start = dfl->start + ofst;
+	res->end = res->start + finfo->resource_size - 1;
+	res->flags = IORESOURCE_MEM;
+	res->name = finfo->name;
+
+	pdata->features[index].name = finfo->name;
+	pdata->features[index].resource_index = index;
+	pdata->features[index].ioaddr = dfl->ioaddr + ofst;
+
+	return 0;
+}
+
+static int parse_feature_fme(struct build_feature_devs_info *binfo,
+			     struct fpga_enum_dfl *dfl,
+			     resource_size_t ofst)
+{
+	int ret;
+
+	ret = build_info_create_dev(binfo, FME_ID, FME_FEATURE_NUM,
+				    FPGA_FEATURE_DEV_FME);
+	if (ret)
+		return ret;
+
+	return create_feature_instance(binfo, dfl, ofst,
+				       &fme_features[FME_FEATURE_ID_HEADER]);
+}
+
+static int parse_feature_fme_private(struct build_feature_devs_info *binfo,
+				     struct fpga_enum_dfl *dfl,
+				     resource_size_t ofst)
+{
+	u64 v;
+	int id;
+
+	v = readq(dfl->ioaddr + ofst + DFH);
+	id = FIELD_GET(DFH_ID, v);
+
+	if (id >= ARRAY_SIZE(fme_features)) {
+		dev_info(binfo->dev, "FME feature id %x is not supported yet.\n",
+			 id);
+		return 0;
+	}
+
+	return create_feature_instance(binfo, dfl, ofst, &fme_features[id]);
+}
+
+static int parse_feature_port(struct build_feature_devs_info *binfo,
+			      struct fpga_enum_dfl *dfl,
+			      resource_size_t ofst)
+{
+	int ret;
+
+	ret = build_info_create_dev(binfo, PORT_ID, PORT_FEATURE_NUM,
+				    FPGA_FEATURE_DEV_PORT);
+	if (ret)
+		return ret;
+
+	return create_feature_instance(binfo, dfl, ofst,
+				       &port_features[PORT_FEATURE_ID_HEADER]);
+}
+
+static void enable_port_uafu(struct build_feature_devs_info *binfo)
+{
+	enum port_feature_id id = PORT_FEATURE_ID_UAFU;
+	void __iomem *base;
+	u64 v;
+
+	base = get_feature_ioaddr_by_index(&binfo->feature_dev->dev,
+					   PORT_FEATURE_ID_HEADER);
+
+	v = readq(base + PORT_HDR_CAP);
+	port_features[id].resource_size =
+			FIELD_GET(PORT_CAP_MMIO_SIZE, v) << 10;
+
+	/*
+	 * To enable User AFU, driver needs to clear reset bit on related port,
+	 * otherwise the mmio space of this user AFU will be invalid.
+	 */
+	if (port_features[id].resource_size)
+		fpga_port_reset(binfo->feature_dev);
+}
+
+static int parse_feature_port_private(struct build_feature_devs_info *binfo,
+				      struct fpga_enum_dfl *dfl,
+				      resource_size_t ofst)
+{
+	enum port_feature_id id;
+	u32 dfh_id;
+	u64 v;
+
+	v = readq(dfl->ioaddr + ofst + DFH);
+	dfh_id = FIELD_GET(DFH_ID, v);
+
+	/*
+	 * the region of port feature id is [0x10, 0x13], + 1 to reserve 0
+	 * which is dedicated for port-hdr.
+	 */
+	id = (dfh_id & 0x000f) + 1;
+
+	if (id >= ARRAY_SIZE(port_features)) {
+		dev_info(binfo->dev, "Port feature id %x is not supported yet.\n",
+			 dfh_id);
+		return 0;
+	}
+
+	return create_feature_instance(binfo, dfl, ofst, &port_features[id]);
+}
+
+static int parse_feature_port_uafu(struct build_feature_devs_info *binfo,
+				   struct fpga_enum_dfl *dfl,
+				   resource_size_t ofst)
+{
+	enum port_feature_id id = PORT_FEATURE_ID_UAFU;
+	int ret;
+
+	if (port_features[id].resource_size) {
+		ret = create_feature_instance(binfo, dfl, ofst,
+					      &port_features[id]);
+		port_features[id].resource_size = 0;
+	} else {
+		dev_err(binfo->dev, "the uafu feature header is mis-configured.\n");
+		ret = -EINVAL;
+	}
+
+	return ret;
+}
+
+static int parse_feature_afus(struct build_feature_devs_info *binfo,
+			      struct fpga_enum_dfl *dfl,
+			      resource_size_t ofst)
+{
+	void __iomem *start = dfl->ioaddr + ofst;
+	void __iomem *end = dfl->ioaddr + dfl->len;
+	u32 offset;
+	u64 v;
+	int ret;
+
+	for (; start < end; start += offset) {
+		if (end - start < AFU_DFH_SIZE)
+			return -EINVAL;
+
+		if (feature_is_UAFU(binfo))
+			ret = parse_feature_port_uafu(binfo, dfl,
+						      start - dfl->ioaddr);
+			if (ret)
+				return ret;
+
+		v = readq(start + NEXT_AFU);
+
+		offset = FIELD_GET(NEXT_AFU_NEXT_DFH_OFST, v);
+		if (!offset)
+			break;
+	}
+
+	return 0;
+}
+
+static int parse_feature_fiu(struct build_feature_devs_info *binfo,
+			     struct fpga_enum_dfl *dfl,
+			     resource_size_t ofst)
+{
+	u32 id, offset;
+	u64 v;
+	int ret = 0;
+
+	v = readq(dfl->ioaddr + ofst + DFH);
+	id = FIELD_GET(DFH_ID, v);
+
+	switch (id) {
+	case DFH_ID_FIU_FME:
+		return parse_feature_fme(binfo, dfl, ofst);
+	case DFH_ID_FIU_PORT:
+		ret = parse_feature_port(binfo, dfl, ofst);
+		enable_port_uafu(binfo);
+		if (ret)
+			return ret;
+
+		/* Check Port FIU's next_afu pointer to User AFU DFH */
+		v = readq(dfl->ioaddr + ofst + NEXT_AFU);
+
+		offset = FIELD_GET(NEXT_AFU_NEXT_DFH_OFST, v);
+		if (offset)
+			return parse_feature_afus(binfo, dfl, ofst + offset);
+
+		dev_dbg(binfo->dev, "No AFUs detected on Port\n");
+		break;
+	default:
+		dev_info(binfo->dev, "FIU TYPE %d is not supported yet.\n",
+			 id);
+	}
+
+	return ret;
+}
+
+static int parse_feature_private(struct build_feature_devs_info *binfo,
+				 struct fpga_enum_dfl *dfl,
+				 resource_size_t ofst)
+{
+	u64 v;
+	u32 id;
+
+	v = readq(dfl->ioaddr + ofst + DFH);
+	id = FIELD_GET(DFH_ID, v);
+
+	if (!binfo->feature_dev) {
+		dev_err(binfo->dev, "the private feature %x does not belong to any AFU.\n",
+			id);
+		return -EINVAL;
+	}
+
+	switch (feature_dev_id_type(binfo->feature_dev)) {
+	case FME_ID:
+		return parse_feature_fme_private(binfo, dfl, ofst);
+	case PORT_ID:
+		return parse_feature_port_private(binfo, dfl, ofst);
+	default:
+		dev_info(binfo->dev, "private feature %x belonging to AFU %s is not supported yet.\n",
+			 id, binfo->feature_dev->name);
+	}
+	return 0;
+}
+
+/**
+ * parse_feature - parse a feature on given device feature list
+ *
+ * @binfo: build feature devices information.
+ * @dfl: device feature list to parse
+ * @ofst: offset to feature header on this device feature list
+ */
+static int parse_feature(struct build_feature_devs_info *binfo,
+			 struct fpga_enum_dfl *dfl, resource_size_t ofst)
+{
+	u64 v;
+	u32 type;
+
+	v = readq(dfl->ioaddr + ofst + DFH);
+	type = FIELD_GET(DFH_TYPE, v);
+
+	switch (type) {
+	case DFH_TYPE_AFU:
+		return parse_feature_afus(binfo, dfl, ofst);
+	case DFH_TYPE_PRIVATE:
+		return parse_feature_private(binfo, dfl, ofst);
+	case DFH_TYPE_FIU:
+		return parse_feature_fiu(binfo, dfl, ofst);
+	default:
+		dev_info(binfo->dev,
+			 "Feature Type %x is not supported.\n", type);
+	}
+
+	return 0;
+}
+
+static int parse_feature_list(struct build_feature_devs_info *binfo,
+			      struct fpga_enum_dfl *dfl)
+{
+	void __iomem *start = dfl->ioaddr;
+	void __iomem *end = dfl->ioaddr + dfl->len;
+	int ret = 0;
+	u32 ofst = 0;
+	u64 v;
+
+	/* walk through the device feature list via DFH's next DFH pointer. */
+	for (; start < end; start += ofst) {
+		if (end - start < DFH_SIZE) {
+			dev_err(binfo->dev, "The region is too small to contain a feature.\n");
+			return -EINVAL;
+		}
+
+		ret = parse_feature(binfo, dfl, start - dfl->ioaddr);
+		if (ret)
+			return ret;
+
+		v = readq(start + DFH);
+		ofst = FIELD_GET(DFH_NEXT_HDR_OFST, v);
+
+		/* stop parsing if EOL(End of List) is set or offset is 0 */
+		if ((v & DFH_EOL) || !ofst)
+			break;
+	}
+
+	/* commit current feature device when reach the end of list */
+	return build_info_commit_dev(binfo);
+}
+
+struct fpga_enum_info *fpga_enum_info_alloc(struct device *dev)
+{
+	struct fpga_enum_info *info;
+
+	get_device(dev);
+
+	info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL);
+	if (!info) {
+		put_device(dev);
+		return NULL;
+	}
+
+	info->dev = dev;
+	INIT_LIST_HEAD(&info->dfls);
+
+	return info;
+}
+EXPORT_SYMBOL_GPL(fpga_enum_info_alloc);
+
+int fpga_enum_info_add_dfl(struct fpga_enum_info *info, resource_size_t start,
+			   resource_size_t len, void __iomem *ioaddr)
+{
+	struct fpga_enum_dfl *dfl;
+
+	dfl = devm_kzalloc(info->dev, sizeof(*dfl), GFP_KERNEL);
+	if (!dfl)
+		return -ENOMEM;
+
+	dfl->start = start;
+	dfl->len = len;
+	dfl->ioaddr = ioaddr;
+
+	list_add_tail(&dfl->node, &info->dfls);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(fpga_enum_info_add_dfl);
+
+void fpga_enum_info_free(struct fpga_enum_info *info)
+{
+	struct fpga_enum_dfl *tmp, *dfl;
+	struct device *dev;
+
+	if (!info)
+		return;
+
+	dev = info->dev;
+
+	/* remove all device feature lists in the list. */
+	list_for_each_entry_safe(dfl, tmp, &info->dfls, node) {
+		list_del(&dfl->node);
+		devm_kfree(dev, dfl);
+	}
+
+	devm_kfree(dev, info);
+	put_device(dev);
+}
+EXPORT_SYMBOL_GPL(fpga_enum_info_free);
+
+static int remove_feature_dev(struct device *dev, void *data)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	enum fpga_id_type type = feature_dev_id_type(pdev);
+	int id = pdev->id;
+
+	platform_device_unregister(pdev);
+
+	free_fpga_id(type, id);
+
+	return 0;
+}
+
+static void remove_feature_devs(struct fpga_cdev *cdev)
+{
+	device_for_each_child(&cdev->region.dev, NULL, remove_feature_dev);
+}
+
+/**
+ * fpga_enumerate_feature_devs - enumerate feature devices
+ * @info: information for enumeration.
+ *
+ * This function creates a container device (base FPGA region), enumerates
+ * feature devices based on the enumeration info and creates platform devices
+ * under the container device.
+ *
+ * Return: fpga_cdev struct on success, -errno on failure
+ */
+struct fpga_cdev *fpga_enumerate_feature_devs(struct fpga_enum_info *info)
+{
+	struct build_feature_devs_info *binfo;
+	struct fpga_cdev *cdev;
+	struct fpga_enum_dfl *dfl;
+	int ret = 0;
+
+	if (!info->dev)
+		return ERR_PTR(-ENODEV);
+
+	cdev = devm_kzalloc(info->dev, sizeof(*cdev), GFP_KERNEL);
+	if (!cdev)
+		return ERR_PTR(-ENOMEM);
+
+	cdev->parent = info->dev;
+	mutex_init(&cdev->lock);
+	INIT_LIST_HEAD(&cdev->port_dev_list);
+	cdev->region.parent = info->dev;
+
+	ret = fpga_region_register(&cdev->region);
+	if (ret)
+		goto free_cdev_exit;
+
+	/* create and init build info for enumeration */
+	binfo = devm_kzalloc(info->dev, sizeof(*binfo), GFP_KERNEL);
+	if (!binfo) {
+		ret = -ENOMEM;
+		goto unregister_region_exit;
+	}
+
+	binfo->dev = info->dev;
+	binfo->cdev = cdev;
+
+	/*
+	 * start enumeration for all feature devices based on Device Feature
+	 * Lists.
+	 */
+	list_for_each_entry(dfl, &info->dfls, node) {
+		ret = parse_feature_list(binfo, dfl);
+		if (ret) {
+			remove_feature_devs(cdev);
+			build_info_free(binfo);
+			goto unregister_region_exit;
+		}
+	}
+
+	build_info_free(binfo);
+
+	return cdev;
+
+unregister_region_exit:
+	fpga_region_unregister(&cdev->region);
+free_cdev_exit:
+	devm_kfree(cdev->parent, cdev);
+	return ERR_PTR(ret);
+}
+EXPORT_SYMBOL_GPL(fpga_enumerate_feature_devs);
+
+/**
+ * fpga_remove_feature_devs - remove all feature devices
+ * @cdev: fpga container device.
+ *
+ * Remove the container device and all feature devices under given container
+ * devices.
+ */
+void fpga_remove_feature_devs(struct fpga_cdev *cdev)
+{
+	struct feature_platform_data *pdata, *ptmp;
+
+	remove_feature_devs(cdev);
+
+	mutex_lock(&cdev->lock);
+	if (cdev->fme_dev) {
+		/* the fme should be unregistered. */
+		WARN_ON(device_is_registered(cdev->fme_dev));
+		put_device(cdev->fme_dev);
+	}
+
+	list_for_each_entry_safe(pdata, ptmp, &cdev->port_dev_list, node) {
+		struct platform_device *port_dev = pdata->dev;
+
+		/* the port should be unregistered. */
+		WARN_ON(device_is_registered(&port_dev->dev));
+		list_del(&pdata->node);
+		put_device(&port_dev->dev);
+	}
+	mutex_unlock(&cdev->lock);
+
+	fpga_region_unregister(&cdev->region);
+	devm_kfree(cdev->parent, cdev);
+}
+EXPORT_SYMBOL_GPL(fpga_remove_feature_devs);
+
+int fpga_port_id(struct platform_device *pdev)
+{
+	void __iomem *base;
+
+	base = get_feature_ioaddr_by_index(&pdev->dev, PORT_FEATURE_ID_HEADER);
+	WARN_ON(!base);
+
+	return FIELD_GET(PORT_CAP_PORT_NUM, readq(base + FME_HDR_CAP));
+}
+EXPORT_SYMBOL_GPL(fpga_port_id);
+
+/*
+ * Enable Port by clear the port soft reset bit, which is set by default.
+ * The User AFU is unable to respond to any MMIO access while in reset.
+ * __fpga_port_enable function should only be used after __fpga_port_disable
+ * function.
+ */
+void __fpga_port_enable(struct platform_device *pdev)
+{
+	struct feature_platform_data *pdata = dev_get_platdata(&pdev->dev);
+	void __iomem *base;
+	u64 v;
+
+	WARN_ON(!pdata->disable_count);
+
+	if (--pdata->disable_count != 0)
+		return;
+
+	base = get_feature_ioaddr_by_index(&pdev->dev, PORT_FEATURE_ID_HEADER);
+	WARN_ON(!base);
+
+	/* Clear port soft reset */
+	v = readq(base + PORT_HDR_CTRL);
+	v &= ~PORT_CTRL_SFTRST;
+	writeq(v, base + PORT_HDR_CTRL);
+}
+EXPORT_SYMBOL_GPL(__fpga_port_enable);
+
+#define RST_POLL_INVL 10 /* us */
+#define RST_POLL_TIMEOUT 1000 /* us */
+
+int __fpga_port_disable(struct platform_device *pdev)
+{
+	struct feature_platform_data *pdata = dev_get_platdata(&pdev->dev);
+	void __iomem *base;
+	u64 v;
+
+	if (pdata->disable_count++ != 0)
+		return 0;
+
+	base = get_feature_ioaddr_by_index(&pdev->dev, PORT_FEATURE_ID_HEADER);
+	WARN_ON(!base);
+
+	/* Set port soft reset */
+	v = readq(base + PORT_HDR_CTRL);
+	v |= PORT_CTRL_SFTRST;
+	writeq(v, base + PORT_HDR_CTRL);
+
+	/*
+	 * HW sets ack bit to 1 when all outstanding requests have been drained
+	 * on this port and minimum soft reset pulse width has elapsed.
+	 * Driver polls port_soft_reset_ack to determine if reset done by HW.
+	 */
+	if (readq_poll_timeout(base + PORT_HDR_CTRL, v, v & PORT_CTRL_SFTRST,
+			       RST_POLL_INVL, RST_POLL_TIMEOUT)) {
+		dev_err(&pdev->dev, "timeout, fail to reset device\n");
+		return -ETIMEDOUT;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(__fpga_port_disable);
+
+static int __init dfl_fpga_init(void)
+{
+	fpga_ids_init();
+
+	return 0;
+}
+
+static void __exit dfl_fpga_exit(void)
+{
+	fpga_ids_destroy();
+}
+
+module_init(dfl_fpga_init);
+module_exit(dfl_fpga_exit);
+
+MODULE_DESCRIPTION("FPGA Device Feature List (DFL) Support");
+MODULE_AUTHOR("Intel Corporation");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/fpga/fpga-dfl.h b/drivers/fpga/fpga-dfl.h
new file mode 100644
index 0000000..abcbe57
--- /dev/null
+++ b/drivers/fpga/fpga-dfl.h
@@ -0,0 +1,365 @@
+/*
+ * Driver Header File for FPGA Device Feature List (DFL) Support
+ *
+ * Copyright (C) 2017 Intel Corporation, Inc.
+ *
+ * Authors:
+ *   Kang Luwei <luwei.kang@xxxxxxxxx>
+ *   Zhang Yi <yi.z.zhang@xxxxxxxxx>
+ *   Wu Hao <hao.wu@xxxxxxxxx>
+ *   Xiao Guangrong <guangrong.xiao@xxxxxxxxxxxxxxx>
+ *
+ * This work is licensed under the terms of the GNU GPL version 2.
+ * SPDX-License-Identifier: GPL-2.0
+ */
+
+#ifndef __DFL_FPGA_H
+#define __DFL_FPGA_H
+
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+#include <linux/fs.h>
+#include <linux/iopoll.h>
+#include <linux/io-64-nonatomic-lo-hi.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/uuid.h>
+#include <linux/fpga/fpga-region.h>
+
+/* maximum supported number of ports */
+#define MAX_FPGA_PORT_NUM 4
+/* plus one for fme device */
+#define MAX_FEATURE_DEV_NUM	(MAX_FPGA_PORT_NUM + 1)
+
+#define FME_FEATURE_HEADER          "fme_hdr"
+#define FME_FEATURE_THERMAL_MGMT    "fme_thermal"
+#define FME_FEATURE_POWER_MGMT      "fme_power"
+#define FME_FEATURE_GLOBAL_IPERF    "fme_iperf"
+#define FME_FEATURE_GLOBAL_ERR      "fme_error"
+#define FME_FEATURE_PR_MGMT         "fme_pr"
+#define FME_FEATURE_HSSI            "fme_hssi"
+#define FME_FEATURE_GLOBAL_DPERF    "fme_dperf"
+
+#define PORT_FEATURE_HEADER         "port_hdr"
+#define PORT_FEATURE_UAFU           "port_uafu"
+#define PORT_FEATURE_ERR            "port_err"
+#define PORT_FEATURE_UMSG           "port_umsg"
+#define PORT_FEATURE_PR             "port_pr"
+#define PORT_FEATURE_STP            "port_stp"
+
+/* Device Feature Header Register Set */
+#define DFH			0x0
+#define GUID_L			0x8
+#define GUID_H			0x10
+#define NEXT_AFU		0x18
+
+/* Device Feature Header Register Bitfield */
+#define DFH_ID			GENMASK_ULL(11, 0)	/* Feature ID */
+#define DFH_ID_FIU_FME		0
+#define DFH_ID_FIU_PORT		1
+#define DFH_REVISION		GENMASK_ULL(15, 12)	/* Feature revision */
+#define DFH_NEXT_HDR_OFST	GENMASK_ULL(39, 16)	/* Offset to next DFH */
+#define DFH_EOL			BIT(40)			/* End of list */
+#define DFH_TYPE		GENMASK_ULL(63, 60)	/* Feature type */
+#define DFH_TYPE_AFU		1
+#define DFH_TYPE_PRIVATE	3
+#define DFH_TYPE_FIU		4
+
+/* Next AFU Register Bitfield */
+#define NEXT_AFU_NEXT_DFH_OFST	GENMASK_ULL(23, 0)	/* Offset to next AFU */
+
+/*
+ * It only has DFH register as header for private feature, but for FIU/AFU
+ * For FIU/AFU features, they all have DFH + GUID + NEXT_AFU as common header
+ * registers.
+ */
+#define DFH_SIZE		0x8
+#define AFU_DFH_SIZE		0x20
+#define FIU_DFH_SIZE		0x20
+
+/* FME Header Register Set */
+#define FME_HDR_DFH		DFH
+#define FME_HDR_AFU_GUID_L	GUID_L
+#define FME_HDR_AFU_GUID_H	GUID_H
+#define FME_HDR_NEXT_AFU	NEXT_AFU
+#define FME_HDR_CAP		0x30
+#define FME_HDR_PORT_OFST(n)	(0x38 + ((n) * 0x8))
+#define FME_HDR_BITSTREAM_ID	0x60
+#define FME_HDR_BITSTREAM_MD	0x68
+#define FME_HDR_SIZE		0x70
+
+/* FME Fab Capability Register Bitfield */
+#define FME_CAP_FABRIC_VERID	GENMASK_ULL(7, 0)	/* Fabric version ID */
+#define FME_CAP_SOCKET_ID	BIT(8)			/* Socket ID */
+#define FME_CAP_PCIE0_LINK_AVL	BIT(12)			/* PCIE0 Link */
+#define FME_CAP_PCIE1_LINK_AVL	BIT(13)			/* PCIE1 Link */
+#define FME_CAP_COHR_LINK_AVL	BIT(14)			/* Coherent Link */
+#define FME_CAP_IOMMU_AVL	BIT(16)			/* IOMMU available */
+#define FME_CAP_NUM_PORTS	GENMASK_ULL(19, 17)	/* Number of ports */
+#define FME_CAP_ADDR_WIDTH	GENMASK_ULL(29, 24)	/* Address bus width */
+#define FME_CAP_CACHE_SIZE	GENMASK_ULL(43, 32)	/* cache size in KB */
+#define FME_CAP_CACHE_ASSOC	GENMASK_ULL(47, 44)	/* Associativity */
+
+/* FME Port Offset Register Bitfield */
+/* Offset to port device feature header */
+#define FME_PORT_OFST_DFH_OFST	GENMASK_ULL(23, 0)
+/* PCI Bar ID for this port */
+#define FME_PORT_OFST_BAR_ID	GENMASK_ULL(34, 32)
+/* AFU MMIO access permission. 1 - VF, 0 - PF. */
+#define FME_PORT_OFST_ACC_CTRL	BIT(55)
+#define FME_PORT_OFST_ACC_PF	0
+#define FME_PORT_OFST_ACC_VF	1
+#define FME_PORT_OFST_IMP	BIT(60)
+
+/* FME Thermal Sub Feature Register Set */
+#define FME_THERMAL_DFH		DFH
+#define FME_THERMAL_SIZE	DFH_SIZE
+
+/* FME Power Sub Feature Register Set */
+#define FME_POWER_DFH		DFH
+#define FME_POWER_SIZE		DFH_SIZE
+
+/* FME Global Performance Sub Feature Register Set */
+#define FME_IPERF_DFH		DFH
+#define FME_IPERF_SIZE		DFH_SIZE
+
+/* FME Global Error Sub Feature Register Set */
+#define FME_ERR_DFH		DFH
+#define FME_ERR_SIZE		DFH_SIZE
+
+/* FME Partial Reconfiguration Sub Feature Register Set */
+#define FME_PR_DFH		DFH
+#define FME_PR_SIZE		DFH_SIZE
+
+/* FME HSSI Sub Feature Register Set */
+#define FME_HSSI_DFH		DFH
+#define FME_HSSI_SIZE		DFH_SIZE
+
+/* FME Global Performance Sub Feature Register Set */
+#define FME_DPERF_DFH		DFH
+#define FME_DPERF_SIZE		DFH_SIZE
+
+/* PORT Header Register Set */
+#define PORT_HDR_DFG		DFH
+#define PORT_HDR_AFU_GUID_L	GUID_L
+#define PORT_HDR_AFU_GUID_H	GUID_H
+#define PORT_HDR_NEXT_AFU	NEXT_AFU
+#define PORT_HDR_CAP		0x30
+#define PORT_HDR_CTRL		0x38
+#define PORT_HDR_SIZE		0x40
+
+/* Port Capability Register Bitfield */
+#define PORT_CAP_PORT_NUM	GENMASK(1, 0)		/* ID of this port */
+#define PORT_CAP_MMIO_SIZE	GENMASK(23, 8)		/* MMIO size in KB */
+#define PORT_CAP_SUPP_INT_NUM	GENMASK(35, 32)		/* Interrupts num */
+
+/* Port Control Register Bitfield */
+#define PORT_CTRL_SFTRST	BIT(0)			/* Port soft reset */
+/* Latency tolerance reporting. '1' >= 40us, '0' < 40us.*/
+#define PORT_CTRL_LATENCY	BIT(2)
+#define PORT_CTRL_SFTRST_ACK	BIT(4)			/* HW ack for reset */
+
+/* PORT Error Sub Feature Register Set */
+#define PORT_ERR_DFH		DFH
+#define PORT_ERR_SIZE		DFH_SIZE
+
+/* PORT Unordered Message Sub Feature Register Set */
+#define PORT_UMSG_DFH		DFH
+#define PORT_UMSG_SIZE		DFH_SIZE
+
+/* PORT SignalTap Sub Feature Register Set */
+#define PORT_STP_DFH		DFH
+#define PORT_STP_SIZE		DFH_SIZE
+
+/* PORT User AFU Sub Feature Register Set */
+#define PORT_UAFU_DFH		DFH
+#define PORT_UAFU_SIZE		DFH_SIZE
+
+struct feature {
+	const char *name;
+	int resource_index;
+	void __iomem *ioaddr;
+};
+
+struct feature_platform_data {
+	/* list the feature dev to cci_drvdata->port_dev_list. */
+	struct list_head node;
+	struct mutex lock;		/* protect platform data */
+	struct platform_device *dev;
+	unsigned int disable_count;	/* count for port disable */
+
+	int num;			/* number of features */
+	struct feature features[0];
+};
+
+enum fme_feature_id {
+	FME_FEATURE_ID_HEADER = 0x0,
+	FME_FEATURE_ID_THERMAL_MGMT = 0x1,
+	FME_FEATURE_ID_POWER_MGMT = 0x2,
+	FME_FEATURE_ID_GLOBAL_IPERF = 0x3,
+	FME_FEATURE_ID_GLOBAL_ERR = 0x4,
+	FME_FEATURE_ID_PR_MGMT = 0x5,
+	FME_FEATURE_ID_HSSI = 0x6,
+	FME_FEATURE_ID_GLOBAL_DPERF = 0x7,
+	FME_FEATURE_ID_MAX = 0x8,
+};
+
+enum port_feature_id {
+	PORT_FEATURE_ID_HEADER = 0x0,
+	PORT_FEATURE_ID_ERROR = 0x1,
+	PORT_FEATURE_ID_UMSG = 0x2,
+	PORT_FEATURE_ID_PR = 0x3,
+	PORT_FEATURE_ID_STP = 0x4,
+	PORT_FEATURE_ID_UAFU = 0x5,
+	PORT_FEATURE_ID_MAX = 0x6,
+};
+
+#define FME_FEATURE_NUM			FME_FEATURE_ID_MAX
+#define PORT_FEATURE_NUM		PORT_FEATURE_ID_MAX
+
+#define FPGA_FEATURE_DEV_FME		"fpga-dfl-fme"
+#define FPGA_FEATURE_DEV_PORT		"fpga-dfl-port"
+
+static inline int feature_platform_data_size(const int num)
+{
+	return sizeof(struct feature_platform_data) +
+		num * sizeof(struct feature);
+}
+
+int fpga_port_id(struct platform_device *pdev);
+
+static inline int fpga_port_check_id(struct platform_device *pdev,
+				     void *pport_id)
+{
+	return fpga_port_id(pdev) == *(int *)pport_id;
+}
+
+void __fpga_port_enable(struct platform_device *pdev);
+int __fpga_port_disable(struct platform_device *pdev);
+
+static inline void fpga_port_enable(struct platform_device *pdev)
+{
+	struct feature_platform_data *pdata = dev_get_platdata(&pdev->dev);
+
+	mutex_lock(&pdata->lock);
+	__fpga_port_enable(pdev);
+	mutex_unlock(&pdata->lock);
+}
+
+static inline int fpga_port_disable(struct platform_device *pdev)
+{
+	struct feature_platform_data *pdata = dev_get_platdata(&pdev->dev);
+	int ret;
+
+	mutex_lock(&pdata->lock);
+	ret = __fpga_port_disable(pdev);
+	mutex_unlock(&pdata->lock);
+
+	return ret;
+}
+
+static inline int __fpga_port_reset(struct platform_device *pdev)
+{
+	int ret;
+
+	ret = __fpga_port_disable(pdev);
+	if (ret)
+		return ret;
+
+	__fpga_port_enable(pdev);
+
+	return 0;
+}
+
+static inline int fpga_port_reset(struct platform_device *pdev)
+{
+	struct feature_platform_data *pdata = dev_get_platdata(&pdev->dev);
+	int ret;
+
+	mutex_lock(&pdata->lock);
+	ret = __fpga_port_reset(pdev);
+	mutex_unlock(&pdata->lock);
+
+	return ret;
+}
+
+static inline void __iomem *
+get_feature_ioaddr_by_index(struct device *dev, int index)
+{
+	struct feature_platform_data *pdata = dev_get_platdata(dev);
+
+	return pdata->features[index].ioaddr;
+}
+
+static inline bool feature_is_fme(void __iomem *base)
+{
+	u64 v = readq(base + DFH);
+
+	return (FIELD_GET(DFH_TYPE, v) == DFH_TYPE_FIU) &&
+		(FIELD_GET(DFH_ID, v) == DFH_ID_FIU_FME);
+}
+
+static inline bool feature_is_port(void __iomem *base)
+{
+	u64 v = readq(base + DFH);
+
+	return (FIELD_GET(DFH_TYPE, v) == DFH_TYPE_FIU) &&
+		(FIELD_GET(DFH_ID, v) == DFH_ID_FIU_PORT);
+}
+
+/**
+ * fpga_enum_info - FPGA enumeration information
+ *
+ * @dev: parent device.
+ * @dfls: list of device feature lists.
+ */
+struct fpga_enum_info {
+	struct device *dev;
+	struct list_head dfls;
+};
+
+/**
+ * fpga_enum_dfl - FPGA enumeration device feature list information
+ *
+ * @start: base address of this device feature list.
+ * @len: size of this device feature list.
+ * @ioaddr: mapped base address of this device feature list.
+ * @node: node in list of device feature lists.
+ */
+struct fpga_enum_dfl {
+	resource_size_t start;
+	resource_size_t len;
+
+	void __iomem *ioaddr;
+
+	struct list_head node;
+};
+
+struct fpga_enum_info *fpga_enum_info_alloc(struct device *dev);
+int fpga_enum_info_add_dfl(struct fpga_enum_info *info, resource_size_t start,
+			   resource_size_t len, void __iomem *ioaddr);
+void fpga_enum_info_free(struct fpga_enum_info *info);
+
+/**
+ * fpga_cdev - fpga container device
+ * @parent: parent device of this container device.
+ * @region: base fpga region.
+ * @fme_dev: FME feature device under this container device.
+ * @lock: mutex lock to protect the port device list.
+ * @port_dev_list: list of all port feature devices under this container device.
+ */
+struct fpga_cdev {
+	struct device *parent;
+
+	struct fpga_region region;
+
+	struct device *fme_dev;
+
+	struct mutex lock; /* to protect the port device list */
+	struct list_head port_dev_list;
+};
+
+struct fpga_cdev *fpga_enumerate_feature_devs(struct fpga_enum_info *info);
+void fpga_remove_feature_devs(struct fpga_cdev *cdev);
+
+#endif /* __DFL_FPGA_H */
-- 
1.8.3.1

--
To unsubscribe from this list: send the line "unsubscribe linux-api" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Index of Archives]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux