From: Matias Bjørling Date: Mon, 23 May 2016 12:44:00 +0000 (+0200) Subject: Add LightNVM adminstration and diagnose support X-Git-Tag: v0.8~43^2~2 X-Git-Url: https://www.infradead.org/git/?a=commitdiff_plain;h=c5144c69fe0650d5a61d47bf65a6d2ddae6e4a14;p=users%2Fsagi%2Fnvme-cli.git Add LightNVM adminstration and diagnose support 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 --- diff --git a/Makefile b/Makefile index 8463766c..d26a2111 100644 --- 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 index 00000000..774a4312 --- /dev/null +++ b/linux/lightnvm.h @@ -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 +#include +#else /* __KERNEL__ */ +#include +#include +#define DISK_NAME_LEN 32 +#endif /* __KERNEL__ */ + +#include +#include + +#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 index 00000000..3c8ed44b --- /dev/null +++ b/nvme-lightnvm.c @@ -0,0 +1,393 @@ +/* + * lightnvm.c -- LightNVM NVMe integration. + * + * Copyright (c) 2016, CNEX Labs. + * + * Written by Matias Bjoerling + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#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 index 00000000..6a98f935 --- /dev/null +++ b/nvme-lightnvm.h @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2016 CNEX Labs. All rights reserved. + * + * Author: Matias Bjoerling + * + * 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 6626ed1c..90ebca63 100644 --- 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 [] []\n");