]> www.infradead.org Git - users/sagi/nvme-cli.git/commitdiff
Add LightNVM adminstration and diagnose support
authorMatias Bjørling <m@bjorling.me>
Mon, 23 May 2016 12:44:00 +0000 (14:44 +0200)
committerMatias Bjørling <m@bjorling.me>
Thu, 2 Jun 2016 12:46:26 +0000 (14:46 +0200)
Add support for identify geometry, get bad block interface and
administration interface for LightNVM. This patch adds the following
commands:

 - (lnvm-id-ns) Identify geometry of a LightNVM device.
 - (lnvm-diag-bbtbl) Retrieve bad block table from LightNVM device.
 - (lnvm-info) Get information about modules loaded, and available
               devices.
 - (lnvm-init) Initialize LightNVM device with a media manager.
 - (lnvm-create) Create targets on top of registered devices.
 - (lnvm-remove) Remove a registered target.
 - (lnvm-factory) Factory initialize a LightNVM device.

Signed-off-by: Matias Bjørling <m@bjorling.me>
Makefile
linux/lightnvm.h [new file with mode: 0644]
nvme-lightnvm.c [new file with mode: 0644]
nvme-lightnvm.h [new file with mode: 0644]
nvme.c

index 8463766c6872f20310a17b5d32f6dfbfd3688df3..d26a21118c6aacf1f01805347273a19dd57584ad 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -30,8 +30,8 @@ override CFLAGS += -DNVME_VERSION='"$(NVME_VERSION)"'
 
 NVME_DPKG_VERSION=1~`lsb_release -sc`
 
-nvme: nvme.c ./linux/nvme.h argconfig.o suffix.o nvme-print.o nvme-ioctl.o NVME-VERSION-FILE
-       $(CC) $(CPPFLAGS) $(CFLAGS) nvme.c $(LDFLAGS) -o $(NVME) argconfig.o suffix.o nvme-print.o nvme-ioctl.o
+nvme: nvme.c ./linux/nvme.h argconfig.o suffix.o nvme-print.o nvme-ioctl.o nvme-lightnvm.o NVME-VERSION-FILE
+       $(CC) $(CPPFLAGS) $(CFLAGS) nvme.c $(LDFLAGS) -o $(NVME) argconfig.o suffix.o nvme-print.o nvme-ioctl.o nvme-lightnvm.o
 
 nvme-ioctl.o: nvme-ioctl.c nvme-ioctl.h
        $(CC) $(CPPFLAGS) $(CFLAGS) -c nvme-ioctl.c
@@ -45,6 +45,9 @@ suffix.o: $(SRC)/suffix.c $(SRC)/suffix.h
 nvme-print.o: nvme-print.c nvme-print.h
        $(CC) $(CPPFLAGS) $(CFLAGS) -c nvme-print.c
 
+nvme-lightnvm.o: nvme-lightnvm.h nvme-lightnvm.h
+       $(CC) $(CPPFLAGS) $(CFLAGS) -c nvme-lightnvm.c
+
 doc: $(NVME)
        $(MAKE) -C Documentation
 
