diff --git a/drivers/scsi/scsi_common.c b/drivers/scsi/scsi_common.c index 11bf6c275d4e..b7a7a2eea887 100644 --- a/drivers/scsi/scsi_common.c +++ b/drivers/scsi/scsi_common.c @@ -64,6 +64,27 @@ const char *scsi_device_type(unsigned type) } EXPORT_SYMBOL(scsi_device_type); +enum pr_type scsi_pr_type_to_block(enum scsi_pr_type type) +{ + switch (type) { + case SCSI_PR_WRITE_EXCLUSIVE: + return PR_WRITE_EXCLUSIVE; + case SCSI_PR_EXCLUSIVE_ACCESS: + return PR_EXCLUSIVE_ACCESS; + case SCSI_PR_WRITE_EXCLUSIVE_REG_ONLY: + return PR_WRITE_EXCLUSIVE_REG_ONLY; + case SCSI_PR_EXCLUSIVE_ACCESS_REG_ONLY: + return PR_EXCLUSIVE_ACCESS_REG_ONLY; + case SCSI_PR_WRITE_EXCLUSIVE_ALL_REGS: + return PR_WRITE_EXCLUSIVE_ALL_REGS; + case SCSI_PR_EXCLUSIVE_ACCESS_ALL_REGS: + return PR_EXCLUSIVE_ACCESS_ALL_REGS; + } + + return 0; +} +EXPORT_SYMBOL_GPL(scsi_pr_type_to_block); + enum scsi_pr_type block_pr_type_to_scsi(enum pr_type type) { switch (type) { diff --git a/drivers/scsi/sd.c b/drivers/scsi/sd.c index fa3123a63f65..3e7a69eeb8f0 100644 --- a/drivers/scsi/sd.c +++ b/drivers/scsi/sd.c @@ -1723,6 +1723,95 @@ static int sd_scsi_to_pr_err(struct scsi_sense_hdr *sshdr, int result) } } +static int sd_pr_in_command(struct block_device *bdev, u8 sa, + unsigned char *data, int data_len) +{ + struct scsi_disk *sdkp = scsi_disk(bdev->bd_disk); + struct scsi_device *sdev = sdkp->device; + struct scsi_sense_hdr sshdr; + u8 cmd[10] = { PERSISTENT_RESERVE_IN, sa }; + const struct scsi_exec_args exec_args = { + .sshdr = &sshdr, + }; + int result; + + put_unaligned_be16(data_len, &cmd[7]); + + result = scsi_execute_cmd(sdev, cmd, REQ_OP_DRV_IN, data, data_len, + SD_TIMEOUT, sdkp->max_retries, &exec_args); + if (scsi_status_is_check_condition(result) && + scsi_sense_valid(&sshdr)) { + sdev_printk(KERN_INFO, sdev, "PR command failed: %d\n", result); + scsi_print_sense_hdr(sdev, NULL, &sshdr); + } + + if (result <= 0) + return result; + + return sd_scsi_to_pr_err(&sshdr, result); +} + +static int sd_pr_read_keys(struct block_device *bdev, struct pr_keys *keys_info) +{ + int result, i, data_offset, num_copy_keys; + u32 num_keys = keys_info->num_keys; + int data_len = num_keys * 8 + 8; + u8 *data; + + data = kzalloc(data_len, GFP_KERNEL); + if (!data) + return -ENOMEM; + + result = sd_pr_in_command(bdev, READ_KEYS, data, data_len); + if (result) + goto free_data; + + keys_info->generation = get_unaligned_be32(&data[0]); + keys_info->num_keys = get_unaligned_be32(&data[4]) / 8; + + data_offset = 8; + num_copy_keys = min(num_keys, keys_info->num_keys); + + for (i = 0; i < num_copy_keys; i++) { + keys_info->keys[i] = get_unaligned_be64(&data[data_offset]); + data_offset += 8; + } + +free_data: + kfree(data); + return result; +} + +static int sd_pr_read_reservation(struct block_device *bdev, + struct pr_held_reservation *rsv) +{ + struct scsi_disk *sdkp = scsi_disk(bdev->bd_disk); + struct scsi_device *sdev = sdkp->device; + u8 data[24] = { }; + int result, len; + + result = sd_pr_in_command(bdev, READ_RESERVATION, data, sizeof(data)); + if (result) + return result; + + len = get_unaligned_be32(&data[4]); + if (!len) + return 0; + + /* Make sure we have at least the key and type */ + if (len < 14) { + sdev_printk(KERN_INFO, sdev, + "READ RESERVATION failed due to short return buffer of %d bytes\n", + len); + return -EINVAL; + } + + rsv->generation = get_unaligned_be32(&data[0]); + rsv->key = get_unaligned_be64(&data[8]); + rsv->type = scsi_pr_type_to_block(data[21] & 0x0f); + return 0; +} + static int sd_pr_out_command(struct block_device *bdev, u8 sa, u64 key, u64 sa_key, enum scsi_pr_type type, u8 flags) { @@ -1804,6 +1893,8 @@ static const struct pr_ops sd_pr_ops = { .pr_release = sd_pr_release, .pr_preempt = sd_pr_preempt, .pr_clear = sd_pr_clear, + .pr_read_keys = sd_pr_read_keys, + .pr_read_reservation = sd_pr_read_reservation, }; static void scsi_disk_free_disk(struct gendisk *disk) diff --git a/include/scsi/scsi_common.h b/include/scsi/scsi_common.h index e25291bbbe9b..fb58715fac86 100644 --- a/include/scsi/scsi_common.h +++ b/include/scsi/scsi_common.h @@ -20,6 +20,7 @@ enum scsi_pr_type { }; enum scsi_pr_type block_pr_type_to_scsi(enum pr_type type); +enum pr_type scsi_pr_type_to_block(enum scsi_pr_type type); static inline unsigned scsi_varlen_cdb_length(const void *hdr) diff --git a/include/scsi/scsi_proto.h b/include/scsi/scsi_proto.h index fbe5bdfe4d6e..07d65c1f59db 100644 --- a/include/scsi/scsi_proto.h +++ b/include/scsi/scsi_proto.h @@ -151,6 +151,11 @@ #define ZO_FINISH_ZONE 0x02 #define ZO_OPEN_ZONE 0x03 #define ZO_RESET_WRITE_POINTER 0x04 +/* values for PR in service action */ +#define READ_KEYS 0x00 +#define READ_RESERVATION 0x01 +#define REPORT_CAPABILITES 0x02 +#define READ_FULL_STATUS 0x03 /* values for variable length command */ #define XDREAD_32 0x03 #define XDWRITE_32 0x04