This is the initial try to support persistent reservation I finished only READ_KEYS, READ_RESERVATION, REPORT_CAPABILITIES, and REGISTER, RESERVE, RELEASE (and they are not complete). So I guess that this enables you to only play sg_persist and see if commands are rejected properly. But I'd really appreciate any input (comments, test results, patches, etc). As I wrote before, I'd like to add persistent reservation support and release 1.0.0 this year. = From: FUJITA Tomonori <fujita.tomonori@xxxxxxxxxxxxx> Date: Sun, 25 Oct 2009 22:03:41 +0900 Subject: [PATCH] persistent reservation support Signed-off-by: FUJITA Tomonori <fujita.tomonori@xxxxxxxxxxxxx> --- usr/sbc.c | 38 +++--- usr/scc.c | 2 +- usr/scsi.c | 40 +++++ usr/scsi.h | 31 ++++ usr/smc.c | 2 +- usr/spc.c | 488 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++- usr/spc.h | 8 +- usr/ssc.c | 2 +- usr/target.c | 10 ++ usr/tgtd.h | 21 +++ 10 files changed, 611 insertions(+), 31 deletions(-) diff --git a/usr/sbc.c b/usr/sbc.c index 710165d..f443508 100644 --- a/usr/sbc.c +++ b/usr/sbc.c @@ -284,9 +284,9 @@ static struct device_type_template sbc_template = { {spc_illegal_op,}, {spc_illegal_op,}, - {sbc_rw,}, + {sbc_rw, NULL, PR_EA_FA|PR_EA_FN}, {spc_illegal_op,}, - {sbc_rw,}, + {sbc_rw, NULL, PR_WE_FA|PR_EA_FA|PR_WE_FN|PR_EA_FN}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, @@ -299,14 +299,14 @@ static struct device_type_template sbc_template = { {spc_inquiry,}, {spc_illegal_op,}, {spc_illegal_op,}, - {sbc_mode_select}, + {sbc_mode_select, NULL, PR_WE_FA|PR_EA_FA|PR_WE_FN|PR_EA_FN}, {sbc_reserve,}, {sbc_release,}, {spc_illegal_op,}, {spc_illegal_op,}, - {spc_mode_sense,}, - {spc_start_stop,}, + {spc_mode_sense, NULL, PR_WE_FA|PR_EA_FA|PR_WE_FN|PR_EA_FN}, + {spc_start_stop, NULL, PR_SPECIAL}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, @@ -322,14 +322,14 @@ static struct device_type_template sbc_template = { {spc_illegal_op,}, {spc_illegal_op,}, - {sbc_rw}, + {sbc_rw, NULL, PR_EA_FA|PR_EA_FN}, {spc_illegal_op,}, - {sbc_rw}, + {sbc_rw, NULL, PR_WE_FA|PR_EA_FA|PR_WE_FN|PR_EA_FN}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, - {sbc_verify,}, + {sbc_verify, NULL, PR_EA_FA|PR_EA_FN}, /* 0x30 */ {spc_illegal_op,}, @@ -337,7 +337,7 @@ static struct device_type_template sbc_template = { {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, - {sbc_sync_cache,}, + {sbc_sync_cache, NULL, PR_WE_FA|PR_EA_FA|PR_WE_FN|PR_EA_FN}, {spc_illegal_op,}, {spc_illegal_op,}, @@ -358,18 +358,18 @@ static struct device_type_template sbc_template = { {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, - {sbc_mode_select,}, + {sbc_mode_select, NULL, PR_WE_FA|PR_EA_FA|PR_WE_FN|PR_EA_FN}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, - {spc_mode_sense,}, - {spc_illegal_op,}, - {spc_illegal_op,}, + {spc_mode_sense, NULL, PR_WE_FA|PR_EA_FA|PR_WE_FN|PR_EA_FN}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, + {spc_service_action, persistent_reserve_in_actions,}, + {spc_service_action, persistent_reserve_out_actions,}, [0x60 ... 0x7f] = {spc_illegal_op,}, @@ -383,9 +383,9 @@ static struct device_type_template sbc_template = { {spc_illegal_op,}, {spc_illegal_op,}, - {sbc_rw,}, + {sbc_rw, NULL, PR_EA_FA|PR_EA_FN}, {spc_illegal_op,}, - {sbc_rw,}, + {sbc_rw, NULL, PR_WE_FA|PR_EA_FA|PR_WE_FN|PR_EA_FN}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, @@ -394,7 +394,7 @@ static struct device_type_template sbc_template = { /* 0x90 */ {spc_illegal_op,}, - {sbc_sync_cache,}, + {sbc_sync_cache, NULL, PR_WE_FA|PR_EA_FA|PR_WE_FN|PR_EA_FN}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, @@ -415,15 +415,15 @@ static struct device_type_template sbc_template = { {spc_report_luns,}, {spc_illegal_op,}, {spc_illegal_op,}, - {spc_maint_in, maint_in_service_actions,}, + {spc_service_action, maint_in_service_actions,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, - {sbc_rw,}, + {sbc_rw, NULL, PR_EA_FA|PR_EA_FN}, {spc_illegal_op,}, - {sbc_rw,}, + {sbc_rw, NULL, PR_WE_FA|PR_EA_FA|PR_WE_FN|PR_EA_FN}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, diff --git a/usr/scc.c b/usr/scc.c index eed48a0..27bc07b 100644 --- a/usr/scc.c +++ b/usr/scc.c @@ -143,7 +143,7 @@ static struct device_type_template scc_template = { {spc_report_luns,}, {spc_illegal_op,}, {spc_illegal_op,}, - {spc_maint_in, maint_in_service_actions,}, + {spc_service_action, maint_in_service_actions,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, diff --git a/usr/scsi.c b/usr/scsi.c index daf4aef..61a2d7e 100644 --- a/usr/scsi.c +++ b/usr/scsi.c @@ -183,6 +183,43 @@ uint32_t scsi_rw_count(uint8_t *scb) return cnt; } +static int scsi_pr_access_check(struct scsi_cmd *cmd) +{ + unsigned char op = cmd->scb[0]; + uint8_t bits; + uint8_t pr_type; + struct registration *reg; + int conflict = 0; + + if (!cmd->dev->pr_holder) + return 0; + + reg = lookup_registration_by_nexus(cmd->dev, cmd->cmd_itn_id); + + if (reg == cmd->dev->pr_holder) + return 0; + + pr_type = cmd->dev->pr_holder->pr_type; + bits = cmd->dev->dev_type_template.ops[op].pr_conflict_bits; + + if (pr_type == PR_TYPE_WRITE_EXCLUSIVE || + pr_type == PR_TYPE_EXCLUSIVE_ACCESS) + conflict = bits & (PR_WE_FA|PR_EA_FA); + else { + if (reg) + conflict = bits & PR_RR_FR; + else { + if (pr_type == PR_TYPE_WRITE_EXCLUSIVE_REGONLY || + pr_type == PR_TYPE_WRITE_EXCLUSIVE_ALLREG) + conflict = bits & PR_WE_FN; + else + conflict = bits & PR_EA_FN; + } + } + + return conflict; +} + int scsi_cmd_perform(int host_no, struct scsi_cmd *cmd) { int ret; @@ -227,6 +264,9 @@ int scsi_cmd_perform(int host_no, struct scsi_cmd *cmd) return SAM_STAT_CHECK_CONDITION; } + if (scsi_pr_access_check(cmd)) + return SAM_STAT_RESERVATION_CONFLICT; + return cmd->dev->dev_type_template.ops[op].cmd_perform(host_no, cmd); } diff --git a/usr/scsi.h b/usr/scsi.h index 84fadff..9fd78fd 100644 --- a/usr/scsi.h +++ b/usr/scsi.h @@ -201,12 +201,14 @@ #define ASC_INVALID_FIELD_IN_CDB 0x2400 #define ASC_LUN_NOT_SUPPORTED 0x2500 #define ASC_INVALID_FIELD_IN_PARMS 0x2600 +#define ASC_INVALID_RELEASE_OF_PERSISTENT_RESERVATION 0x2604 #define ASC_INCOMPATIBLE_FORMAT 0x3005 #define ASC_SAVING_PARMS_UNSUP 0x3900 #define ASC_MEDIUM_DEST_FULL 0x3b0d #define ASC_MEDIUM_SRC_EMPTY 0x3b0e #define ASC_POSITION_PAST_BOM 0x3b0c #define ASC_MEDIUM_REMOVAL_PREVENTED 0x5302 +#define ASC_INSUFFICENT_REGISTRATION_RESOURCES 0x5504 #define ASC_BAD_MICROCODE_DETECTED 0x8283 /* Key 6: Unit Attention */ @@ -214,6 +216,7 @@ #define ASC_POWERON_RESET 0x2900 #define ASC_I_T_NEXUS_LOSS_OCCURRED 0x2907 #define ASC_MODE_PARAMETERS_CHANGED 0x2a01 +#define ASC_RESERVATIONS_RELEASED 0x2a04 #define ASC_INSUFFICIENT_TIME_FOR_OPERATION 0x2e00 #define ASC_COMMANDS_CLEARED_BY_ANOTHOR_INI 0x2f00 #define ASC_MICROCODE_DOWNLOADED 0x3f01 @@ -225,4 +228,32 @@ #define ASC_WRITE_PROTECT 0x2700 #define ASC_MEDIUM_OVERWRITE_ATTEMPTED 0x300c + +/* PERSISTENT_RESERVE_IN service action codes */ +#define PR_IN_READ_KEYS 0x00 +#define PR_IN_READ_RESERVATION 0x01 +#define PR_IN_REPORT_CAPABILITIES 0x02 +#define PR_IN_READ_FULL_STATUS 0x03 + +/* PERSISTENT_RESERVE_OUT service action codes */ +#define PR_OUT_REGISTER 0x00 +#define PR_OUT_RESERVE 0x01 +#define PR_OUT_RELEASE 0x02 +#define PR_OUT_CLEAR 0x03 +#define PR_OUT_PREEMPT 0x04 +#define PR_OUT_PREEMPT_AND_ABORT 0x05 +#define PR_OUT_REGISTER_AND_IGNORE_EXISTING_KEY 0x06 +#define PR_OUT_REGISTER_AND_MOVE 0x07 + +/* Persistent Reservation scope */ +#define PR_LU_SCOPE 0x00 + +/* Persistent Reservation Type Mask format */ +#define PR_TYPE_WRITE_EXCLUSIVE 0x01 +#define PR_TYPE_EXCLUSIVE_ACCESS 0x03 +#define PR_TYPE_WRITE_EXCLUSIVE_REGONLY 0x05 +#define PR_TYPE_EXCLUSIVE_ACCESS_REGONLY 0x06 +#define PR_TYPE_WRITE_EXCLUSIVE_ALLREG 0x07 +#define PR_TYPE_EXCLUSIVE_ACCESS_ALLREG 0x08 + #endif diff --git a/usr/smc.c b/usr/smc.c index ab36e9c..6430882 100644 --- a/usr/smc.c +++ b/usr/smc.c @@ -861,7 +861,7 @@ struct device_type_template smc_template = { {spc_report_luns,}, {spc_illegal_op,}, {spc_illegal_op,}, - {spc_maint_in, maint_in_service_actions,}, + {spc_service_action, maint_in_service_actions,}, {spc_illegal_op,}, {smc_move_medium,}, {spc_illegal_op,}, diff --git a/usr/spc.c b/usr/spc.c index 1a75766..96ac8db 100644 --- a/usr/spc.c +++ b/usr/spc.c @@ -720,7 +720,7 @@ struct service_action maint_in_service_actions[] = { {0, NULL} }; -struct service_action * +static struct service_action * find_service_action(struct service_action *service_action, uint32_t action) { while (service_action->cmd_perform) { @@ -731,16 +731,20 @@ find_service_action(struct service_action *service_action, uint32_t action) return NULL; } -/** - * This functions emulates the various commands using the 0xa3 cdb opcode +/* + * This is useful for the various commands using the SERVICE ACTION + * format. */ -int spc_maint_in(int host_no, struct scsi_cmd *cmd) +int spc_service_action(int host_no, struct scsi_cmd *cmd) { uint8_t action; - struct service_action *service_action; + unsigned char op = cmd->scb[0]; + struct service_action *service_action, *actions; action = cmd->scb[1] & 0x1f; - service_action = find_service_action(maint_in_service_actions, action); + actions = cmd->dev->dev_type_template.ops[op].service_actions; + + service_action = find_service_action(actions, action); if (!service_action) { scsi_set_in_resid_by_actual(cmd, 0); @@ -752,6 +756,477 @@ int spc_maint_in(int host_no, struct scsi_cmd *cmd) return service_action->cmd_perform(host_no, cmd); } +static int spc_pr_read_keys(int host_no, struct scsi_cmd *cmd) +{ + uint16_t asc = ASC_INVALID_FIELD_IN_CDB; + uint8_t key = ILLEGAL_REQUEST; + struct registration *reg; + uint16_t len; + uint32_t keys; + uint8_t *buf; + int off; + + len = get_unaligned_be16(cmd->scb + 7); + if (len < 8) + goto sense; + + if (scsi_get_in_length(cmd) < len) + goto sense; + + buf = scsi_get_in_buffer(cmd); + memset(buf, 0, len); + + len &= ~(8 - 1); + keys = 0; + off = 8; + + put_unaligned_be32(cmd->dev->prgeneration, &buf[0]); + + list_for_each_entry(reg, &cmd->dev->registration_list, + registration_siblings) { + if (!len) + continue; + put_unaligned_be64(reg->key, &buf[off]); + + len -= 8; + keys++; + } + + put_unaligned_be32(keys * 8, &buf[4]); + + scsi_set_in_resid_by_actual(cmd, keys * 8 + 8); + + return SAM_STAT_GOOD; +sense: + scsi_set_in_resid_by_actual(cmd, 0); + sense_data_build(cmd, key, asc); + return SAM_STAT_CHECK_CONDITION; +} + +static int spc_pr_read_reservation(int host_no, struct scsi_cmd *cmd) +{ + uint16_t asc = ASC_INVALID_FIELD_IN_CDB; + uint8_t key = ILLEGAL_REQUEST; + struct registration *reg; + uint16_t len; + uint8_t *buf; + uint64_t res_key; + + reg = cmd->dev->pr_holder; + + if (reg) + len = 24; + else + len = 8; + + if (get_unaligned_be16(cmd->scb + 7) < len) + goto sense; + + if (scsi_get_in_length(cmd) < len) + goto sense; + + buf = scsi_get_in_buffer(cmd); + memset(buf, 0, len); + + put_unaligned_be32(cmd->dev->prgeneration, &buf[0]); + + if (reg) { + put_unaligned_be32(16, &buf[4]); + + if (reg->pr_type == PR_TYPE_WRITE_EXCLUSIVE_ALLREG || + reg->pr_type == PR_TYPE_EXCLUSIVE_ACCESS_ALLREG) + res_key = 0; + else + res_key = reg->key; + + put_unaligned_be32(res_key, &buf[8]); + buf[21] = ((reg->pr_scope << 4) & 0xf0) | (reg->pr_type & 0x0f); + } else + put_unaligned_be32(0, &buf[4]); + + scsi_set_in_resid_by_actual(cmd, len); + + return SAM_STAT_GOOD; +sense: + scsi_set_in_resid_by_actual(cmd, 0); + sense_data_build(cmd, key, asc); + return SAM_STAT_CHECK_CONDITION; +} + +static int spc_pr_report_capabilities(int host_no, struct scsi_cmd *cmd) +{ + uint16_t asc = ASC_INVALID_FIELD_IN_CDB; + uint8_t key = ILLEGAL_REQUEST; + uint8_t *buf; + uint16_t len; + + len = get_unaligned_be16(cmd->scb + 7); + if (len < 8) + goto sense; + + if (scsi_get_in_length(cmd) < len) + goto sense; + + buf = scsi_get_in_buffer(cmd); + + len = 8; + + memset(buf, 0, len); + + put_unaligned_be16(len, &buf[0]); + + /* we don't set any capability for now */ + + /* Persistent Reservation Type Mask format */ + buf[4] |= 0x80; /* PR_TYPE_EXCLUSIVE_ACCESS_ALLREG */ + buf[4] |= 0x40; /* PR_TYPE_EXCLUSIVE_ACCESS_REGONLY */ + buf[4] |= 0x20; /* PR_TYPE_WRITE_EXCLUSIVE_REGONLY */ + buf[4] |= 0x08; /* PR_TYPE_EXCLUSIVE_ACCESS */ + buf[4] |= 0x02; /* PR_TYPE_WRITE_EXCLUSIVE */ + buf[5] |= 0x01; /* PR_TYPE_EXCLUSIVE_ACCESS_ALLREG */ + + return SAM_STAT_GOOD; +sense: + scsi_set_in_resid_by_actual(cmd, 0); + sense_data_build(cmd, key, asc); + return SAM_STAT_CHECK_CONDITION; +} + +static int spc_pr_read_full_status(int host_no, struct scsi_cmd *cmd) +{ + uint16_t asc = ASC_INVALID_FIELD_IN_CDB; + uint8_t key = ILLEGAL_REQUEST; + struct registration *reg; + uint16_t len; + uint32_t keys; + uint8_t *buf; + int off; + + len = get_unaligned_be16(cmd->scb + 7); + if (len < 8) + goto sense; + if (scsi_get_in_length(cmd) < len) + goto sense; + + buf = scsi_get_in_buffer(cmd); + memset(buf, 0, len); + + put_unaligned_be32(cmd->dev->prgeneration, &buf[0]); + + len &= ~(24 - 1); + off = 8; + keys = 0; + + list_for_each_entry(reg, &cmd->dev->registration_list, + registration_siblings) { + + if (!len) + continue; + + put_unaligned_be64(reg->key, &buf[off + 0]); + + /* TODO */ + + put_unaligned_be32(0, &buf[off + 20]); + + keys++; + len -= 24; + off += 24; + } + + put_unaligned_be32(keys * 24, &buf[4]); + + scsi_set_in_resid_by_actual(cmd, keys * 24 + 8); + + return SAM_STAT_GOOD; +sense: + scsi_set_in_resid_by_actual(cmd, 0); + sense_data_build(cmd, key, asc); + return SAM_STAT_CHECK_CONDITION; +} + +struct service_action persistent_reserve_in_actions[] = { + {PR_IN_READ_KEYS, spc_pr_read_keys}, + {PR_IN_READ_RESERVATION, spc_pr_read_reservation}, + {PR_IN_REPORT_CAPABILITIES, spc_pr_report_capabilities}, + {PR_IN_READ_FULL_STATUS, spc_pr_read_full_status}, + {0, NULL}, +}; + +struct registration *lookup_registration_by_nexus(struct scsi_lu *lu, + uint64_t nexus_id) +{ + struct registration *reg; + + list_for_each_entry(reg, &lu->registration_list, registration_siblings) { + if (reg->nexus_id == nexus_id) + return reg; + } + + return NULL; +} + +static int check_pr_out_basic_parameter(struct scsi_cmd *cmd) +{ + uint8_t spec_i_pt, all_tg_pt, aptpl; + uint8_t *buf; + uint16_t len = 24; + + if (get_unaligned_be16(cmd->scb + 7) < len) + return 1; + + if (scsi_get_out_length(cmd) < len) + return 1; + + buf = scsi_get_out_buffer(cmd); + + spec_i_pt = buf[20] & (1U << 3); + all_tg_pt = buf[20] & (1U << 2); + aptpl = buf[20] & (1U << 0); + + if (spec_i_pt | all_tg_pt | aptpl) { + /* + * for now, we say that we don't support these bits + * via REPORT CAPABILITIES. + */ + return 1; + } + + return 0; +} + +static int spc_pr_register(int host_no, struct scsi_cmd *cmd) +{ + uint8_t force, key = ILLEGAL_REQUEST; + uint16_t asc = ASC_INVALID_FIELD_IN_CDB; + uint64_t res_key, sa_res_key; + int ret; + uint8_t *buf; + struct registration *reg; + + force = ((cmd->scb[1] & 0x1f) == PR_OUT_REGISTER_AND_IGNORE_EXISTING_KEY); + + ret = check_pr_out_basic_parameter(cmd); + if (ret) + goto sense; + + buf = scsi_get_out_buffer(cmd); + + res_key = get_unaligned_be64(buf); + sa_res_key = get_unaligned_be64(buf + 8); + + reg = lookup_registration_by_nexus(cmd->dev, cmd->cmd_itn_id); + if (reg) { + if (force || reg->key == res_key) { + if (sa_res_key) + reg->key = sa_res_key; + else { + /* unregister */ + list_del(®->registration_siblings); + free(reg); + } + } else + return SAM_STAT_RESERVATION_CONFLICT; + } else { + if (force || !res_key) { + if (sa_res_key) { + reg = zalloc(sizeof(*reg)); + if (!reg) { + key = ILLEGAL_REQUEST; + asc = ASC_INSUFFICENT_REGISTRATION_RESOURCES; + goto sense; + } + reg->key = sa_res_key; + reg->nexus_id = cmd->cmd_itn_id; + list_add_tail(®->registration_siblings, + &cmd->dev->registration_list); + } else + ; /* do nothing */ + } else + return SAM_STAT_RESERVATION_CONFLICT; + } + + cmd->dev->prgeneration++; + + return SAM_STAT_GOOD; +sense: + scsi_set_in_resid_by_actual(cmd, 0); + sense_data_build(cmd, key, asc); + return SAM_STAT_CHECK_CONDITION; +} + +static int spc_pr_reserve(int host_no, struct scsi_cmd *cmd) +{ + uint16_t asc = ASC_INVALID_FIELD_IN_CDB; + uint8_t key = ILLEGAL_REQUEST; + uint8_t pr_scope, pr_type; + uint8_t *buf; + uint64_t res_key, sa_res_key; + int ret; + struct registration *reg, *holder; + + ret = check_pr_out_basic_parameter(cmd); + if (ret) + goto sense; + + pr_scope = (cmd->scb[2] & 0xf0) >> 4; + pr_type = cmd->scb[2] & 0x0f; + + buf = scsi_get_out_buffer(cmd); + + res_key = get_unaligned_be64(buf); + sa_res_key = get_unaligned_be64(buf + 8); + + switch (pr_type) { + case PR_TYPE_WRITE_EXCLUSIVE: + case PR_TYPE_EXCLUSIVE_ACCESS: + case PR_TYPE_WRITE_EXCLUSIVE_REGONLY: + case PR_TYPE_EXCLUSIVE_ACCESS_REGONLY: + case PR_TYPE_WRITE_EXCLUSIVE_ALLREG: + case PR_TYPE_EXCLUSIVE_ACCESS_ALLREG: + break; + default: + goto sense; + } + + if (pr_scope != PR_LU_SCOPE) + goto sense; + + reg = lookup_registration_by_nexus(cmd->dev, cmd->cmd_itn_id); + if (!reg) + return SAM_STAT_RESERVATION_CONFLICT; + + holder = cmd->dev->pr_holder; + if (holder) { + if (holder != reg) + return SAM_STAT_RESERVATION_CONFLICT; + + if (holder->pr_type != pr_type || + holder->pr_scope != pr_scope) + return SAM_STAT_RESERVATION_CONFLICT; + + return SAM_STAT_GOOD; + } + + reg->pr_scope = pr_scope; + reg->pr_type = pr_type; + cmd->dev->pr_holder = reg; + + return SAM_STAT_GOOD; +sense: + scsi_set_in_resid_by_actual(cmd, 0); + sense_data_build(cmd, key, asc); + return SAM_STAT_CHECK_CONDITION; +} + +static int spc_pr_release(int host_no, struct scsi_cmd *cmd) +{ + uint16_t asc = ASC_INVALID_FIELD_IN_CDB; + uint8_t key = ILLEGAL_REQUEST; + uint8_t pr_scope, pr_type; + uint8_t *buf; + uint64_t res_key, sa_res_key; + int ret; + struct registration *reg, *holder, *sibling; + + ret = check_pr_out_basic_parameter(cmd); + if (ret) + goto sense; + + pr_scope = (cmd->scb[2] & 0xf0) >> 4; + pr_type = cmd->scb[2] & 0x0f; + + buf = scsi_get_out_buffer(cmd); + + res_key = get_unaligned_be64(buf); + sa_res_key = get_unaligned_be64(buf + 8); + + reg = lookup_registration_by_nexus(cmd->dev, cmd->cmd_itn_id); + if (!reg) + return SAM_STAT_RESERVATION_CONFLICT; + + holder = cmd->dev->pr_holder; + if (!holder) + return SAM_STAT_GOOD; + + if (holder != reg) + return SAM_STAT_GOOD; + + if (res_key != reg->key) + return SAM_STAT_RESERVATION_CONFLICT; + + if (reg->pr_scope != pr_scope || reg->pr_type != pr_type) { + asc = ASC_INVALID_RELEASE_OF_PERSISTENT_RESERVATION; + goto sense; + } + + cmd->dev->pr_holder = NULL; + reg->pr_scope = 0; + reg->pr_type = 0; + + switch (pr_type) { + case PR_TYPE_WRITE_EXCLUSIVE_REGONLY: + case PR_TYPE_EXCLUSIVE_ACCESS_REGONLY: + case PR_TYPE_WRITE_EXCLUSIVE_ALLREG: + case PR_TYPE_EXCLUSIVE_ACCESS_ALLREG: + return SAM_STAT_GOOD; + default: + ; + } + + list_for_each_entry(sibling, &cmd->dev->registration_list, + registration_siblings) { + /* we don't send myself */ + if (sibling == reg) + continue; + + ua_sense_add_other_it_nexus(sibling->nexus_id, + cmd->dev, ASC_RESERVATIONS_RELEASED); + } + + return SAM_STAT_GOOD; +sense: + scsi_set_in_resid_by_actual(cmd, 0); + sense_data_build(cmd, key, asc); + return SAM_STAT_CHECK_CONDITION; +} + +#if 0 +static int spc_pr_clear(int host_no, struct scsi_cmd *cmd) +{ + return SAM_STAT_GOOD; +} + +static int spc_pr_preempt(int host_no, struct scsi_cmd *cmd) +{ + return SAM_STAT_GOOD; +} + +static int spc_pr_preempt_and_abort(int host_no, struct scsi_cmd *cmd) +{ + return SAM_STAT_GOOD; +} + +static int spc_pr_register_and_move(int host_no, struct scsi_cmd *cmd) +{ + return SAM_STAT_GOOD; +} +#endif + +struct service_action persistent_reserve_out_actions[] = { + {PR_OUT_REGISTER, spc_pr_register}, + {PR_OUT_RESERVE, spc_pr_reserve}, + {PR_OUT_RELEASE, spc_pr_release}, +#if 0 + {PR_OUT_CLEAR, spc_pr_clear}, + {PR_OUT_PREEMPT, spc_pr_preempt}, + {PR_OUT_PREEMPT_AND_ABORT, spc_pr_preempt_and_abort}, + {PR_OUT_REGISTER_AND_IGNORE_EXISTING_KEY, spc_pr_register}, + {PR_OUT_REGISTER_AND_MOVE, spc_pr_register_and_move}, +#endif + {0, NULL}, +}; + int spc_request_sense(int host_no, struct scsi_cmd *cmd) { uint8_t *data; @@ -1065,4 +1540,3 @@ void spc_lu_exit(struct scsi_lu *lu) if (lu->mode_pgs[i]) free(lu->mode_pgs[i]); } - diff --git a/usr/spc.h b/usr/spc.h index cfc9cf3..a731b94 100644 --- a/usr/spc.h +++ b/usr/spc.h @@ -1,8 +1,10 @@ #ifndef __SPC_H #define __SPC_H -extern struct service_action maint_in_service_actions[]; -extern int spc_maint_in(int host_no, struct scsi_cmd *cmd); +extern struct service_action maint_in_service_actions[], + persistent_reserve_in_actions[], persistent_reserve_out_actions[]; + +extern int spc_service_action(int host_no, struct scsi_cmd *cmd); extern int spc_inquiry(int host_no, struct scsi_cmd *cmd); extern int spc_report_luns(int host_no, struct scsi_cmd *cmd); extern int spc_start_stop(int host_no, struct scsi_cmd *cmd); @@ -27,4 +29,6 @@ extern struct vpd *alloc_vpd(uint16_t size); extern int spc_lu_online(struct scsi_lu *lu); extern int spc_lu_offline(struct scsi_lu *lu); +struct registration *lookup_registration_by_nexus(struct scsi_lu *lu, + uint64_t nexus_id); #endif diff --git a/usr/ssc.c b/usr/ssc.c index 834ec16..84fa317 100644 --- a/usr/ssc.c +++ b/usr/ssc.c @@ -310,7 +310,7 @@ static struct device_type_template ssc_template = { {spc_report_luns,}, {spc_illegal_op,}, {spc_illegal_op,}, - {spc_maint_in, maint_in_service_actions,}, + {spc_service_action, maint_in_service_actions,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, diff --git a/usr/target.c b/usr/target.c index 14e3632..3f4f05c 100644 --- a/usr/target.c +++ b/usr/target.c @@ -490,6 +490,9 @@ int tgt_device_create(int tid, int dev_type, uint64_t lun, char *params, lu->tgt = target; lu->lun = lun; tgt_cmd_queue_init(&lu->cmd_queue); + INIT_LIST_HEAD(&lu->registration_list); + lu->prgeneration = 0; + lu->pr_holder = NULL; if (lu->dev_type_template.lu_init) { ret = lu->dev_type_template.lu_init(lu); @@ -567,6 +570,7 @@ int tgt_device_destroy(int tid, uint64_t lun, int force) struct scsi_lu *lu; struct it_nexus *itn; struct it_nexus_lu_info *itn_lu, *next; + struct registration *reg, *reg_next; int ret; dprintf("%u %" PRIu64 "\n", tid, lun); @@ -606,6 +610,12 @@ int tgt_device_destroy(int tid, uint64_t lun, int force) } list_del(&lu->device_siblings); + + list_for_each_entry_safe(reg, reg_next, &lu->registration_list, + registration_siblings) { + free(reg); + } + free(lu); list_for_each_entry(itn, &target->it_nexus_list, nexus_siblings) { diff --git a/usr/tgtd.h b/usr/tgtd.h index febaed7..96c744f 100644 --- a/usr/tgtd.h +++ b/usr/tgtd.h @@ -90,8 +90,16 @@ struct service_action { struct device_type_operations { int (*cmd_perform)(int host_no, struct scsi_cmd *cmd); struct service_action *service_actions; + uint8_t pr_conflict_bits; }; +#define PR_SPECIAL (1U << 5) +#define PR_WE_FA (1U << 4) +#define PR_EA_FA (1U << 3) +#define PR_RR_FR (1U << 2) +#define PR_WE_FN (1U << 1) +#define PR_EA_FN (1U << 0) + struct device_type_template { unsigned char type; @@ -126,6 +134,15 @@ struct mode_pg { uint8_t mode_data[0]; /* Rest of mode page info */ }; +struct registration { + uint64_t key; + uint64_t nexus_id; + struct list_head registration_siblings; + + uint8_t pr_scope; + uint8_t pr_type; +}; + struct scsi_lu { int fd; uint64_t addr; /* persistent mapped address */ @@ -152,6 +169,10 @@ struct scsi_lu { struct lu_phy_attr attrs; + struct list_head registration_list; + uint32_t prgeneration; + struct registration *pr_holder; + /* A pointer for each modules private use. * Currently used by ssc, smc and mmc modules. */ -- 1.5.6.5 -- To unsubscribe from this list: send the line "unsubscribe stgt" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html