I think that the persistent reservation support patch is ready for merging. The patch should not affect the existing features (that is, it doesn't hurt you who are not interested in persistent reservation support). = From: FUJITA Tomonori <fujita.tomonori@xxxxxxxxxxxxx> Subject: [PATCH] add persistent reservation support This supports: - PR_IN_READ_KEYS - PR_IN_READ_RESERVATION - PR_IN_REPORT_CAPABILITIES - PR_OUT_REGISTER - PR_OUT_RESERVE - PR_OUT_RELEASE - PR_OUT_CLEAR - PR_OUT_PREEMPT - PR_OUT_REGISTER_AND_IGNORE_EXISTING_KEY - PR_OUT_REGISTER_AND_MOVE Signed-off-by: FUJITA Tomonori <fujita.tomonori@xxxxxxxxxxxxx> --- usr/driver.h | 2 + usr/iscsi/iscsid.c | 39 +++ usr/iscsi/iscsid.h | 1 + usr/iscsi/session.c | 2 +- usr/sbc.c | 38 ++-- usr/scc.c | 2 +- usr/scsi.c | 3 + usr/scsi.h | 32 +++ usr/smc.c | 2 +- usr/spc.c | 687 ++++++++++++++++++++++++++++++++++++++++++++++++++- usr/spc.h | 7 +- usr/ssc.c | 2 +- usr/target.c | 16 ++ usr/target.h | 1 + usr/tgtd.h | 22 ++ 15 files changed, 824 insertions(+), 32 deletions(-) diff --git a/usr/driver.h b/usr/driver.h index f9cb386..f53e012 100644 --- a/usr/driver.h +++ b/usr/driver.h @@ -21,6 +21,8 @@ struct tgt_driver { int (*cmd_end_notify)(uint64_t nid, int result, struct scsi_cmd *); int (*mgmt_end_notify)(struct mgmt_req *); + int (*transportid)(int, uint64_t, char *, int); + const char *default_bst; }; diff --git a/usr/iscsi/iscsid.c b/usr/iscsi/iscsid.c index 0199978..5515fb9 100644 --- a/usr/iscsi/iscsid.c +++ b/usr/iscsi/iscsid.c @@ -2241,6 +2241,44 @@ out: return ret; } +static int iscsi_transportid(int tid, uint64_t itn_id, char *buf, int size) +{ + struct iscsi_session *session; + char *p; + uint16_t len; + + session = session_lookup_by_tsih(itn_id); + if (!session) + return 0; + + len = 4; + len += strlen(session->initiator) + 1; + len += 5; /* separator */ + len += 7; /* isid + '\0' */ + + len = ALIGN(len, 4); + + if (len > size) + return len; + + memset(buf, 0, size); + + buf[0] = 0x05; + buf[0] |= 0x40; + + put_unaligned_be16(len - 4, buf + 2); + + sprintf(buf + 4, session->initiator); + + p = buf + (4 + strlen(session->initiator) + 1); + + p += sprintf(p, ",i,0x"); + + memcpy(p, session->isid, sizeof(session->isid)); + + return len; +} + static struct tgt_driver iscsi = { .name = "iscsi", .use_kernel = 0, @@ -2253,6 +2291,7 @@ static struct tgt_driver iscsi = { .show = iscsi_target_show, .cmd_end_notify = iscsi_scsi_cmd_done, .mgmt_end_notify = iscsi_tm_done, + .transportid = iscsi_transportid, .default_bst = "rdwr", }; diff --git a/usr/iscsi/iscsid.h b/usr/iscsi/iscsid.h index 9eecfa2..6b982cb 100644 --- a/usr/iscsi/iscsid.h +++ b/usr/iscsi/iscsid.h @@ -293,6 +293,7 @@ extern void iscsi_free_cmd_task(struct iscsi_task *task); /* session.c */ extern struct iscsi_session *session_find_name(int tid, const char *iname, uint8_t *isid); +extern struct iscsi_session *session_lookup_by_tsih(uint16_t tsih); extern int session_create(struct iscsi_connection *conn); extern void session_get(struct iscsi_session *session); extern void session_put(struct iscsi_session *session); diff --git a/usr/iscsi/session.c b/usr/iscsi/session.c index 028d538..46864c7 100644 --- a/usr/iscsi/session.c +++ b/usr/iscsi/session.c @@ -52,7 +52,7 @@ struct iscsi_session *session_find_name(int tid, const char *iname, uint8_t *isi return NULL; } -static struct iscsi_session *session_lookup_by_tsih(uint16_t tsih) +struct iscsi_session *session_lookup_by_tsih(uint16_t tsih) { struct iscsi_session *session; list_for_each_entry(session, &sessions_list, hlist) { 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..bf39328 100644 --- a/usr/scsi.c +++ b/usr/scsi.c @@ -227,6 +227,9 @@ int scsi_cmd_perform(int host_no, struct scsi_cmd *cmd) return SAM_STAT_CHECK_CONDITION; } + if (spc_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..80a0431 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,8 @@ #define ASC_POWERON_RESET 0x2900 #define ASC_I_T_NEXUS_LOSS_OCCURRED 0x2907 #define ASC_MODE_PARAMETERS_CHANGED 0x2a01 +#define ASC_RESERVATIONS_PREEMPTED 0x2a03 +#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 +229,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..4d57091 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,676 @@ int spc_maint_in(int host_no, struct scsi_cmd *cmd) return service_action->cmd_perform(host_no, cmd); } +static int is_pr_holder(struct scsi_lu *lu, struct registration *reg) +{ + if (lu->pr_holder->pr_type == PR_TYPE_WRITE_EXCLUSIVE_ALLREG || + lu->pr_holder->pr_type == PR_TYPE_EXCLUSIVE_ACCESS_ALLREG) + return 1; + + if (lu->pr_holder == reg) + return 1; + + return 0; +} + +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; + off += 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; +} + +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}, + {0, NULL}, +}; + +static struct registration *lookup_registration_by_nexus(struct scsi_lu *lu, + struct it_nexus *itn) +{ + struct registration *reg; + + list_for_each_entry(reg, &lu->registration_list, registration_siblings) { + if (reg->nexus_id == itn->itn_id && + reg->ctime == itn->ctime) + return reg; + } + + return NULL; +} + +static void __unregister(struct scsi_lu *lu, struct registration *reg) +{ + list_del(®->registration_siblings); + free(reg); +} + +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->it_nexus); + if (reg) { + if (force || reg->key == res_key) { + if (sa_res_key) + reg->key = sa_res_key; + else + __unregister(cmd->dev, 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; + reg->ctime = cmd->it_nexus->ctime; + 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->it_nexus); + if (!reg) + return SAM_STAT_RESERVATION_CONFLICT; + + holder = cmd->dev->pr_holder; + if (holder) { + if (!is_pr_holder(cmd->dev, 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->it_nexus); + if (!reg) + return SAM_STAT_RESERVATION_CONFLICT; + + holder = cmd->dev->pr_holder; + if (!holder) + return SAM_STAT_GOOD; + + if (!is_pr_holder(cmd->dev, 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; +} + +static int spc_pr_clear(int host_no, struct scsi_cmd *cmd) +{ + uint16_t asc = ASC_INVALID_FIELD_IN_CDB; + uint8_t key = ILLEGAL_REQUEST; + uint8_t *buf; + uint64_t res_key, sa_res_key; + int ret; + struct registration *reg, *holder, *sibling, *n; + + 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->it_nexus); + if (!reg) + return SAM_STAT_RESERVATION_CONFLICT; + + if (reg->key != res_key) + return SAM_STAT_RESERVATION_CONFLICT; + + holder = cmd->dev->pr_holder; + if (holder) { + holder->pr_scope = 0; + holder->pr_type = 0; + cmd->dev->pr_holder = NULL; + } + + list_for_each_entry_safe(sibling, n, &cmd->dev->registration_list, + registration_siblings) { + /* we don't send myself */ + if (sibling != reg) + ua_sense_add_other_it_nexus(sibling->nexus_id, + cmd->dev, + ASC_RESERVATIONS_PREEMPTED); + list_del(&sibling->registration_siblings); + free(sibling); + } + + 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_preempt(int host_no, struct scsi_cmd *cmd) +{ + uint16_t asc = ASC_INVALID_FIELD_IN_CDB; + uint8_t key = ILLEGAL_REQUEST; + int ret, abort; + uint64_t res_key, sa_res_key; + uint8_t pr_scope, pr_type; + uint8_t *buf; + struct registration *reg, *sibling, *n; + + ret = check_pr_out_basic_parameter(cmd); + if (ret) + goto sense; + + abort = ((cmd->scb[1] & 0x1f) == PR_OUT_PREEMPT_AND_ABORT); + + 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->it_nexus); + if (!reg) + return SAM_STAT_RESERVATION_CONFLICT; + + if (reg->key != res_key) + return SAM_STAT_RESERVATION_CONFLICT; + +remove_registration: + if (!cmd->dev->pr_holder) { + list_for_each_entry_safe(sibling, n, &cmd->dev->registration_list, + registration_siblings) { + + if (sibling->key == sa_res_key) { + __unregister(cmd->dev, sibling); + break; + } + } + + return SAM_STAT_GOOD; + } + + if (cmd->dev->pr_holder->pr_type == PR_TYPE_WRITE_EXCLUSIVE_ALLREG || + cmd->dev->pr_holder->pr_type == PR_TYPE_EXCLUSIVE_ACCESS_ALLREG) { + + if (sa_res_key) + goto remove_registration; + + list_for_each_entry_safe(sibling, n, &cmd->dev->registration_list, + registration_siblings) { + + if (sibling == reg) + continue; + + __unregister(cmd->dev, sibling); + } + + cmd->dev->pr_holder = reg; + reg->pr_type = pr_type; + reg->pr_scope = pr_scope; + + return SAM_STAT_GOOD; + } + + if (cmd->dev->pr_holder->key == sa_res_key) { + cmd->dev->pr_holder = reg; + reg->pr_type = pr_type; + reg->pr_scope = pr_scope; + + goto remove_registration; + } + + if (sa_res_key) + goto remove_registration; + + else + goto sense; + + 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_register_and_move(int host_no, struct scsi_cmd *cmd) +{ + uint16_t asc = ASC_INVALID_FIELD_IN_CDB; + uint8_t key = ILLEGAL_REQUEST; + char *buf; + uint8_t pr_scope, pr_type; + uint8_t unreg; + uint64_t res_key, sa_res_key; + uint32_t addlen, idlen; + struct registration *reg, *dst; + uint16_t len = 24; + int (*id)(int, uint64_t, char *, int); + char tpid[300]; /* large enough? */ + + pr_scope = (cmd->scb[2] & 0xf0) >> 4; + pr_type = cmd->scb[2] & 0x0f; + + if (get_unaligned_be16(cmd->scb + 7) < len) + goto sense; + + if (scsi_get_out_length(cmd) < len) + goto sense; + + buf = scsi_get_out_buffer(cmd); + + if (buf[17] & 0x01) + goto sense; + + unreg = buf[17] & 0x02; + + res_key = get_unaligned_be64(buf); + sa_res_key = get_unaligned_be64(buf + 8); + + addlen = get_unaligned_be32(buf + 24); + + reg = lookup_registration_by_nexus(cmd->dev, cmd->it_nexus); + if (!reg) { + if (cmd->dev->pr_holder) + return SAM_STAT_RESERVATION_CONFLICT; + else + goto sense; + } + + if (!is_pr_holder(cmd->dev, reg)) + return SAM_STAT_GOOD; + + if (reg->key != res_key) + return SAM_STAT_RESERVATION_CONFLICT; + + if (!sa_res_key) + return SAM_STAT_RESERVATION_CONFLICT; + + list_for_each_entry(dst, &cmd->dev->registration_list, + registration_siblings) + if (dst->key == sa_res_key) + goto found; + + /* we can't find the destination */ + goto sense; +found: + id = tgt_drivers[cmd->c_target->lid]->transportid; + if (id) { + memset(tpid, 0, sizeof(tpid)); + idlen = id(cmd->dev->tgt->tid, dst->nexus_id, tpid, sizeof(tpid)); + if (addlen) { + if (strncmp(tpid, buf + 28, strlen(tpid))) + goto sense; + } + } + + cmd->dev->pr_holder = dst; + + if (unreg) + __unregister(cmd->dev, 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; +} + +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}, + {PR_OUT_CLEAR, spc_pr_clear}, + {PR_OUT_PREEMPT, spc_pr_preempt}, +/* {PR_OUT_PREEMPT_AND_ABORT, spc_pr_preempt}, */ + {PR_OUT_REGISTER_AND_IGNORE_EXISTING_KEY, spc_pr_register}, + {PR_OUT_REGISTER_AND_MOVE, spc_pr_register_and_move}, + {0, NULL}, +}; + +int spc_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->it_nexus); + + if (reg && is_pr_holder(cmd->dev, reg)) + 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 spc_request_sense(int host_no, struct scsi_cmd *cmd) { uint8_t *data; @@ -1065,4 +1739,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..430a882 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,5 @@ 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); +extern int spc_access_check(struct scsi_cmd *cmd); #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..a69fe0b 100644 --- a/usr/target.c +++ b/usr/target.c @@ -25,8 +25,10 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> +#include <time.h> #include <unistd.h> #include <sys/socket.h> +#include <sys/time.h> #include "list.h" #include "util.h" @@ -36,6 +38,7 @@ #include "scsi.h" #include "tgtadm.h" #include "parser.h" +#include "spc.h" static LIST_HEAD(device_type_list); @@ -236,6 +239,7 @@ int it_nexus_create(int tid, uint64_t itn_id, int host_no, char *info) struct it_nexus *itn; struct scsi_lu *lu; struct it_nexus_lu_info *itn_lu; + struct timeval tv; dprintf("%d %" PRIu64 " %d\n", tid, itn_id, host_no); /* for reserve/release code */ @@ -257,6 +261,8 @@ int it_nexus_create(int tid, uint64_t itn_id, int host_no, char *info) itn->nexus_target = target; itn->info = info; INIT_LIST_HEAD(&itn->it_nexus_lu_info_list); + gettimeofday(&tv, NULL); + itn->ctime = tv.tv_sec; list_for_each_entry(lu, &target->device_list, device_siblings) { itn_lu = zalloc(sizeof(*itn_lu)); @@ -490,6 +496,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 +576,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 +616,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/target.h b/usr/target.h index 8fe30aa..9283431 100644 --- a/usr/target.h +++ b/usr/target.h @@ -43,6 +43,7 @@ struct target { struct it_nexus { uint64_t itn_id; + long ctime; struct list_head cmd_hash_list[1 << HASH_ORDER]; diff --git a/usr/tgtd.h b/usr/tgtd.h index febaed7..3323a9b 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,16 @@ struct mode_pg { uint8_t mode_data[0]; /* Rest of mode page info */ }; +struct registration { + uint64_t key; + uint64_t nexus_id; + long ctime; + 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 +170,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