[PATCH] NVMe: Add a character device for each nvme device
Keith Busch
keith.busch at intel.com
Thu Aug 2 15:10:06 EDT 2012
Registers a character device for the nvme module and creates character
files as /dev/nvmeN for each nvme device probed, where N is the device
instance. The character devices support nvme admin ioctl commands so
that nvme devices without namespaces can be managed.
Signed-off-by: Keith Busch <keith.busch at intel.com>
---
drivers/block/nvme.c | 96 ++++++++++++++++++++++++++++++++++++++++++++++---
1 files changed, 90 insertions(+), 6 deletions(-)
diff --git a/drivers/block/nvme.c b/drivers/block/nvme.c
index 3278fbd..3d13d4b 100644
--- a/drivers/block/nvme.c
+++ b/drivers/block/nvme.c
@@ -20,6 +20,7 @@
#include <linux/bio.h>
#include <linux/bitops.h>
#include <linux/blkdev.h>
+#include <linux/cdev.h>
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/fs.h>
@@ -45,6 +46,7 @@
#define SQ_SIZE(depth) (depth * sizeof(struct nvme_command))
#define CQ_SIZE(depth) (depth * sizeof(struct nvme_completion))
#define NVME_MINORS 64
+#define NVME_MAX_DEVS 1024
#define NVME_IO_TIMEOUT (5 * HZ)
#define ADMIN_TIMEOUT (60 * HZ)
@@ -54,9 +56,15 @@ module_param(nvme_major, int, 0);
static int use_threaded_interrupts;
module_param(use_threaded_interrupts, int, 0);
+static int nvme_char_major;
+module_param(nvme_char_major, int, 0);
+
static DEFINE_SPINLOCK(dev_list_lock);
static LIST_HEAD(dev_list);
static struct task_struct *nvme_thread;
+static struct class *nvme_char_cl;
+
+static void nvme_remove_dev(struct kref *kref);
/*
* Represents an NVM Express device. Each nvme_dev is a PCI function.
@@ -75,6 +83,7 @@ struct nvme_dev {
struct msix_entry *entry;
struct nvme_bar __iomem *bar;
struct list_head namespaces;
+ struct kref kref;
char serial[20];
char model[40];
char firmware_rev[8];
@@ -1226,6 +1235,54 @@ static const struct block_device_operations nvme_fops = {
.compat_ioctl = nvme_ioctl,
};
+static int nvme_char_open(struct inode *inode, struct file *f)
+{
+ struct nvme_dev *dev;
+ int instance = iminor(f->f_dentry->d_inode);
+
+ spin_lock(&dev_list_lock);
+ list_for_each_entry(dev, &dev_list, node) {
+ if (dev->instance == instance) {
+ kref_get(&dev->kref);
+ f->private_data = dev;
+ break;
+ }
+ }
+ spin_unlock(&dev_list_lock);
+
+ if (!f->private_data)
+ return -ENXIO;
+
+ return 0;
+}
+
+static int nvme_char_release(struct inode *inode, struct file *f)
+{
+ struct nvme_dev *dev = f->private_data;
+ kref_put(&dev->kref, nvme_remove_dev);
+ return 0;
+}
+
+static long nvme_char_ioctl(struct file *f, unsigned int cmd, unsigned long arg)
+{
+ struct nvme_dev *dev = f->private_data;
+
+ switch (cmd) {
+ case NVME_IOCTL_ADMIN_CMD:
+ return nvme_user_admin_cmd(dev, (void __user *)arg);
+ default:
+ return -ENOTTY;
+ }
+}
+
+static const struct file_operations nvme_char_fops = {
+ .owner = THIS_MODULE,
+ .open = nvme_char_open,
+ .release = nvme_char_release,
+ .unlocked_ioctl = nvme_char_ioctl,
+ .compat_ioctl = nvme_char_ioctl,
+};
+
static void nvme_timeout_ios(struct nvme_queue *nvmeq)
{
int depth = nvmeq->q_depth - 1;
@@ -1664,6 +1721,11 @@ static int __devinit nvme_probe(struct pci_dev *pdev,
if (result)
goto delete;
+ kref_init(&dev->kref);
+
+ device_create(nvme_char_cl, &pdev->dev,
+ MKDEV(nvme_char_major, dev->instance),
+ NULL, "nvme%d", dev->instance);
return 0;
delete:
@@ -1688,21 +1750,28 @@ static int __devinit nvme_probe(struct pci_dev *pdev,
return result;
}
-static void __devexit nvme_remove(struct pci_dev *pdev)
+static void nvme_remove_dev(struct kref *kref)
{
- struct nvme_dev *dev = pci_get_drvdata(pdev);
+ struct nvme_dev *dev = container_of(kref, struct nvme_dev, kref);
nvme_dev_remove(dev);
- pci_disable_msix(pdev);
+ device_destroy(nvme_char_cl, MKDEV(nvme_char_major, dev->instance));
+ pci_disable_msix(dev->pci_dev);
iounmap(dev->bar);
nvme_release_instance(dev);
nvme_release_prp_pools(dev);
- pci_disable_device(pdev);
- pci_release_regions(pdev);
+ pci_disable_device(dev->pci_dev);
+ pci_release_regions(dev->pci_dev);
kfree(dev->queues);
kfree(dev->entry);
kfree(dev);
}
+static void __devexit nvme_remove(struct pci_dev *pdev)
+{
+ struct nvme_dev *dev = pci_get_drvdata(pdev);
+ kref_put(&dev->kref, nvme_remove_dev);
+}
+
/* These functions are yet to be implemented */
#define nvme_error_detected NULL
#define nvme_dump_registers NULL
@@ -1753,11 +1822,24 @@ static int __init nvme_init(void)
else if (result > 0)
nvme_major = result;
+ result = __register_chrdev(nvme_char_major, 0, NVME_MAX_DEVS, "nvme",
+ &nvme_char_fops);
+ if (result < 0)
+ goto unregister_blkdev;
+ else if (result > 0)
+ nvme_char_major = result;
+ nvme_char_cl = class_create(THIS_MODULE, "nvme");
+ if (!nvme_char_cl)
+ goto unregister_chrdev;
result = pci_register_driver(&nvme_driver);
if (result)
- goto unregister_blkdev;
+ goto destroy_class;
return 0;
+ destroy_class:
+ class_destroy(nvme_char_cl);
+ unregister_chrdev:
+ __unregister_chrdev(nvme_char_major, 0, NVME_MAX_DEVS, "nvme");
unregister_blkdev:
unregister_blkdev(nvme_major, "nvme");
kill_kthread:
@@ -1768,6 +1850,8 @@ static int __init nvme_init(void)
static void __exit nvme_exit(void)
{
pci_unregister_driver(&nvme_driver);
+ class_destroy(nvme_char_cl);
+ __unregister_chrdev(nvme_char_major, 0, NVME_MAX_DEVS, "nvme");
unregister_blkdev(nvme_major, "nvme");
kthread_stop(nvme_thread);
}
--
1.7.0.4
More information about the Linux-nvme
mailing list