Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752027Ab3IRTC7 (ORCPT ); Wed, 18 Sep 2013 15:02:59 -0400 Received: from co1ehsobe005.messaging.microsoft.com ([216.32.180.188]:24484 "EHLO co1outboundpool.messaging.microsoft.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1751396Ab3IRTC5 (ORCPT ); Wed, 18 Sep 2013 15:02:57 -0400 X-Forefront-Antispam-Report: CIP:66.35.236.232;KIP:(null);UIP:(null);IPV:NLI;H:SJ-ITEXEDGE02.altera.priv.altera.com;RD:none;EFVD:NLI X-SpamScore: 0 X-BigFish: VS0(zz98dI936eI148cI1432I4015Ic8kdbb0idbf2izz1f42h208ch1ee6h1de0h1fdah2073h1202h1e76h1d1ah1d2ah1fc6hzz8275ch1d7338h1de098h17326ah1de097h186068h1d68deh8275bh8275dhz2fh2a8h839h93fhd24hd2bhf0ah1288h12a5h12a9h12bdh137ah139eh13b6h1441h1504h1537h162dh1631h1758h1898h18e1h1946h19b5h1ad9h1b0ah1d0ch1d2eh1d3fh1dfeh1dffh1e1dh1e23h1fe8h1ff5h1155h) Message-ID: <1379530957.23877.11.camel@linux-builds1> Subject: Re: [RFC PATCH] fpga: Introduce new fpga subsystem From: Dinh Nguyen To: Michal Simek CC: , , Alan Tull , Pavel Machek , Greg Kroah-Hartman , Philip Balister , Alessandro Rubini , Mauro Carvalho Chehab , Andrew Morton , Cesar Eduardo Barros , Joe Perches , "David S. Miller" , Stephen Warren , Arnd Bergmann , David Brown , Dom Cobley , , Date: Wed, 18 Sep 2013 14:02:37 -0500 In-Reply-To: References: Content-Type: text/plain; charset="UTF-8" X-Mailer: Evolution 3.2.3-0ubuntu6 Content-Transfer-Encoding: 7bit MIME-Version: 1.0 X-OriginatorOrg: altera.com X-FOPE-CONNECTOR: Id%0$Dn%*$RO%0$TLS%0$FQDN%$TlsDn% Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 20592 Lines: 678 Hi Michal, On Wed, 2013-09-18 at 17:56 +0200, Michal Simek wrote: > This new subsystem should unify all fpga drivers which > do the same things. Load configuration data to fpga > or another programmable logic through common interface. > It doesn't matter if it is MMIO device, gpio bitbanging, > etc. connection. The point is to have the same > inteface for these drivers. > > Signed-off-by: Michal Simek [CC] Alan Tull [CC] Yves Vandervennet Thanks for doing this. Myself and Alan Tull had plans to send this upstream, but we wanted to make sure that this framework would also work on the Xilinx platform as well. Have you had a chance to test this on a SOCFGPA platform? If not, we can go do that. > > --- > MAINTAINERS | 7 + > drivers/Kconfig | 2 + > drivers/Makefile | 1 + > drivers/fpga/Kconfig | 18 ++ > drivers/fpga/Makefile | 5 + > drivers/fpga/fpga-mgr.c | 433 ++++++++++++++++++++++++++++++++++++++++++++++++ > include/linux/fpga.h | 105 ++++++++++++ > 7 files changed, 571 insertions(+) > create mode 100644 drivers/fpga/Kconfig > create mode 100644 drivers/fpga/Makefile > create mode 100644 drivers/fpga/fpga-mgr.c > create mode 100644 include/linux/fpga.h > > diff --git a/MAINTAINERS b/MAINTAINERS > index e61c2e8..5c7597b 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -3427,6 +3427,13 @@ F: include/linux/fmc*.h > F: include/linux/ipmi-fru.h > K: fmc_d.*register > > +FPGA SUBSYSTEM > +M: Michal Simek Since the basic framework of this driver was done by Alan Tull and myself, can you please add Alan and myself as the maintainer here? > +T: git git://git.xilinx.com/linux-xlnx.git > +S: Supported > +F: drivers/fpga/ > +F: include/linux/fpga.h > + > FPU EMULATOR > M: Bill Metzenthen > W: http://floatingpoint.sourceforge.net/emulator/index.html > diff --git a/drivers/Kconfig b/drivers/Kconfig > index aa43b91..17f3caa 100644 > --- a/drivers/Kconfig > +++ b/drivers/Kconfig > @@ -166,4 +166,6 @@ source "drivers/reset/Kconfig" > > source "drivers/fmc/Kconfig" > > +source "drivers/fpga/Kconfig" > + > endmenu > diff --git a/drivers/Makefile b/drivers/Makefile > index ab93de8..2b5e73b 100644 > --- a/drivers/Makefile > +++ b/drivers/Makefile > @@ -152,3 +152,4 @@ obj-$(CONFIG_VME_BUS) += vme/ > obj-$(CONFIG_IPACK_BUS) += ipack/ > obj-$(CONFIG_NTB) += ntb/ > obj-$(CONFIG_FMC) += fmc/ > +obj-$(CONFIG_FPGA) += fpga/ > diff --git a/drivers/fpga/Kconfig b/drivers/fpga/Kconfig > new file mode 100644 > index 0000000..5357645 > --- /dev/null > +++ b/drivers/fpga/Kconfig > @@ -0,0 +1,18 @@ > +# > +# FPGA framework configuration > +# > + > +menu "FPGA devices" > + > +config FPGA > + tristate "FPGA Framework" > + help > + Say Y here if you want support for configuring FPGAs from the > + kernel. The FPGA framework adds a FPGA manager class and FPGA > + manager drivers. > + > +if FPGA Not sure what this is for? Thanks, Dinh > + > +endif > + > +endmenu > diff --git a/drivers/fpga/Makefile b/drivers/fpga/Makefile > new file mode 100644 > index 0000000..3c7f29b > --- /dev/null > +++ b/drivers/fpga/Makefile > @@ -0,0 +1,5 @@ > +# > +# Makefile for the fpga framework and fpga manager drivers. > +# > + > +obj-$(CONFIG_FPGA) += fpga-mgr.o > diff --git a/drivers/fpga/fpga-mgr.c b/drivers/fpga/fpga-mgr.c > new file mode 100644 > index 0000000..7312efd > --- /dev/null > +++ b/drivers/fpga/fpga-mgr.c > @@ -0,0 +1,433 @@ > +/* > + * FPGA Framework > + * > + * Copyright (C) 2013 Xilinx, Inc. > + * > + * based on origin code from > + * Copyright (C) 2013 Altera Corporation > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License as published by > + * the Free Software Foundation; either version 2 of the License, or > + * (at your option) any later version. > + * > + * 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. > + * > + * You should have received a copy of the GNU General Public License > + * along with this program. If not, see . > + */ > + > +#include > +#include > +#include > +#include > +#include > +#include > +#include > + > +static struct idr fpga_mgr_idr; > +static spinlock_t fpga_mgr_idr_lock; > +static struct class *fpga_mgr_class; > + > +/** > + * fpga_mgr_name_show - Show fpga manager name > + * @dev: Pointer to the device structure > + * @attr: Pointer to the device attribute structure > + * @buf: Pointer to the buffer location > + * > + * Returns the number of bytes copied to @buf, a negative error number otherwise > + */ > +static ssize_t fpga_mgr_name_show(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + struct fpga_manager *mgr = dev_get_drvdata(dev); > + > + if (!mgr) > + return -ENODEV; > + > + return snprintf(buf, sizeof(mgr->name), "%s\n", mgr->name); > +} > + > +/** > + * fpga_mgr_status_show - Show fpga manager status > + * @dev: Pointer to the device structure > + * @attr: Pointer to the device attribute structure > + * @buf: Pointer to the buffer location > + * > + * Returns the number of bytes copied to @buf, a negative error number otherwise > + */ > +static ssize_t fpga_mgr_status_show(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + struct fpga_manager *mgr = dev_get_drvdata(dev); > + > + if (!mgr || !mgr->mops || !mgr->mops->status) > + return -ENODEV; > + > + return mgr->mops->status(mgr, buf); > +} > + > +/** > + * fpga_mgr_read - Read data from fpga > + * @mgr: Pointer to the fpga manager structure > + * @buf: Pointer to the buffer location > + * @count: Pointer to the number of copied bytes > + * > + * Function reads fpga bitstream and copy them to output buffer. > + * > + * Returns the number of bytes copied to @buf, a negative error number otherwise > + */ > +static int fpga_mgr_read(struct fpga_manager *mgr, char *buf, ssize_t *count) > +{ > + int ret; > + > + if (test_and_set_bit_lock(FPGA_MGR_DEV_BUSY, &mgr->flags)) > + return -EBUSY; > + > + if (!mgr->mops || !mgr->mops->read) { > + dev_err(mgr->dev, > + "Controller doesn't support read operations!\n"); > + return -EPERM; > + } > + > + if (mgr->mops->read_init) { > + ret = mgr->mops->read_init(mgr); > + if (ret) { > + dev_err(mgr->dev, "Failed in read-init!\n"); > + return ret; > + } > + } > + > + if (mgr->mops->read) { > + ret = mgr->mops->read(mgr, buf, count); > + if (ret) { > + dev_err(mgr->dev, "Failed to read firmware!\n"); > + return ret; > + } > + } > + > + if (mgr->mops->read_complete) { > + ret = mgr->mops->read_complete(mgr); > + if (ret) { > + dev_err(mgr->dev, "Failed in read-complete!\n"); > + return ret; > + } > + } > + > + clear_bit_unlock(FPGA_MGR_DEV_BUSY, &mgr->flags); > + > + return 0; > +} > + > +/** > + * fpga_mgr_attr_read - Read data from fpga > + * @dev: Pointer to the device structure > + * @attr: Pointer to the device attribute structure > + * @buf: Pointer to the buffer location > + * > + * Function reads fpga bitstream and copy them to output buffer > + * > + * Returns the number of bytes copied to @buf, a negative error number otherwise > + */ > +static ssize_t fpga_mgr_attr_read(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + struct fpga_manager *mgr = dev_get_drvdata(dev); > + ssize_t count; > + int ret; > + > + if (mgr && mgr->fpga_read) > + ret = mgr->fpga_read(mgr, buf, &count); > + > + return ret == 0 ? count : -EPERM; > +} > + > +/** > + * fpga_mgr_write - Write data to fpga > + * @mgr: Pointer to the fpga manager structure > + * @fw_name: Pointer to the buffer location with bistream firmware filename > + * > + * @buf contains firmware filename which is loading through firmware > + * interface and passed to the fpga driver. > + * > + * Returns string lenght added to @fw_name, a negative error number otherwise > + */ > +static int fpga_mgr_write(struct fpga_manager *mgr, const char *fw_name) > +{ > + int ret = 0; > + const struct firmware *fw; > + > + if (!fw_name || !strlen(fw_name)) { > + dev_err(mgr->dev, "Firmware name is not specified!\n"); > + return -EINVAL; > + } > + > + if (!mgr->mops || !mgr->mops->write) { > + dev_err(mgr->dev, > + "Controller doesn't support write operations!\n"); > + return -EPERM; > + } > + > + ret = request_firmware(&fw, fw_name, mgr->dev); > + if (ret) { > + dev_err(mgr->dev, "Failed to load firmware %s!\n", fw_name); > + return ret; > + } > + > + if (test_and_set_bit_lock(FPGA_MGR_DEV_BUSY, &mgr->flags)) > + return -EBUSY; > + > + /* Init controller */ > + if (mgr->mops->write_init) { > + ret = mgr->mops->write_init(mgr); > + if (ret) { > + dev_err(mgr->dev, "Failed to write_init!\n"); > + return ret; > + } > + } > + > + /* Do write */ > + ret = mgr->mops->write(mgr, (void *)fw->data, fw->size); > + if (ret) { > + dev_err(mgr->dev, "Failed to write data!\n"); > + return ret; > + } > + > + if (mgr->mops->write_complete) { > + ret = mgr->mops->write_complete(mgr); > + if (ret) { > + dev_err(mgr->dev, "Failed to write_init!\n"); > + return ret; > + } > + } > + > + release_firmware(fw); > + > + clear_bit_unlock(FPGA_MGR_DEV_BUSY, &mgr->flags); > + > + return 0; > +} > + > +/** > + * fpga_mgr_attr_write - Write data to fpga > + * @dev: Pointer to the device structure > + * @attr: Pointer to the device attribute structure > + * @buf: Pointer to the buffer location with bistream firmware filename > + * @count: Number of characters in @buf > + * > + * @buf contains firmware filename which is loading through firmware > + * interface and passed to the fpga driver. > + * > + * Returns string lenght added to @buf, a negative error number otherwise > + */ > +static ssize_t fpga_mgr_attr_write(struct device *dev, > + struct device_attribute *attr, > + const char *buf, size_t count) > +{ > + struct fpga_manager *mgr = dev_get_drvdata(dev); > + int ret; > + > + if (mgr && mgr->fpga_write) > + ret = mgr->fpga_write(mgr, buf); > + > + return ret == 0 ? strlen(buf) : -EPERM; > +} > + > +static struct device_attribute fpga_mgr_attrs[] = { > + __ATTR(name, S_IRUGO, fpga_mgr_name_show, NULL), > + __ATTR(status, S_IRUGO, fpga_mgr_status_show, NULL), > + __ATTR(fpga, S_IRUGO | S_IWUGO, fpga_mgr_attr_read, > + fpga_mgr_attr_write), > + __ATTR_NULL > +}; > + > +/** > + * fpga_mgr_register: Register new fpga manager > + * @pdev: Pointer to the platform device of fpga manager > + * @mops: Pointer to the fpga manager operations > + * @name: Name of fpga manager > + * @priv: Pointer to the fpga manager private data > + * > + * Function register fpga manager with uniq ID and create device > + * for accessing the device. > + * > + * Returns 0 on success, a negative error number otherwise > + */ > +int fpga_mgr_register(struct platform_device *pdev, > + struct fpga_manager_ops *mops, char *name, void *priv) > +{ > + struct fpga_manager *mgr; > + int ret; > + > + if (!mops) { > + dev_err(&pdev->dev, "Register failed: NO fpga_manager_ops!\n"); > + return -EINVAL; > + } > + > + if (!name || !strlen(name)) { > + dev_err(&pdev->dev, "Register failed: NO name specific!\n"); > + return -EINVAL; > + } > + > + mgr = devm_kzalloc(&pdev->dev, sizeof(*mgr), GFP_KERNEL); > + if (!mgr) { > + dev_err(&pdev->dev, "Failed to alloc mgr struct!\n"); > + return -ENOMEM; > + } > + > + /* Get unique number */ > + idr_preload(GFP_KERNEL); > + spin_lock(&fpga_mgr_idr_lock); > + ret = idr_alloc(&fpga_mgr_idr, mgr, 0, 0, GFP_NOWAIT); > + if (ret >= 0) > + mgr->nr = ret; > + spin_unlock(&fpga_mgr_idr_lock); > + idr_preload_end(); > + if (ret < 0) > + return ret; > + > + /* Setup all necessary information */ > + mgr->dev = &pdev->dev; > + mgr->mops = mops; > + mgr->priv = priv; > + mgr->fpga_read = fpga_mgr_read; > + mgr->fpga_write = fpga_mgr_write; > + strlcpy(mgr->name, name, sizeof(mgr->name)); > + > + mgr->dev = device_create(fpga_mgr_class, get_device(&pdev->dev), > + MKDEV(0, 0), mgr, "fpga%d", mgr->nr); > + if (IS_ERR(mgr->dev)) { > + spin_lock(&fpga_mgr_idr_lock); > + idr_remove(&fpga_mgr_idr, mgr->nr); > + spin_unlock(&fpga_mgr_idr_lock); > + > + dev_err(&pdev->dev, "Failed to create device!\n"); > + return PTR_ERR(mgr->dev); > + } > + > + platform_set_drvdata(pdev, mgr); > + > + dev_info(&pdev->dev, "Fpga manager [%s] registered as minor %d\n", > + mgr->name, mgr->nr); > + > + return 0; > +} > +EXPORT_SYMBOL_GPL(fpga_mgr_register); > + > +/** > + * fpga_mgr_unregister: Remove fpga manager > + * @pdev: Pointer to the platform device of fpga manager > + * > + * Function unregister fpga manager and release all temporary structures > + * > + * Returns 0 for all cases > + */ > +int fpga_mgr_unregister(struct platform_device *pdev) > +{ > + struct fpga_manager *mgr = platform_get_drvdata(pdev); > + > + if (mgr && mgr->mops && mgr->mops->fpga_remove) > + mgr->mops->fpga_remove(mgr); > + > + device_unregister(mgr->dev); > + > + spin_lock(&fpga_mgr_idr_lock); > + idr_remove(&fpga_mgr_idr, mgr->nr); > + spin_unlock(&fpga_mgr_idr_lock); > + > + return 0; > +} > +EXPORT_SYMBOL_GPL(fpga_mgr_unregister); > + > +/** > + * of_dev_node_match: Compare associated device tree node with > + * @dev: Pointer to the device > + * @data: Pointer to the device tree node > + * > + * Returns 1 on success > + */ > +static int of_dev_node_match(struct device *dev, void *data) > +{ > + return dev->of_node == data; > +} > + > +/** > + * of_find_fpga_mgr_by_node - Find the fpga_manager associated with a node > + * @node: Pointer to the device tree node > + * > + * Returns fpga_manager pointer, or NULL if not found > + */ > +struct fpga_manager *of_find_fpga_mgr_by_node(struct device_node *node) > +{ > + struct device *dev; > + struct fpga_manager *mgr; > + > + dev = bus_find_device(&platform_bus_type, NULL, node, > + of_dev_node_match); > + if (!dev) > + return NULL; > + > + mgr = dev_get_drvdata(dev); > + > + return mgr ? mgr : NULL; > +} > +EXPORT_SYMBOL(of_find_fpga_mgr_by_node); > + > +/** > + * of_find_fpga_mgr_by_phandle - Find the fpga_manager associated with a node > + * @node: Pointer to the device tree node > + * @phandle_name: Pointer to the phandle_name > + * > + * Returns fpga_manager pointer, or NULL if not found > + */ > +struct fpga_manager *of_find_fpga_mgr_by_phandle(struct device_node *node, > + const char *phandle_name) > +{ > + struct device_node *fpga_node; > + > + fpga_node = of_parse_phandle(node, phandle_name, 0); > + if (!fpga_node) { > + pr_err("Phandle not found\n"); > + return ERR_PTR(-ENODEV); > + } > + > + return of_find_fpga_mgr_by_node(fpga_node); > +} > +EXPORT_SYMBOL(of_find_fpga_mgr_by_phandle); > + > +/** > + * fpga_mgr_init: Create fpga class > + */ > +static int __init fpga_mgr_init(void) > +{ > + pr_info("FPGA Manager framework driver\n"); > + > + fpga_mgr_class = class_create(THIS_MODULE, "fpga"); > + if (IS_ERR(fpga_mgr_class)) > + return PTR_ERR(fpga_mgr_class); > + > + idr_init(&fpga_mgr_idr); > + spin_lock_init(&fpga_mgr_idr_lock); > + > + fpga_mgr_class->dev_attrs = fpga_mgr_attrs; > + > + return 0; > +} > +subsys_initcall(fpga_mgr_init); > + > +/** > + * fpga_mgr_exit: Destroy fpga class > + */ > +static void __exit fpga_mgr_exit(void) > +{ > + class_destroy(fpga_mgr_class); > + idr_destroy(&fpga_mgr_idr); > +} > +module_exit(fpga_mgr_exit); > + > +MODULE_AUTHOR("Xilinx, Inc."); > +MODULE_DESCRIPTION("FPGA Manager framework driver"); > +MODULE_LICENSE("GPL v2"); > diff --git a/include/linux/fpga.h b/include/linux/fpga.h > new file mode 100644 > index 0000000..970c42e > --- /dev/null > +++ b/include/linux/fpga.h > @@ -0,0 +1,105 @@ > +/* > + * FPGA Framework > + * > + * Copyright (C) 2013 Xilinx, Inc. > + * > + * based on origin code from > + * Copyright (C) 2013 Altera Corporation > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License as published by > + * the Free Software Foundation; either version 2 of the License, or > + * (at your option) any later version. > + * > + * 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. > + * > + * You should have received a copy of the GNU General Public License > + * along with this program. If not, see . > + */ > + > +#include > +#include > +#include > + > +#ifndef _LINUX_FPGA_H > +#define _LINUX_FPGA_H > + > +struct fpga_manager; > + > +/** > + * struct fpga_manager_ops - FPGA manager driver operations > + * > + * @status: The function to call for getting fpga manager status. > + * Returns number of characters written to the @buf and > + * a string of the FPGA's status in @bug. > + * @read_init: The function to call for preparing the FPGA for reading > + * its confuration data. Returns 0 on success, a negative error > + * number otherwise. > + * @read: Read configuration data from the FPGA. Return 0 on success, > + * a negative error number otherwise. > + * @read_complete: Return FPGA to a default state after reading is done. > + * Return 0 on success, a negative error number otherwise. > + * @write_init: Prepare the FPGA to receive configuration data. > + * Return 0 on success, a negative error number otherwise. > + * @write: Write count bytes of configuration data to the FPGA placed in @buf. > + * Return 0 on success, a negative error number otherwise. > + * @write_complete: Return FPGA to default state after writing is done. > + * Return 0 on success, a negative error number otherwise. > + * @fpga_remove: Set FPGA into a specific state during driver remove. > + * > + * fpga_manager_ops are the low level functions implemented by a specific > + * fpga manager driver. Leaving any of these out that aren't needed is fine > + * as they are all tested for NULL before being called. > + */ > +struct fpga_manager_ops { > + int (*status)(struct fpga_manager *mgr, char *buf); > + int (*read_init)(struct fpga_manager *mgr); > + int (*read)(struct fpga_manager *mgr, char *buf, ssize_t *count); > + int (*read_complete)(struct fpga_manager *mgr); > + int (*write_init)(struct fpga_manager *mgr); > + int (*write)(struct fpga_manager *mgr, char *buf, ssize_t count); > + int (*write_complete)(struct fpga_manager *mgr); > + void (*fpga_remove)(struct fpga_manager *mgr); > +}; > + > +/* flag bits */ > +#define FPGA_MGR_DEV_BUSY 0 > + > +/** > + * struct fpga_manager - FPGA manager driver structure > + * > + * @name: Name of fpga manager > + * @dev: Pointer to the device structure > + * @mops: Pointer to the fpga manager operations > + * @priv: Pointer to fpga manager private data > + * @nr: Unique manager number in the system > + * @flags: For saving temporary > + * @fpga_read: The function to call for reading configuration data from > + * the FPGA. > + * @fpga_write: The function to call for writing configuration data to the FPGA. > + */ > +struct fpga_manager { > + char name[48]; > + struct device *dev; > + struct fpga_manager_ops *mops; > + void *priv; > + unsigned int nr; > + unsigned long flags; > + int (*fpga_read)(struct fpga_manager *mgr, char *buf, > + ssize_t *count); > + int (*fpga_write)(struct fpga_manager *mgr, const char *fw_name); > +}; > + > +int fpga_mgr_register(struct platform_device *pdev, > + struct fpga_manager_ops *mops, char *name, void *priv); > + > +int fpga_mgr_unregister(struct platform_device *pdev); > + > +struct fpga_manager *of_find_fpga_mgr_by_node(struct device_node *node); > +struct fpga_manager *of_find_fpga_mgr_by_phandle(struct device_node *node, > + const char *phandle_name); > + > +#endif /*_LINUX_FPGA_H */ > -- > 1.8.2.3 > -- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/