diff --git a/linux/lightnvm.h b/linux/lightnvm.h
new file mode 100644 (file)
index 0000000..774a431
--- /dev/null
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2015 CNEX Labs.  All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * 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; see the file COPYING.  If not, write to
+ * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139,
+ * USA.
+ */
+
+#ifndef _UAPI_LINUX_LIGHTNVM_H
+#define _UAPI_LINUX_LIGHTNVM_H
+
+#ifdef __KERNEL__
+#include <linux/kernel.h>
+#include <linux/ioctl.h>
+#else /* __KERNEL__ */
+#include <stdio.h>
+#include <sys/ioctl.h>
+#define DISK_NAME_LEN 32
+#endif /* __KERNEL__ */
+
+#include <linux/types.h>
+#include <linux/ioctl.h>
+
+#define NVM_TTYPE_NAME_MAX 48
+#define NVM_TTYPE_MAX 63
+#define NVM_MMTYPE_LEN 8
+
+#define NVM_CTRL_FILE "/dev/lightnvm/control"
+
+struct nvm_ioctl_info_tgt {
+       __u32 version[3];
+       __u32 reserved;
+       char tgtname[NVM_TTYPE_NAME_MAX];
+};
+
+struct nvm_ioctl_info {
+       __u32 version[3];       /* in/out - major, minor, patch */
+       __u16 tgtsize;          /* number of targets */
+       __u16 reserved16;       /* pad to 4K page */
+       __u32 reserved[12];
+       struct nvm_ioctl_info_tgt tgts[NVM_TTYPE_MAX];
+};
+
+enum {
+       NVM_DEVICE_ACTIVE = 1 << 0,
+};
+
+struct nvm_ioctl_device_info {
+       char devname[DISK_NAME_LEN];
+       char bmname[NVM_TTYPE_NAME_MAX];
+       __u32 bmversion[3];
+       __u32 flags;
+       __u32 reserved[8];
+};
+
+struct nvm_ioctl_get_devices {
+       __u32 nr_devices;
+       __u32 reserved[31];
+       struct nvm_ioctl_device_info info[31];
+};
+
+struct nvm_ioctl_create_simple {
+       __u32 lun_begin;
+       __u32 lun_end;
+};
+
+enum {
+       NVM_CONFIG_TYPE_SIMPLE = 0,
+};
+
+struct nvm_ioctl_create_conf {
+       __u32 type;
+       union {
+               struct nvm_ioctl_create_simple s;
+       };
+};
+
+struct nvm_ioctl_create {
+       char dev[DISK_NAME_LEN];                /* open-channel SSD device */
+       char tgttype[NVM_TTYPE_NAME_MAX];       /* target type name */
+       char tgtname[DISK_NAME_LEN];            /* dev to expose target as */
+
+       __u32 flags;
+
+       struct nvm_ioctl_create_conf conf;
+};
+
+struct nvm_ioctl_remove {
+       char tgtname[DISK_NAME_LEN];
+
+       __u32 flags;
+};
+
+struct nvm_ioctl_dev_init {
+       char dev[DISK_NAME_LEN];                /* open-channel SSD device */
+       char mmtype[NVM_MMTYPE_LEN];            /* register to media manager */
+
+       __u32 flags;
+};
+
+enum {
+       NVM_FACTORY_ERASE_ONLY_USER     = 1 << 0, /* erase only blocks used as
+                                                  * host blks or grown blks */
+       NVM_FACTORY_RESET_HOST_BLKS     = 1 << 1, /* remove host blk marks */
+       NVM_FACTORY_RESET_GRWN_BBLKS    = 1 << 2, /* remove grown blk marks */
+       NVM_FACTORY_NR_BITS             = 1 << 3, /* stops here */
+};
+
+struct nvm_ioctl_dev_factory {
+       char dev[DISK_NAME_LEN];
+
+       __u32 flags;
+};
+
+/* The ioctl type, 'L', 0x20 - 0x2F documented in ioctl-number.txt */
+enum {
+       /* top level cmds */
+       NVM_INFO_CMD = 0x20,
+       NVM_GET_DEVICES_CMD,
+
+       /* device level cmds */
+       NVM_DEV_CREATE_CMD,
+       NVM_DEV_REMOVE_CMD,
+
+       /* Init a device to support LightNVM media managers */
+       NVM_DEV_INIT_CMD,
+
+       /* Factory reset device */
+       NVM_DEV_FACTORY_CMD,
+};
+
+#define NVM_IOCTL 'L' /* 0x4c */
+
+#define NVM_INFO               _IOWR(NVM_IOCTL, NVM_INFO_CMD, \
+                                               struct nvm_ioctl_info)
+#define NVM_GET_DEVICES                _IOR(NVM_IOCTL, NVM_GET_DEVICES_CMD, \
+                                               struct nvm_ioctl_get_devices)
+#define NVM_DEV_CREATE         _IOW(NVM_IOCTL, NVM_DEV_CREATE_CMD, \
+                                               struct nvm_ioctl_create)
+#define NVM_DEV_REMOVE         _IOW(NVM_IOCTL, NVM_DEV_REMOVE_CMD, \
+                                               struct nvm_ioctl_remove)
+#define NVM_DEV_INIT           _IOW(NVM_IOCTL, NVM_DEV_INIT_CMD, \
+                                               struct nvm_ioctl_dev_init)
+#define NVM_DEV_FACTORY                _IOW(NVM_IOCTL, NVM_DEV_FACTORY_CMD, \
+                                               struct nvm_ioctl_dev_factory)
+
+#define NVM_VERSION_MAJOR      1
+#define NVM_VERSION_MINOR      0
+#define NVM_VERSION_PATCHLEVEL 0
+
+#endif
diff --git a/nvme-lightnvm.c b/nvme-lightnvm.c
new file mode 100644 (file)
index 0000000..3c8ed44
--- /dev/null
@@ -0,0 +1,393 @@
+/*
+ * lightnvm.c -- LightNVM NVMe integration.
+ *
+ * Copyright (c) 2016, CNEX Labs.
+ *
+ * Written by Matias Bjoerling <matias@cnexlabs.com>
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <string.h>
+#include <errno.h>
+#include <inttypes.h>
+
+#include "nvme-lightnvm.h"
+#include "nvme-print.h"
+#include "nvme-ioctl.h"
+
+static int lnvm_open(void)
+{
+       char dev[FILENAME_MAX] = NVM_CTRL_FILE;
+       int fd;
+
+       fd = open(dev, O_WRONLY);
+       if (fd < 0) {
+               printf("Failed to open LightNVM mgmt interface\n");
+               perror(dev);
+               exit(errno);
+       }
+
+       return fd;
+}
+
+static void lnvm_close(int fd)
+{
+       close(fd);
+}
+
+int lnvm_do_init(char *dev, char *mmtype)
+{
+       struct nvm_ioctl_dev_init init;
+       int fd, ret;
+
+       fd = lnvm_open();
+
+       memset(&init, 0, sizeof(struct nvm_ioctl_dev_init));
+       strncpy(init.dev, dev, DISK_NAME_LEN);
+       strncpy(init.mmtype, mmtype, NVM_MMTYPE_LEN);
+
+       ret = ioctl(fd, NVM_DEV_INIT, &init);
+       switch (errno) {
+       case EINVAL:
+               printf("Initialization failed.\n");
+               break;
+       case EEXIST:
+               printf("Device has already been initialized.\n");
+               break;
+       case 0:
+               break;
+       default:
+               printf("Unknown error occurred (%d)\n", errno);
+               break;
+       }
+
+       lnvm_close(fd);
+
+       return ret;
+}
+
+int lnvm_do_list_devices(void)
+{
+       struct nvm_ioctl_get_devices devs;
+       int fd, ret, i;
+
+       fd = lnvm_open();
+
+       ret = ioctl(fd, NVM_GET_DEVICES, &devs);
+       if (ret)
+               return ret;
+
+       printf("Number of devices: %u\n", devs.nr_devices);
+       printf("%-12s\t%-12s\tVersion\n", "Device", "Block manager");
+
+       for (i = 0; i < devs.nr_devices && i < 31; i++) {
+               struct nvm_ioctl_device_info *info = &devs.info[i];
+
+               printf("%-12s\t%-12s\t(%u,%u,%u)\n", info->devname, info->bmname,
+                               info->bmversion[0], info->bmversion[1],
+                               info->bmversion[2]);
+       }
+
+       lnvm_close(fd);
+
+       return 0;
+}
+
+int lnvm_do_info(void)
+{
+       struct nvm_ioctl_info c;
+       int fd, ret, i;
+
+       fd = lnvm_open();
+
+       memset(&c, 0, sizeof(struct nvm_ioctl_info));
+       ret = ioctl(fd, NVM_INFO, &c);
+       if (ret)
+               return ret;
+
+       printf("LightNVM (%u,%u,%u). %u target type(s) registered.\n",
+                       c.version[0], c.version[1], c.version[2], c.tgtsize);
+       printf("Type\tVersion\n");
+
+       for (i = 0; i < c.tgtsize; i++) {
+               struct nvm_ioctl_info_tgt *tgt = &c.tgts[i];
+
+               printf("%s\t(%u,%u,%u)\n",
+                               tgt->tgtname, tgt->version[0], tgt->version[1],
+                               tgt->version[2]);
+       }
+
+       lnvm_close(fd);
+       return 0;
+}
+
+int lnvm_do_create_tgt(char *devname, char *tgtname, char *tgttype,
+                                               int lun_begin, int lun_end)
+{
+       struct nvm_ioctl_create c;
+       int fd, ret;
+
+       fd = lnvm_open();
+
+       strncpy(c.dev, devname, DISK_NAME_LEN);
+       strncpy(c.tgtname, tgtname, DISK_NAME_LEN);
+       strncpy(c.tgttype, tgttype, NVM_TTYPE_NAME_MAX);
+       c.flags = 0;
+       c.conf.type = 0;
+       c.conf.s.lun_begin = lun_begin;
+       c.conf.s.lun_end = lun_end;
+
+       ret = ioctl(fd, NVM_DEV_CREATE, &c);
+       if (ret)
+               fprintf(stderr, "Creation of target failed. Please see dmesg.\n");
+
+       lnvm_close(fd);
+       return ret;
+}
+
+int lnvm_do_remove_tgt(char *tgtname)
+{
+       struct nvm_ioctl_remove c;
+       int fd, ret;
+
+       fd = lnvm_open();
+
+       strncpy(c.tgtname, tgtname, DISK_NAME_LEN);
+       c.flags = 0;
+
+       ret = ioctl(fd, NVM_DEV_REMOVE, &c);
+       if (ret)
+               fprintf(stderr, "Remove of target failed. Please see dmesg.\n");
+
+       lnvm_close(fd);
+       return ret;
+}
+
+int lnvm_do_factory_init(char *devname, int erase_only_marked,
+                                               int clear_host_marks,
+                                               int clear_bb_marks)
+{
+       struct nvm_ioctl_dev_factory fact;
+       int fd, ret;
+
+       fd = lnvm_open();
+
+       memset(&fact, 0, sizeof(struct nvm_ioctl_dev_factory));
+
+       strncpy(fact.dev, devname, DISK_NAME_LEN);
+       if (erase_only_marked)
+               fact.flags |= NVM_FACTORY_ERASE_ONLY_USER;
+       if (clear_host_marks)
+               fact.flags |= NVM_FACTORY_RESET_HOST_BLKS;
+       if (clear_bb_marks)
+               fact.flags |= NVM_FACTORY_RESET_GRWN_BBLKS;
+
+       ret = ioctl(fd, NVM_DEV_FACTORY, &fact);
+       switch (errno) {
+       case EINVAL:
+               fprintf(stderr, "Factory reset failed.\n");
+               break;
+       case 0:
+               break;
+       default:
+               fprintf(stderr, "Unknown error occurred (%d)\n", errno);
+               break;
+       }
+
+       lnvm_close(fd);
+       return ret;
+}
+
+void show_lnvm_id_grp(struct nvme_nvm_id_group *grp)
+{
+       printf(" mtype   : %d\n", grp->mtype);
+       printf(" fmtype  : %d\n", grp->fmtype);
+       printf(" chnls   : %d\n", grp->num_ch);
+       printf(" luns    : %d\n", grp->num_lun);
+       printf(" plns    : %d\n", grp->num_pln);
+       printf(" blks    : %d\n", (uint16_t)le16toh(grp->num_blk));
+       printf(" pgs     : %d\n", (uint16_t)le16toh(grp->num_pg));
+       printf(" fpg_sz  : %d\n", (uint16_t)le16toh(grp->fpg_sz));
+       printf(" csecs   : %d\n", (uint16_t)le16toh(grp->csecs));
+       printf(" sos     : %d\n", (uint16_t)le16toh(grp->sos));
+       printf(" trdt    : %d\n", (uint32_t)le32toh(grp->trdt));
+       printf(" trdm    : %d\n", (uint32_t)le32toh(grp->trdm));
+       printf(" tprt    : %d\n", (uint32_t)le32toh(grp->tprt));
+       printf(" tprm    : %d\n", (uint32_t)le32toh(grp->tprm));
+       printf(" tbet    : %d\n", (uint32_t)le32toh(grp->tbet));
+       printf(" tbem    : %d\n", (uint32_t)le32toh(grp->tbem));
+       printf(" mpos    : %#x\n", (uint32_t)le32toh(grp->mpos));
+       printf(" mccap   : %#x\n", (uint32_t)le32toh(grp->mccap));
+       printf(" cpar    : %#x\n", (uint16_t)le16toh(grp->cpar));
+}
+
+void show_lnvm_ppaf(struct nvme_nvm_addr_format *ppaf)
+{
+       printf("ppaf     :\n");
+       printf(" ch offs : %d ch bits  : %d\n",
+                                       ppaf->ch_offset, ppaf->ch_len);
+       printf(" lun offs: %d lun bits : %d\n",
+                                       ppaf->lun_offset, ppaf->lun_len);
+       printf(" pl offs : %d pl bits  : %d\n",
+                                       ppaf->pln_offset, ppaf->pln_len);
+       printf(" blk offs: %d blk bits : %d\n",
+                                       ppaf->blk_offset, ppaf->blk_len);
+       printf(" pg offs : %d pg bits  : %d\n",
+                                       ppaf->pg_offset, ppaf->pg_len);
+       printf(" sec offs: %d sec bits : %d\n",
+                                       ppaf->sect_offset, ppaf->sect_len);
+}
+
+void show_lnvm_id_ns(struct nvme_nvm_id *id)
+{
+       int i;
+
+       if (id->cgrps > 4) {
+               fprintf(stderr, "invalid identify geometry returned\n");
+               return;
+       }
+
+       printf("verid    : %#x\n", id->ver_id);
+       printf("vmnt     : %#x\n", id->vmnt);
+       printf("cgrps    : %d\n", id->cgrps);
+       printf("cap      : %#x\n", (uint32_t)le32toh(id->cap));
+       printf("dom      : %#x\n", (uint32_t)le32toh(id->dom));
+       show_lnvm_ppaf(&id->ppaf);
+
+       for (i = 0; i < id->cgrps; i++) {
+               printf("grp      : %d\n", i);
+               show_lnvm_id_grp(&id->groups[i]);
+       }
+}
+
+int lnvm_get_identity(int fd, int nsid, struct nvme_nvm_id *nvm_id)
+{
+       struct nvme_admin_cmd cmd = {
+               .opcode         = nvme_nvm_admin_identity,
+               .nsid           = nsid,
+               .addr           = (__u64)(uintptr_t)nvm_id,
+               .data_len       = 0x1000,
+       };
+
+       return nvme_submit_passthru(fd, NVME_IOCTL_ADMIN_CMD, &cmd);
+}
+
+int lnvm_do_id_ns(int fd, int nsid, unsigned int flags)
+{
+       struct nvme_nvm_id nvm_id;
+       int err;
+
+       err = lnvm_get_identity(fd, nsid, &nvm_id);
+       if (!err) {
+               if (flags & RAW)
+                       d_raw((unsigned char *)&nvm_id, sizeof(nvm_id));
+               else {
+                       printf("LightNVM Identify Geometry (%d):\n", nsid);
+                       show_lnvm_id_ns(&nvm_id);
+               }
+       }
+       else if (err > 0)
+               fprintf(stderr, "NVMe Status:%s(%x) NSID:%d\n",
+                       nvme_status_to_string(err), err, nsid);
+       return err;
+}
+
+static void show_lnvm_bbtbl(struct nvme_nvm_bb_tbl *tbl)
+{
+       printf("verid    : %#x\n", (uint16_t)le16toh(tbl->verid));
+       printf("tblks    : %d\n", (uint32_t)le32toh(tbl->tblks));
+       printf("tfact    : %d\n", (uint32_t)le32toh(tbl->tfact));
+       printf("tgrown   : %d\n", (uint32_t)le32toh(tbl->tgrown));
+       printf("tdresv   : %d\n", (uint32_t)le32toh(tbl->tdresv));
+       printf("thresv   : %d\n", (uint32_t)le32toh(tbl->thresv));
+       printf("Use raw output to retrieve table.\n");
+}
+
+static int __lnvm_do_get_bbtbl(int fd, struct nvme_nvm_id *id,
+                                               struct ppa_addr ppa,
+                                               unsigned int flags)
+{
+       struct nvme_nvm_id_group *grp = &id->groups[0];
+       int bbtblsz = ((uint16_t)le16toh(grp->num_blk) * grp->num_pln);
+       int bufsz = bbtblsz + sizeof(struct nvme_nvm_bb_tbl);
+       struct nvme_nvm_bb_tbl *bbtbl;
+       int err;
+
+       bbtbl = calloc(1, bufsz);
+       if (!bbtbl)
+               return -ENOMEM;
+
+       struct nvme_nvm_getbbtbl cmd = {
+               .opcode         = nvme_nvm_admin_get_bb_tbl,
+               .nsid           = 1,
+               .addr           = (__u64)(uintptr_t)bbtbl,
+               .data_len       = bufsz,
+               .ppa            = htole64(ppa.ppa),
+       };
+
+       err = nvme_submit_passthru(fd, NVME_IOCTL_ADMIN_CMD,
+                                       (struct nvme_passthru_cmd *)&cmd);
+       if (err > 0) {
+               fprintf(stderr, "NVMe Status:%s(%x)\n",
+                       nvme_status_to_string(err), err);
+               free(bbtbl);
+               return err;
+       }
+
+       if (flags & RAW)
+               d_raw((unsigned char *)&bbtbl->blk, bbtblsz);
+       else {
+               printf("LightNVM Bad Block Stats:\n");
+               show_lnvm_bbtbl(bbtbl);
+       }
+
+       free(bbtbl);
+       return 0;
+}
+
+int lnvm_do_get_bbtbl(int fd, int nsid, int lunid, int chid, unsigned int flags)
+{
+       struct nvme_nvm_id nvm_id;
+       struct ppa_addr ppa;
+       int err;
+
+       err = lnvm_get_identity(fd, nsid, &nvm_id);
+       if (err) {
+               fprintf(stderr, "NVMe Status:%s(%x)\n",
+                       nvme_status_to_string(err), err);
+               return err;
+       }
+
+       if (chid >= nvm_id.groups[0].num_ch ||
+                                       lunid >= nvm_id.groups[0].num_lun) {
+               fprintf(stderr, "Out of bound channel id or LUN id\n");
+               return -EINVAL;
+       }
+
+       ppa.ppa = 0;
+       ppa.g.lun = lunid;
+       ppa.g.ch = chid;
+
+       ppa = generic_to_dev_addr(&nvm_id.ppaf, ppa);
+
+       return __lnvm_do_get_bbtbl(fd, &nvm_id, ppa, flags);
+}
diff --git a/nvme-lightnvm.h b/nvme-lightnvm.h
new file mode 100644 (file)
index 0000000..6a98f93
--- /dev/null
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2016 CNEX Labs.  All rights reserved.
+ *
+ * Author: Matias Bjoerling <matias@cnexlabs.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * 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; see the file COPYING.  If not, write to
+ * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139,
+ * USA.
+ */
+
+#ifndef NVME_LIGHTNVM_H_
+#define NVME_LIGHTNVM_H_
+
+#include "linux/lightnvm.h"
+
+enum nvme_nvm_admin_opcode {
+       nvme_nvm_admin_identity         = 0xe2,
+       nvme_nvm_admin_get_bb_tbl       = 0xf2,
+       nvme_nvm_admin_set_bb_tbl       = 0xf1,
+};
+
+struct nvme_nvm_identity {
+       __u8    opcode;
+       __u8    flags;
+       __u16   command_id;
+       __u32   nsid;
+       __u64   rsvd[2];
+       __u64   prp1;
+       __u64   prp2;
+       __u32   chnl_off;
+       __u32   rsvd11[5];
+};
+
+struct nvme_nvm_getbbtbl {
+       __u8    opcode;
+       __u8    flags;
+       __u16   rsvd1;
+       __u32   nsid;
+       __u32   cdw2;
+       __u32   cdw3;
+       __u64   metadata;
+       __u64   addr;
+       __u32   metadata_len;
+       __u32   data_len;
+       __u64   ppa;
+       __u32   cdw11;
+       __u32   cdw12;
+       __u32   cdw13;
+       __u32   cdw14;
+       __u32   cdw15;
+       __u32   timeout_ms;
+       __u32   result;
+};
+
+struct nvme_nvm_command {
+       union {
+               struct nvme_nvm_identity identity;
+               struct nvme_nvm_getbbtbl get_bb;
+       };
+};
+
+struct nvme_nvm_completion {
+       __u64   result;         /* Used by LightNVM to return ppa completions */
+       __u16   sq_head;        /* how much of this queue may be reclaimed */
+       __u16   sq_id;          /* submission queue that generated this entry */
+       __u16   command_id;     /* of the command which completed */
+       __u16   status;         /* did the command fail, and if so, why? */
+};
+
+#define NVME_NVM_LP_MLC_PAIRS 886
+struct nvme_nvm_lp_mlc {
+       __u16                   num_pairs;
+       __u8                    pairs[NVME_NVM_LP_MLC_PAIRS];
+};
+
+struct nvme_nvm_lp_tbl {
+       __u8                    id[8];
+       struct nvme_nvm_lp_mlc  mlc;
+};
+
+struct nvme_nvm_id_group {
+       __u8                    mtype;
+       __u8                    fmtype;
+       __u16                   res16;
+       __u8                    num_ch;
+       __u8                    num_lun;
+       __u8                    num_pln;
+       __u8                    rsvd1;
+       __u16                   num_blk;
+       __u16                   num_pg;
+       __u16                   fpg_sz;
+       __u16                   csecs;
+       __u16                   sos;
+       __u16                   rsvd2;
+       __u32                   trdt;
+       __u32                   trdm;
+       __u32                   tprt;
+       __u32                   tprm;
+       __u32                   tbet;
+       __u32                   tbem;
+       __u32                   mpos;
+       __u32                   mccap;
+       __u16                   cpar;
+       __u8                    reserved[10];
+       struct nvme_nvm_lp_tbl lptbl;
+} __attribute__((packed));
+
+struct nvme_nvm_addr_format {
+       __u8                    ch_offset;
+       __u8                    ch_len;
+       __u8                    lun_offset;
+       __u8                    lun_len;
+       __u8                    pln_offset;
+       __u8                    pln_len;
+       __u8                    blk_offset;
+       __u8                    blk_len;
+       __u8                    pg_offset;
+       __u8                    pg_len;
+       __u8                    sect_offset;
+       __u8                    sect_len;
+       __u8                    res[4];
+} __attribute__((packed));
+
+struct nvme_nvm_id {
+       __u8                    ver_id;
+       __u8                    vmnt;
+       __u8                    cgrps;
+       __u8                    res;
+       __u32                   cap;
+       __u32                   dom;
+       struct nvme_nvm_addr_format ppaf;
+       __u8                    resv[228];
+       struct nvme_nvm_id_group groups[4];
+} __attribute__((packed));
+
+struct nvme_nvm_bb_tbl {
+       __u8    tblid[4];
+       __u16   verid;
+       __u16   revid;
+       __u32   rvsd1;
+       __u32   tblks;
+       __u32   tfact;
+       __u32   tgrown;
+       __u32   tdresv;
+       __u32   thresv;
+       __u32   rsvd2[8];
+       __u8    blk[0];
+};
+
+#define NVM_BLK_BITS (16)
+#define NVM_PG_BITS  (16)
+#define NVM_SEC_BITS (8)
+#define NVM_PL_BITS  (8)
+#define NVM_LUN_BITS (8)
+#define NVM_CH_BITS  (7)
+
+struct ppa_addr {
+       /* Generic structure for all addresses */
+       union {
+               struct {
+                       __u64 blk       : NVM_BLK_BITS;
+                       __u64 pg        : NVM_PG_BITS;
+                       __u64 sec       : NVM_SEC_BITS;
+                       __u64 pl        : NVM_PL_BITS;
+                       __u64 lun       : NVM_LUN_BITS;
+                       __u64 ch        : NVM_CH_BITS;
+                       __u64 reserved  : 1;
+               } g;
+
+               __u64 ppa;
+       };
+};
+
+static inline struct ppa_addr generic_to_dev_addr(
+                       struct nvme_nvm_addr_format *ppaf, struct ppa_addr r)
+{
+       struct ppa_addr l;
+
+       l.ppa = ((__u64)r.g.blk) << ppaf->blk_offset;
+       l.ppa |= ((__u64)r.g.pg) << ppaf->pg_offset;
+       l.ppa |= ((__u64)r.g.sec) << ppaf->sect_offset;
+       l.ppa |= ((__u64)r.g.pl) << ppaf->pln_offset;
+       l.ppa |= ((__u64)r.g.lun) << ppaf->lun_offset;
+       l.ppa |= ((__u64)r.g.ch) << ppaf->ch_offset;
+
+       return l;
+}
+
+int lnvm_do_init(char *, char *);
+int lnvm_do_list_devices(void);
+int lnvm_do_info(void);
+int lnvm_do_create_tgt(char *, char *, char *, int, int);
+int lnvm_do_remove_tgt(char *);
+int lnvm_do_factory_init(char *, int, int, int);
+int lnvm_do_id_ns(int, int, unsigned int);
+int lnvm_do_get_bbtbl(int, int, int, int, unsigned int);
+
+#endif
diff --git a/nvme.c b/nvme.c
index 6626ed1c221d6111e898efbec5615e49a1f5c280..90ebca63b7bab7247221980a4a3a13e3b2e16bde 100644 (file)
--- a/nvme.c
+++ b/nvme.c
@@ -49,6 +49,7 @@
 
 #include "nvme-print.h"
 #include "nvme-ioctl.h"
