/** * Get the maximum allowed command blocks for the device. */ void zbc_sg_get_max_cmd_blocks(struct zbc_device *dev) { struct stat st; int ret, sgsz = ZBC_SG_MAX_SGSZ; /* Get device stats */ if ( fstat(dev->zbd_fd, &st) < 0 ) { if ( zbc_log_level >= ZBC_LOG_DEBUG ) { zbc_error("%s: stat failed %d (%s)\n", dev->zbd_filename, errno, strerror(errno)); } goto out; } if ( S_ISCHR(st.st_mode) ) { ret = ioctl(dev->zbd_fd, SG_GET_SG_TABLESIZE, &sgsz); if ( ret != 0 ) { if ( zbc_log_level >= ZBC_LOG_DEBUG ) { ret = -errno; zbc_error("%s: SG_GET_SG_TABLESIZE ioctl failed %d (%s)\n", dev->zbd_filename, errno, strerror(errno)); sgsz = ZBC_SG_MAX_SGSZ; } } if (!sgsz) sgsz = 1; } else if ( S_ISBLK(st.st_mode) ) { sgsz = zbc_sg_max_segments(dev); } else { /* Files for fake backend */ sgsz = 128; } out: dev->zbd_info.zbd_max_rw_logical_blocks = (uint32_t)sgsz * sysconf(_SC_PAGESIZE) / dev->zbd_info.zbd_logical_block_size; zbc_debug("%s: Maximum command data transfer size is %llu logical blocks\n", dev->zbd_filename, (unsigned long long)dev->zbd_info.zbd_max_rw_logical_blocks); return; }
/** * zbc_list_zones - report zones for a ZBC device * @dev: (IN) ZBC device handle to report on * @start_lba: (IN) start LBA for the first zone to reported * @ro: (IN) Reporting options * @zones: (OUT) pointer for reported zones * @nr_zones: (OUT) number of returned zones * * Reports the number and details of available zones. The @zones * parameter is used to return an array of zones which is allocated using * malloc(3) internally and needs to be freed using free(3). The number * of zones in @zones is returned in @nr_zones. * * Returns -EIO if an error happened when communicating to the device. * Returns -ENOMEM if memory could not be allocated for @zones. */ int zbc_list_zones(struct zbc_device *dev, uint64_t start_lba, enum zbc_reporting_options ro, struct zbc_zone **pzones, unsigned int *pnr_zones) { zbc_zone_t *zones = NULL; unsigned int nr_zones; int ret; /* Get total number of zones */ ret = zbc_report_nr_zones(dev, start_lba, ro, &nr_zones); if ( ret < 0 ) { return( ret ); } zbc_debug("Device %s: %d zones\n", dev->zbd_filename, nr_zones); /* Allocate zone array */ zones = (zbc_zone_t *) malloc(sizeof(zbc_zone_t) * nr_zones); if ( ! zones ) { zbc_error("No memory\n"); return( -ENOMEM ); } memset(zones, 0, sizeof(zbc_zone_t) * nr_zones); /* Get zones info */ ret = zbc_report_zones(dev, start_lba, ro, zones, &nr_zones); if ( ret != 0 ) { zbc_error("zbc_report_zones failed\n"); free(zones); } else { *pzones = zones; *pnr_zones = nr_zones; } return( ret ); }
/** * Test if unit is ready. This will retry 5 times if the command * returns "UNIT ATTENTION". */ int zbc_sg_cmd_test_unit_ready(zbc_device_t *dev) { zbc_sg_cmd_t cmd; int ret = -EAGAIN, retries = 5; while( retries && (ret == -EAGAIN) ) { retries--; /* Intialize command */ ret = zbc_sg_cmd_init(&cmd, ZBC_SG_TEST_UNIT_READY, NULL, 0); if ( ret != 0 ) { zbc_error("%s: zbc_sg_cmd_init TEST UNIT READY failed\n", dev->zbd_filename); return( ret ); } cmd.cdb[0] = ZBC_SG_TEST_UNIT_READY_CDB_OPCODE; /* Execute the SG_IO command */ ret = zbc_sg_cmd_exec(dev, &cmd); if ( (ret != 0) && ((cmd.io_hdr.host_status == ZBC_SG_DID_SOFT_ERROR) || (cmd.io_hdr.sb_len_wr && (cmd.sense_buf[2] == 0x06))) ) { zbc_debug("%s: Unit attention required, %d / 5 retries left\n", dev->zbd_filename, retries); ret = -EAGAIN; } zbc_sg_cmd_destroy(&cmd); } if ( ret != 0 ) { ret = -ENXIO; } return( ret ); }
/** * Print an array of bytes. */ void zbc_sg_print_bytes(zbc_device_t *dev, uint8_t *buf, unsigned int len) { char msg[512]; unsigned i = 0, j; int n; zbc_debug("%s: * +==================================\n", dev->zbd_filename); zbc_debug("%s: * |Byte | 0 | 1 | 2 | 3 |\n", dev->zbd_filename); zbc_debug("%s: * |=====+======+======+======+======+\n", dev->zbd_filename); while( i < len ) { n = sprintf(msg, "%s: * | %3d |", dev->zbd_filename, i); for(j = 0; j < 4; j++) { if ( i < len ) { n += sprintf(msg + n, " 0x%02x |", (unsigned int)buf[i]); } else { n += sprintf(msg + n, " |"); } i++; } zbc_debug("%s\n", msg); if ( i < (len - 4) ) { zbc_debug("%s: * |=====+======+======+======+======+\n", dev->zbd_filename); } else { zbc_debug("%s: * +==================================\n", dev->zbd_filename); } } return; }
/** * Open an emulation device or file. */ static int zbc_fake_open(const char *filename, int flags, struct zbc_device **pdev) { zbc_fake_device_t *fdev; int fd, ret; zbc_debug("%s: ########## Trying FAKE driver ##########\n", filename); /* Open emulation device/file */ fd = open(filename, zbc_open_flags(flags)); if ( fd < 0 ) { zbc_error("%s: open failed %d (%s)\n", filename, errno, strerror(errno)); ret = -errno; goto out; } /* Allocate a handle */ ret = -ENOMEM; fdev = calloc(1, sizeof(*fdev)); if ( ! fdev ) { goto out; } fdev->dev.zbd_fd = fd; fdev->zbd_meta_fd = -1; fdev->dev.zbd_filename = strdup(filename); if ( ! fdev->dev.zbd_filename ) { goto out_free_dev; } /* Set the fake device information */ ret = zbc_fake_set_info(&fdev->dev); if ( ret != 0 ) { goto out_free_filename; } /* Open metadata */ ret = zbc_fake_open_metadata(fdev); if ( ret ) { goto out_free_filename; } *pdev = &fdev->dev; zbc_debug("%s: ########## FAKE driver succeeded ##########\n", filename); return 0; out_free_filename: free(fdev->dev.zbd_filename); out_free_dev: free(fdev); out: if ( fd >= 0 ) { close(fd); } zbc_debug("%s: ########## FAKE driver failed %d ##########\n", filename, ret); return ret; }
/** * Open metadata file of a fake device. */ static int zbc_fake_open_metadata(zbc_fake_device_t *fdev) { char meta_path[512]; struct stat st; int ret; zbc_fake_dev_meta_path(fdev, meta_path); zbc_debug("%s: using meta file %s\n", fdev->dev.zbd_filename, meta_path); pthread_mutexattr_init(&fdev->zbd_mutex_attr); pthread_mutexattr_setpshared(&fdev->zbd_mutex_attr, PTHREAD_PROCESS_SHARED); fdev->zbd_meta_fd = open(meta_path, O_RDWR); if ( fdev->zbd_meta_fd < 0 ) { /* Metadata does not exist yet, we'll have to wait for a set_zones call */ if ( errno == ENOENT ) { return 0; } ret = -errno; zbc_error("%s: open metadata file %s failed %d (%s)\n", fdev->dev.zbd_filename, meta_path, errno, strerror(errno)); goto out; } if ( fstat(fdev->zbd_meta_fd, &st) < 0 ) { zbc_error("%s: fstat metadata file %s failed %d (%s)\n", fdev->dev.zbd_filename, meta_path, errno, strerror(errno)); ret = -errno; goto out; } /* mmap metadata file */ fdev->zbd_meta_size = st.st_size; fdev->zbd_meta = mmap(NULL, fdev->zbd_meta_size, PROT_READ | PROT_WRITE, MAP_SHARED, fdev->zbd_meta_fd, 0); if ( fdev->zbd_meta == MAP_FAILED ) { fdev->zbd_meta = NULL; zbc_error("%s: mmap metadata file %s failed\n", fdev->dev.zbd_filename, meta_path); ret = -ENOMEM; goto out; } /* Check */ if ( (fdev->zbd_meta->zbd_capacity > (fdev->dev.zbd_info.zbd_logical_block_size * fdev->dev.zbd_info.zbd_logical_blocks)) || (! fdev->zbd_meta->zbd_nr_zones) ) { /* Do not report an error here to allow the execution of zbc_set_zones */ zbc_debug("%s: invalid metadata file %s\n", fdev->dev.zbd_filename, meta_path); zbc_fake_close_metadata(fdev); ret = 0; goto out; } zbc_debug("%s: %llu sectors of %zuB, %u zones\n", fdev->dev.zbd_filename, (unsigned long long)fdev->dev.zbd_info.zbd_logical_blocks, (size_t)fdev->dev.zbd_info.zbd_logical_block_size, fdev->zbd_meta->zbd_nr_zones); fdev->zbd_nr_zones = fdev->zbd_meta->zbd_nr_zones; fdev->zbd_zones = (struct zbc_zone *) (fdev->zbd_meta + 1); if ( fdev->dev.zbd_info.zbd_max_nr_open_seq_req > fdev->zbd_meta->zbd_nr_seq_zones ) { fdev->dev.zbd_info.zbd_max_nr_open_seq_req = fdev->zbd_meta->zbd_nr_seq_zones - 1; } ret = 0; out: if ( ret != 0 ) { zbc_fake_close_metadata(fdev); } return ret; }
/** * Get information (model, vendor, ...) from a SCSI device. */ static int zbc_scsi_classify(zbc_device_t *dev) { zbc_sg_cmd_t cmd; int dev_type; int n, ret; /* Allocate and intialize inquiry command */ ret = zbc_sg_cmd_init(&cmd, ZBC_SG_INQUIRY, NULL, ZBC_SG_INQUIRY_REPLY_LEN); if ( ret != 0 ) { zbc_error("zbc_sg_cmd_init failed\n"); return( ret ); } /* Fill command CDB: * +=============================================================================+ * | Bit| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | * |Byte | | | | | | | | | * |=====+=======================================================================| * | 0 | Operation Code (12h) | * |-----+-----------------------------------------------------------------------| * | 1 | Logical Unit Number | Reserved | EVPD | * |-----+-----------------------------------------------------------------------| * | 2 | Page Code | * |-----+-----------------------------------------------------------------------| * | 3 | (MSB) | * |- - -+--- Allocation Length ---| * | 4 | (LSB) | * |-----+-----------------------------------------------------------------------| * | 5 | Control | * +=============================================================================+ */ cmd.cdb[0] = ZBC_SG_INQUIRY_CDB_OPCODE; zbc_sg_cmd_set_int16(&cmd.cdb[3], ZBC_SG_INQUIRY_REPLY_LEN); /* Execute the SG_IO command */ ret = zbc_sg_cmd_exec(dev, &cmd); if ( ret != 0 ) { goto out; } /* Make sure we are not dealing with an ATA device */ if ( strncmp((char *)&cmd.out_buf[8], "ATA", 3) == 0 ) { ret = -ENXIO; goto out; } /* This is a SCSI device */ dev->zbd_info.zbd_type = ZBC_DT_SCSI; /* Vendor identification */ n = zbc_sg_cmd_strcpy(&dev->zbd_info.zbd_vendor_id[0], (char *)&cmd.out_buf[8], 8); /* Product identification */ n += zbc_sg_cmd_strcpy(&dev->zbd_info.zbd_vendor_id[n], (char *)&cmd.out_buf[16], 16); /* Product revision */ n += zbc_sg_cmd_strcpy(&dev->zbd_info.zbd_vendor_id[n], (char *)&cmd.out_buf[32], 4); /* Now check the device type */ dev_type = (int)(cmd.out_buf[0] & 0x1f); if ( dev_type == ZBC_DEV_TYPE_HOST_MANAGED ) { /* Host-managed device */ zbc_debug("Host-managed ZBC disk signature detected\n"); dev->zbd_info.zbd_model = ZBC_DM_HOST_MANAGED; goto out; } if ( dev_type != ZBC_DEV_TYPE_STANDARD ) { /* Unsupported device */ ret = -ENXIO; goto out; } zbc_debug("Standard SCSI disk signature detected\n"); /* This may be a host-aware device: look at VPD */ /* page B1h (block device characteristics) */ memset(cmd.cdb, 0, sizeof(cmd.cdb)); memset(cmd.out_buf, 0, ZBC_SG_INQUIRY_REPLY_LEN_VPD_PAGE_B1); cmd.cdb[0] = ZBC_SG_INQUIRY_CDB_OPCODE; cmd.cdb[1] = 0x01; cmd.cdb[2] = 0xB1; zbc_sg_cmd_set_int16(&cmd.cdb[3], ZBC_SG_INQUIRY_REPLY_LEN_VPD_PAGE_B1); /* Execute the SG_IO command */ ret = zbc_sg_cmd_exec(dev, &cmd); if ( ret == 0 ) { if ( (cmd.out_buf[1] == 0xB1) && (cmd.out_buf[2] == 0x00) && (cmd.out_buf[3] == 0x3C) ) { switch( (cmd.out_buf[8] & 0x30) >> 4 ) { case 0x01: /* Host aware device */ zbc_debug("Host aware ZBC disk detected\n"); dev->zbd_info.zbd_model = ZBC_DM_HOST_AWARE; break; case 0x00: case 0x10: /* Standard or drive-managed device */ zbc_debug("Standard or drive managed SCSI disk detected\n"); dev->zbd_info.zbd_model = ZBC_DM_DRIVE_MANAGED; ret = -ENXIO; break; default: zbc_debug("Unknown device type\n"); dev->zbd_info.zbd_model = ZBC_DM_DRIVE_UNKNOWN; ret = -ENXIO; break; } } } out: zbc_sg_cmd_destroy(&cmd); return( ret ); }
/** * Open a disk. */ static int zbc_scsi_open(const char *filename, int flags, struct zbc_device **pdev) { struct zbc_device *dev; struct stat st; int fd, ret; zbc_debug("%s: ########## Trying SCSI driver ##########\n", filename); /* Open the device file */ fd = open(filename, zbc_open_flags(flags)); if ( fd < 0 ) { zbc_error("Open device file %s failed %d (%s)\n", filename, errno, strerror(errno)); ret = -errno; goto out; } /* Check device */ if ( fstat(fd, &st) != 0 ) { zbc_error("Stat device %s failed %d (%s)\n", filename, errno, strerror(errno)); ret = -errno; goto out; } if ( (! S_ISCHR(st.st_mode)) && (! S_ISBLK(st.st_mode)) ) { ret = -ENXIO; goto out; } /* Set device decriptor */ ret = -ENOMEM; dev = calloc(1, sizeof(struct zbc_device)); if ( ! dev ) { goto out; } dev->zbd_filename = strdup(filename); if ( ! dev->zbd_filename ) { goto out_free_dev; } dev->zbd_fd = fd; ret = zbc_scsi_get_info(dev); if ( ret ) { goto out_free_filename; } *pdev = dev; zbc_debug("%s: ########## SCSI driver succeeded ##########\n", filename); return( 0 ); out_free_filename: free(dev->zbd_filename); out_free_dev: free(dev); out: if ( fd >= 0 ) { close(fd); } zbc_debug("%s: ########## SCSI driver failed %d ##########\n", filename, ret); return( ret ); }
/** * Execute a command. */ int zbc_sg_cmd_exec(zbc_device_t *dev, zbc_sg_cmd_t *cmd) { int ret; if ( zbc_log_level >= ZBC_LOG_DEBUG ) { zbc_debug("%s: Sending command 0x%02x:0x%02x (%s):\n", dev->zbd_filename, cmd->cdb_opcode, cmd->cdb_sa, zbc_sg_cmd_name(cmd)); zbc_sg_print_bytes(dev, cmd->cdb, cmd->cdb_sz); } /* Send the SG_IO command */ ret = ioctl(dev->zbd_fd, SG_IO, &cmd->io_hdr); if ( ret != 0 ) { ret = -errno; if ( zbc_log_level >= ZBC_LOG_DEBUG ) { zbc_error("%s: SG_IO ioctl failed %d (%s)\n", dev->zbd_filename, errno, strerror(errno)); } goto out; } /* Reset errno */ zbc_sg_set_sense(dev, NULL); zbc_debug("%s: Command %s done: status 0x%02x (0x%02x), host status 0x%04x, driver status 0x%04x (flags 0x%04x)\n", dev->zbd_filename, zbc_sg_cmd_name(cmd), (unsigned int)cmd->io_hdr.status, (unsigned int)cmd->io_hdr.masked_status, (unsigned int)cmd->io_hdr.host_status, (unsigned int)zbc_sg_cmd_driver_status(cmd), (unsigned int)zbc_sg_cmd_driver_flags(cmd)); /* Check status */ if ( ((cmd->code == ZBC_SG_ATA12) || (cmd->code == ZBC_SG_ATA16)) && (cmd->cdb[2] & (1 << 5)) ) { /* ATA command status */ if ( cmd->io_hdr.status != ZBC_SG_CHECK_CONDITION ) { zbc_sg_set_sense(dev, cmd->sense_buf); ret = -EIO; goto out; } if ( (zbc_sg_cmd_driver_status(cmd) == ZBC_SG_DRIVER_SENSE) && (cmd->io_hdr.sb_len_wr > 21) && (cmd->sense_buf[21] != 0x50) ) { zbc_sg_set_sense(dev, cmd->sense_buf); ret = -EIO; goto out; } cmd->io_hdr.status = 0; } if ( cmd->io_hdr.status || (cmd->io_hdr.host_status != ZBC_SG_DID_OK) || (zbc_sg_cmd_driver_status(cmd) && (zbc_sg_cmd_driver_status(cmd) != ZBC_SG_DRIVER_SENSE)) ) { if ( zbc_log_level >= ZBC_LOG_DEBUG ) { zbc_error("%s: Command %s failed with status 0x%02x (0x%02x), host status 0x%04x, driver status 0x%04x (flags 0x%04x)\n", dev->zbd_filename, zbc_sg_cmd_name(cmd), (unsigned int)cmd->io_hdr.status, (unsigned int)cmd->io_hdr.masked_status, (unsigned int)cmd->io_hdr.host_status, (unsigned int)zbc_sg_cmd_driver_status(cmd), (unsigned int)zbc_sg_cmd_driver_flags(cmd)); if ( cmd->io_hdr.sb_len_wr ) { zbc_debug("%s: Sense data (%d B):\n", dev->zbd_filename, cmd->io_hdr.sb_len_wr); zbc_sg_print_bytes(dev, cmd->sense_buf, cmd->io_hdr.sb_len_wr); } else { zbc_debug("%s: No sense data\n", dev->zbd_filename); } } zbc_sg_set_sense(dev, cmd->sense_buf); ret = -EIO; goto out; } if ( cmd->io_hdr.resid ) { zbc_debug("%s: Transfer missing %d B of data\n", dev->zbd_filename, cmd->io_hdr.resid); cmd->out_bufsz -= cmd->io_hdr.resid; } zbc_debug("%s: Command %s executed in %u ms, %zu B transfered\n", dev->zbd_filename, zbc_sg_cmd_name(cmd), cmd->io_hdr.duration, cmd->out_bufsz); out: return( ret ); }