/* * scsinvme.cpp * * Home page of code is: http://www.smartmontools.org * * Copyright (C) 2018 Harry Mallon * * SPDX-License-Identifier: GPL-2.0-or-later * */ #include "config.h" #include "dev_interface.h" #include "dev_tunnelled.h" #include "scsicmds.h" #include "sg_unaligned.h" #include "utility.h" #include // SNT (SCSI NVMe Translation) namespace and prefix namespace snt { #define SNT_JMICRON_NVME_SIGNATURE 0x454d564eu // 'NVME' reversed (little endian) #define SNT_JMICRON_CDB_LEN 12 #define SNT_JMICRON_NVM_CMD_LEN 512 class sntjmicron_device : public tunnelled_device< /*implements*/ nvme_device, /*by tunnelling through a*/ scsi_device > { public: sntjmicron_device(smart_interface * intf, scsi_device * scsidev, const char * req_type, unsigned nsid); virtual ~sntjmicron_device() throw(); virtual bool open(); virtual bool nvme_pass_through(const nvme_cmd_in & in, nvme_cmd_out & out); private: enum { proto_nvm_cmd = 0x0, proto_non_data = 0x1, proto_dma_in = 0x2, proto_dma_out = 0x3, proto_response = 0xF }; }; sntjmicron_device::sntjmicron_device(smart_interface * intf, scsi_device * scsidev, const char * req_type, unsigned nsid) : smart_device(intf, scsidev->get_dev_name(), "sntjmicron", req_type), tunnelled_device(scsidev, nsid) { set_info().info_name = strprintf("%s [USB NVMe JMicron]", scsidev->get_info_name()); } sntjmicron_device::~sntjmicron_device() throw() { } bool sntjmicron_device::open() { // Open USB first if (!tunnelled_device::open()) return false; // No sure how multiple namespaces come up on device so we // cannot detect e.g. /dev/sdX is NSID 2. // Set to broadcast if not available if (!get_nsid()) { set_nsid(0xFFFFFFFF); } return true; } // cdb[0]: ATA PASS THROUGH (12) SCSI command opcode byte (0xa1) // cdb[1]: [ is admin cmd: 1 ] [ protocol : 7 ] // cdb[2]: reserved // cdb[3]: parameter list length (23:16) // cdb[4]: parameter list length (15:08) // cdb[5]: parameter list length (07:00) // cdb[6]: reserved // cdb[7]: reserved // cdb[8]: reserved // cdb[9]: reserved // cdb[10]: reserved // cdb[11]: CONTROL (?) bool sntjmicron_device::nvme_pass_through(const nvme_cmd_in & in, nvme_cmd_out & out) { /* Only admin commands used */ bool admin = true; // 1: "NVM Command Set Payload" { unsigned char cdb[SNT_JMICRON_CDB_LEN] = { 0 }; cdb[0] = SAT_ATA_PASSTHROUGH_12; cdb[1] = (admin ? 0x80 : 0x00) | proto_nvm_cmd; sg_put_unaligned_be24(SNT_JMICRON_NVM_CMD_LEN, &cdb[3]); unsigned nvm_cmd[SNT_JMICRON_NVM_CMD_LEN / sizeof(unsigned)] = { 0 }; nvm_cmd[0] = SNT_JMICRON_NVME_SIGNATURE; // nvm_cmd[1]: reserved nvm_cmd[2] = in.opcode; // More of CDW0 may go in here in future nvm_cmd[3] = in.nsid; // nvm_cmd[4-5]: reserved // nvm_cmd[6-7]: metadata pointer // nvm_cmd[8-11]: data ptr (?) nvm_cmd[12] = in.cdw10; nvm_cmd[13] = in.cdw11; nvm_cmd[14] = in.cdw12; nvm_cmd[15] = in.cdw13; nvm_cmd[16] = in.cdw14; nvm_cmd[17] = in.cdw15; // nvm_cmd[18-127]: reserved if (isbigendian()) for (unsigned i = 0; i < (SNT_JMICRON_NVM_CMD_LEN / sizeof(uint32_t)); i++) swapx(&nvm_cmd[i]); scsi_cmnd_io io_nvm; memset(&io_nvm, 0, sizeof(io_nvm)); io_nvm.cmnd = cdb; io_nvm.cmnd_len = SNT_JMICRON_CDB_LEN; io_nvm.dxfer_dir = DXFER_TO_DEVICE; io_nvm.dxferp = (uint8_t *)nvm_cmd; io_nvm.dxfer_len = SNT_JMICRON_NVM_CMD_LEN; scsi_device * scsidev = get_tunnel_dev(); if (!scsidev->scsi_pass_through_and_check(&io_nvm, "sntjmicron_device::nvme_pass_through:NVM: ")) return set_err(scsidev->get_err()); } // 2: DMA or Non-Data { unsigned char cdb[SNT_JMICRON_CDB_LEN] = { 0 }; cdb[0] = SAT_ATA_PASSTHROUGH_12; scsi_cmnd_io io_data; memset(&io_data, 0, sizeof(io_data)); io_data.cmnd = cdb; io_data.cmnd_len = SNT_JMICRON_CDB_LEN; switch (in.direction()) { case nvme_cmd_in::no_data: cdb[1] = (admin ? 0x80 : 0x00) | proto_non_data; io_data.dxfer_dir = DXFER_NONE; break; case nvme_cmd_in::data_out: cdb[1] = (admin ? 0x80 : 0x00) | proto_dma_out; sg_put_unaligned_be24(in.size, &cdb[3]); io_data.dxfer_dir = DXFER_TO_DEVICE; io_data.dxferp = (uint8_t *)in.buffer; io_data.dxfer_len = in.size; break; case nvme_cmd_in::data_in: cdb[1] = (admin ? 0x80 : 0x00) | proto_dma_in; sg_put_unaligned_be24(in.size, &cdb[3]); io_data.dxfer_dir = DXFER_FROM_DEVICE; io_data.dxferp = (uint8_t *)in.buffer; io_data.dxfer_len = in.size; memset(in.buffer, 0, in.size); break; case nvme_cmd_in::data_io: default: return set_err(EINVAL); } scsi_device * scsidev = get_tunnel_dev(); if (!scsidev->scsi_pass_through_and_check(&io_data, "sntjmicron_device::nvme_pass_through:Data: ")) return set_err(scsidev->get_err()); } // 3: "Return Response Information" { unsigned char cdb[SNT_JMICRON_CDB_LEN] = { 0 }; cdb[0] = SAT_ATA_PASSTHROUGH_12; cdb[1] = (admin ? 0x80 : 0x00) | proto_response; sg_put_unaligned_be24(SNT_JMICRON_NVM_CMD_LEN, &cdb[3]); unsigned nvm_reply[SNT_JMICRON_NVM_CMD_LEN / sizeof(unsigned)] = { 0 }; scsi_cmnd_io io_reply; memset(&io_reply, 0, sizeof(io_reply)); io_reply.cmnd = cdb; io_reply.cmnd_len = SNT_JMICRON_CDB_LEN; io_reply.dxfer_dir = DXFER_FROM_DEVICE; io_reply.dxferp = (uint8_t *)nvm_reply; io_reply.dxfer_len = SNT_JMICRON_NVM_CMD_LEN; scsi_device * scsidev = get_tunnel_dev(); if (!scsidev->scsi_pass_through_and_check(&io_reply, "sntjmicron_device::nvme_pass_through:Reply: ")) return set_err(scsidev->get_err()); if (isbigendian()) for (unsigned i = 0; i < (SNT_JMICRON_NVM_CMD_LEN / sizeof(uint32_t)); i++) swapx(&nvm_reply[i]); if (nvm_reply[0] != SNT_JMICRON_NVME_SIGNATURE) return set_err(EIO, "Out of spec JMicron NVMe reply"); int status = nvm_reply[5] >> 17; if (status > 0) return set_nvme_err(out, status); out.result = nvm_reply[2]; } return true; } } // namespace snt using namespace snt; nvme_device * smart_interface::get_snt_device(const char * type, scsi_device * scsidev) { if (!scsidev) throw std::logic_error("smart_interface: get_snt_device() called with scsidev=0"); // Take temporary ownership of 'scsidev' to delete it on error scsi_device_auto_ptr scsidev_holder(scsidev); nvme_device * sntdev = 0; // TODO: Remove this and adjust drivedb entry accordingly when no longer EXPERIMENTAL if (!strcmp(type, "sntjmicron#please_try")) { set_err(EINVAL, "USB to NVMe bridge [please try '-d sntjmicron' and report result to: " PACKAGE_BUGREPORT "]"); return 0; } if (!strncmp(type, "sntjmicron", 10)) { int n1 = -1, n2 = -1, len = strlen(type); unsigned nsid = 0; // invalid namespace id -> use default sscanf(type, "sntjmicron%n,0x%x%n", &n1, &nsid, &n2); if (!(n1 == len || n2 == len)) { set_err(EINVAL, "Invalid NVMe namespace id in '%s'", type); return 0; } sntdev = new sntjmicron_device(this, scsidev, type, nsid); } else { set_err(EINVAL, "Unknown SNT device type '%s'", type); return 0; } // 'scsidev' is now owned by 'sntdev' scsidev_holder.release(); return sntdev; }