+#include "nvme-lightnvm.h"
 
 #include "src/argconfig.h"
 #include "src/suffix.h"
@@ -102,6 +103,14 @@ static const char nvme_version_string[] = NVME_VERSION;
        ENTRY(RESET, "reset", "Resets the controller", reset) \
        ENTRY(SUBSYS_RESET, "subsystem-reset", "Resets the controller", subsystem_reset) \
        ENTRY(REGISTERS, "show-regs", "Shows the controller registers. Requires admin character device", show_registers) \
+       ENTRY(LNVM_LIST, "lnvm-list", "List available LightNVM devices", lnvm_list) \
+       ENTRY(LNVM_INFO, "lnvm-info", "List general information and available target engines", lnvm_info) \
+       ENTRY(LNVM_ID_NS, "lnvm-id-ns", "List geometry for LightNVM device", lnvm_id_ns) \
+       ENTRY(LNVM_INIT, "lnvm-init", "Initialize media manager on LightNVM device", lnvm_init) \
+       ENTRY(LNVM_CREATE, "lnvm-create", "Create target on top of a LightNVM device", lnvm_create_tgt) \
+       ENTRY(LNVM_REMOVE, "lnvm-remove", "Remove target from device", lnvm_remove_tgt) \
+       ENTRY(LNVM_FACTORY, "lnvm-factory", "Reset device to factory state", lnvm_factory_init) \
+       ENTRY(LNVM_BBTBL_GET, "lnvm-diag-bbtbl", "Diagnose bad block table", lnvm_get_bbtbl) \
        ENTRY(VERSION, "version", "Shows the program version", version) \
        ENTRY(HELP, "help", "Display this help", help)
 
@@ -2512,6 +2521,264 @@ static int admin_passthru(int argc, char **argv)
        return passthru(argc, argv, NVME_IOCTL_ADMIN_CMD, desc);
 }
 
+static int lnvm_init(int argc, char **argv)
+{
+       const char *desc = "Initialize LightNVM device. A LightNVM/Open-Channel SSD"\
+                          " must have a media manager associated before it can "\
+                          " be exposed to the user. The default is to initialize"
+                          " the general media manager on top of the device.\n\n"
+                          "Example:"
+                          " lnvm-init -d nvme0n1";
+       const char *devname = "identifier of desired device. e.g. nvme0n1.";
+       const char *mmtype = "media manager to initialize on top of device. Default: gennvm.";
+
+       struct config
+       {
+               char *devname;
+               char *mmtype;
+       };
+
+       struct config cfg = {
+               .devname = "",
+               .mmtype = "gennvm",
+       };
+
+       const struct argconfig_commandline_options command_line_options[] = {
+               {"device-name",   'd', "DEVICE", CFG_STRING, &cfg.devname, required_argument, devname},
+               {"mediamgr-name", 'm', "MM",     CFG_STRING, &cfg.mmtype,  no_argument,       mmtype},
+               {0}
+       };
+
+       argconfig_parse(argc, argv, desc, command_line_options, &cfg, sizeof(cfg));
+
+       return lnvm_do_init(cfg.devname, cfg.mmtype);
+}
+
+static int lnvm_list(int argc, char **argv)
+{
+       const char *desc = "List all devices registered with LightNVM.";
+
+       const struct argconfig_commandline_options command_line_options[] = {
+               {0}
+       };
+
+       argconfig_parse(argc, argv, desc, command_line_options, NULL, 0);
+
+       return lnvm_do_list_devices();
+}
+
+static int lnvm_info(int argc, char **argv)
+{
+       const char *desc = "Show general information and registered target types with LightNVM";
+
+       const struct argconfig_commandline_options command_line_options[] = {
+               {0}
+       };
+
+       argconfig_parse(argc, argv, desc, command_line_options, NULL, 0);
+
+       return lnvm_do_info();
+}
+
+static int lnvm_id_ns(int argc, char **argv)
+{
+       const char *desc = "Send an Identify Geometry command to the "\
+               "given LightNVM device, returns properties of the specified"\
+               "namespace in either human-readable or binary format.";
+       const char *force = "Return this namespace, even if not supported";
+       const char *raw_binary = "show infos in binary format";
+       const char *human_readable = "show infos in readable format";
+       const char *namespace_id = "identifier of desired namespace. default: 1";
+       unsigned int flags = 0;
+
+       struct config {
+               __u32 namespace_id;
+               __u8  raw_binary;
+               __u8  human_readable;
+               __u8  force;
+       };
+
+       struct config cfg = {
+               .namespace_id    = 1,
+       };
+
+       const struct argconfig_commandline_options command_line_options[] = {
+               {"namespace-id",    'n', "NUM",  CFG_POSITIVE, &cfg.namespace_id,    required_argument, namespace_id},
+               {"force",           'f', "FLAG", CFG_NONE,     &cfg.force,           no_argument,       force},
+               {"raw-binary",      'b', "FLAG", CFG_NONE,     &cfg.raw_binary,      no_argument,       raw_binary},
+               {"human-readable",  'H', "FLAG", CFG_NONE,     &cfg.human_readable,  no_argument,       human_readable},
+               {0}
+       };
+
+       parse_and_open(argc, argv, desc, command_line_options, &cfg, sizeof(cfg));
+
+       if (cfg.human_readable)
+               flags |= HUMAN;
+       else if (cfg.raw_binary)
+               flags |= RAW;
+
+       return lnvm_do_id_ns(fd, cfg.namespace_id, flags);
+}
+
+
+static int lnvm_create_tgt(int argc, char **argv)
+{
+       const char *desc = "Instantiate a target on top of a LightNVM enabled device.";
+       const char *devname = "identifier of desired device. e.g. nvme0n1.";
+       const char *tgtname = "target name of the device to initialize. e.g. target0.";
+       const char *tgttype = "identifier of target type. e.g. pblk.";
+       const char *lun_begin = "Define begin of luns to use for target.";
+       const char *lun_end = "Define set of luns to use for target.";
+
+       struct config
+       {
+               char *devname;
+               char *tgtname;
+               char *tgttype;
+               int lun_begin;
+               int lun_end;
+       };
+
+       struct config cfg = {
+               .devname = "",
+               .tgtname = "",
+               .tgttype = "",
+               .lun_begin = 0,
+               .lun_end = 0,
+       };
+
+       const struct argconfig_commandline_options command_line_options[] = {
+               {"device-name",   'd', "DEVICE", CFG_STRING,    &cfg.devname,   required_argument, devname},
+               {"target-name",   'n', "TARGET", CFG_STRING,    &cfg.tgtname,   required_argument, tgtname},
+               {"target-type",   't', "TARGETTYPE",  CFG_STRING,    &cfg.tgttype,   required_argument, tgttype},
+               {"lun-begin",     'b', "NUM",    CFG_POSITIVE,  &cfg.tgttype,   no_argument,       lun_begin},
+               {"lun-end",       'e', "NUM",    CFG_POSITIVE,  &cfg.tgttype,   no_argument,       lun_end},
+               {0}
+       };
+
+       argconfig_parse(argc, argv, desc, command_line_options, &cfg, sizeof(cfg));
+
+       if (!strlen(cfg.devname)) {
+               fprintf(stderr, "device name missing %d\n", (int)strlen(cfg.devname));
+               return -EINVAL;
+       }
+       if (!strlen(cfg.tgtname)) {
+               fprintf(stderr, "target name missing\n");
+               return -EINVAL;
+       }
+       if (!strlen(cfg.tgttype)) {
+               fprintf(stderr, "target type missing\n");
+               return -EINVAL;
+       }
+
+       return lnvm_do_create_tgt(cfg.devname, cfg.tgtname, cfg.tgttype, cfg.lun_begin, cfg.lun_end);
+}
+
+static int lnvm_remove_tgt(int argc, char **argv)
+{
+       const char *desc = "Remove an initialized LightNVM target.";
+       const char *tgtname = "target name of the device to initialize. e.g. target0.";
+
+       struct config
+       {
+               char *tgtname;
+       };
+
+       struct config cfg = {
+               .tgtname = "",
+       };
+
+       const struct argconfig_commandline_options command_line_options[] = {
+               {"target-name",   'n', "TARGET", CFG_STRING,    &cfg.tgtname,   required_argument, tgtname},
+               {0}
+       };
+
+       argconfig_parse(argc, argv, desc, command_line_options, &cfg, sizeof(cfg));
+
+       if (!strlen(cfg.tgtname)) {
+               fprintf(stderr, "target name missing\n");
+               return -EINVAL;
+       }
+
+       return lnvm_do_remove_tgt(cfg.tgtname);
+}
+
+static int lnvm_factory_init(int argc, char **argv)
+{
+       const char *desc = "Factory initialize a LightNVM enabled device.";
+       const char *devname = "identifier of desired device. e.g. nvme0n1.";
+       const char *erase_only_marked = "only erase marked blocks. default: all blocks.";
+       const char *host_marks = "remove host side blocks list. default: keep.";
+       const char *bb_marks = "remove grown bad blocks list. default: keep";
+
+       struct config
+       {
+               char *devname;
+               __u8 erase_only_marked;
+               __u8 clear_host_marks;
+               __u8 clear_bb_marks;
+       };
+
+       struct config cfg = {
+               .devname = "",
+       };
+
+       const struct argconfig_commandline_options command_line_options[] = {
+               {"device-name",          'd', "DEVICE", CFG_STRING, &cfg.devname,           required_argument, devname},
+               {"erase-only-marked",    'e', "",       CFG_NONE,   &cfg.erase_only_marked, no_argument,       erase_only_marked},
+               {"clear-host-side-blks", 's', "",       CFG_NONE,   &cfg.clear_host_marks,  no_argument,       host_marks},
+               {"clear-bb-blks",        'b', "",       CFG_NONE,   &cfg.clear_bb_marks,    no_argument,       bb_marks},
+               {0}
+       };
+
+       argconfig_parse(argc, argv, desc, command_line_options, &cfg,
+                                                               sizeof(cfg));
+
+       return lnvm_do_factory_init(cfg.devname, cfg.erase_only_marked,
+                               cfg.clear_host_marks, cfg.clear_bb_marks);
+}
+
+static int lnvm_get_bbtbl(int argc, char **argv)
+{
+       const char *desc = "Receive bad block table from a LightNVM compatible"\
+                          " device.";
+       const char *namespace = "(optional) desired namespace";
+       const char *ch = "channel identifier";
+       const char *lun = "lun identifier (within a channel)";
+       const char *raw_binary = "show infos in binary format";
+       unsigned int flags = 0;
+
+       struct config
+       {
+               __u32 namespace_id;
+               __u16 lunid;
+               __u16 chid;
+               __u8  raw_binary;
+       };
+
+       struct config cfg = {
+               .namespace_id = 1,
+               .lunid = 0,
+               .chid = 0,
+       };
+
+       const struct argconfig_commandline_options command_line_options[] = {
+               {"namespace-id", 'n', "NUM",  CFG_POSITIVE, &cfg.namespace_id, required_argument, namespace},
+               {"channel-id",   'c', "",     CFG_SHORT,    &cfg.chid,         required_argument, ch},
+               {"lun-id",       'l', "",     CFG_SHORT,    &cfg.lunid,        required_argument, lun},
+               {"raw-binary",   'b', "FLAG", CFG_NONE,     &cfg.raw_binary,   no_argument,       raw_binary},
+               {0}
+       };
+
+       parse_and_open(argc, argv, desc, command_line_options, &cfg, sizeof(cfg));
+
+       if (cfg.raw_binary)
+               flags |= RAW;
+
+       return lnvm_do_get_bbtbl(fd, cfg.namespace_id, cfg.lunid, cfg.chid,
+                                                                       flags);
+}
+
 static void usage()
 {
        printf("usage: nvme <command> [<device>] [<args>]\n");