diff --git a/kernel/Makefile b/kernel/Makefile index a611aea..8f874f9 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -13,5 +13,5 @@ obj-m += iscsi_trgt.o iscsi_trgt-objs := tio.o iscsi.o nthread.o wthread.o config.o digest.o \ conn.o session.o target.o volume.o iotype.o \ file-io.o null-io.o target_disk.o event.o param.o \ - block-io.o ua.o + block-io.o ua.o persist.o diff --git a/kernel/iscsi.c b/kernel/iscsi.c index b51ee00..ee1d85e 100644 --- a/kernel/iscsi.c +++ b/kernel/iscsi.c @@ -392,7 +392,7 @@ void send_data_rsp(struct iscsi_cmnd *req, void (*func)(struct iscsi_cmnd *)) assert(req->tio); size = min(req->tio->size, cmnd_read_size(req)); - + if (req->status == SAM_STAT_GOOD && size) do_send_data_rsp(req); else { @@ -756,7 +756,10 @@ static int cmnd_recv_pdu(struct iscsi_conn *conn, struct tio *tio, u32 offset, u return 0; } -static void set_offset_and_length(struct iet_volume *lu, u8 *cmd, loff_t *off, u32 *len) +static void set_offset_and_length(const struct iet_volume *lu, + const u8 *cmd, + loff_t *off, + u32 *len) { assert(lu); @@ -784,6 +787,14 @@ static void set_offset_and_length(struct iet_volume *lu, u8 *cmd, loff_t *off, u *len = (u32)cmd[10] << 24 | (u32)cmd[11] << 16 | (u32)cmd[12] << 8 | (u32)cmd[13]; break; + case PERSISTENT_RESERVE_OUT: + { + const struct persistent_reserve_out *pr_out = + (const struct persistent_reserve_out *)cmd; + *off = 0; + *len = be32_to_cpu(pr_out->parameter_list_length); + return; + } default: BUG(); } @@ -792,7 +803,7 @@ static void set_offset_and_length(struct iet_volume *lu, u8 *cmd, loff_t *off, u *len <<= lu->blk_shift; } -static u32 translate_lun(u16 * data) +u32 translate_lun(u16 *data) { u8 *p = (u8 *) data; u32 lun = ~0U; @@ -996,6 +1007,7 @@ static void scsi_cmnd_start(struct iscsi_conn *conn, struct iscsi_cmnd *req) case REQUEST_SENSE: case RESERVE: case RELEASE: + case PERSISTENT_RESERVE_IN: { if (!(req_hdr->flags & ISCSI_CMD_FINAL) || req->pdu.datasize) { /* unexpected unsolicited data */ @@ -1025,6 +1037,7 @@ static void scsi_cmnd_start(struct iscsi_conn *conn, struct iscsi_cmnd *req) tio_set(req->tio, length, offset); break; } + case PERSISTENT_RESERVE_OUT: case WRITE_6: case WRITE_10: case WRITE_16: @@ -1039,7 +1052,9 @@ static void scsi_cmnd_start(struct iscsi_conn *conn, struct iscsi_cmnd *req) req->is_unsolicited_data = !(req_hdr->flags & ISCSI_CMD_FINAL); req->target_task_tag = get_next_ttt(conn->session); - if (LUReadonly(req->lun)) { + /* the readonly check needs to go */ + if (req_hdr->scb[0] != PERSISTENT_RESERVE_OUT && + LUReadonly(req->lun)) { create_sense_rsp(req, DATA_PROTECT, 0x27, 0x0); cmnd_skip_data(req); break; @@ -1180,7 +1195,7 @@ static void data_out_end(struct iscsi_conn *conn, struct iscsi_cmnd *cmnd) return; } -static void __cmnd_abort(struct iscsi_cmnd *cmnd) +void __cmnd_abort(struct iscsi_cmnd *cmnd) { if (cmnd_rxstart(cmnd)) set_cmnd_tmfabort(cmnd); diff --git a/kernel/iscsi.h b/kernel/iscsi.h index 896d750..e6154a4 100644 --- a/kernel/iscsi.h +++ b/kernel/iscsi.h @@ -20,6 +20,7 @@ #include "iscsi_hdr.h" #include "iet_u.h" #include "compat.h" +#include "persist.h" #define IET_SENSE_BUF_SIZE 18 @@ -65,6 +66,13 @@ struct tio { atomic_t count; }; +struct tio_iterator { + struct tio *tio; + u32 size; + u32 pg_idx; + u32 pg_off; +}; + struct network_thread_info { struct task_struct *task; unsigned long flags; @@ -157,7 +165,7 @@ struct iet_volume { u32 blk_shift; u64 blk_cnt; - u64 reserve_sid; + struct reservation reservation; spinlock_t reserve_lock; unsigned long flags; @@ -212,6 +220,7 @@ struct iscsi_session { struct list_head ua_hash[UA_HASH_LEN]; u32 next_ttt; + }; enum connection_state_bit { @@ -335,6 +344,8 @@ extern void send_scsi_rsp(struct iscsi_cmnd *, void (*)(struct iscsi_cmnd *)); extern void iscsi_cmnd_set_sense(struct iscsi_cmnd *, u8 sense_key, u8 asc, u8 ascq); extern void send_nop_in(struct iscsi_conn *); +extern u32 translate_lun(u16 *data); +extern void __cmnd_abort(struct iscsi_cmnd *cmnd); /* conn.c */ extern struct iscsi_conn *conn_lookup(struct iscsi_session *, u16); @@ -380,6 +391,7 @@ extern struct file_operations session_seq_fops; extern struct iscsi_session *session_lookup(struct iscsi_target *, u64); extern int session_add(struct iscsi_target *, struct session_info *); extern int session_del(struct iscsi_target *, u64); +extern void session_abort_tasks(struct iscsi_session *, u32 lun); /* volume.c */ extern struct file_operations volume_seq_fops; @@ -391,7 +403,7 @@ extern struct iet_volume *volume_get(struct iscsi_target *, u32); extern void volume_put(struct iet_volume *); extern int volume_reserve(struct iet_volume *volume, u64 sid); extern int volume_release(struct iet_volume *volume, u64 sid, int force); -extern int is_volume_reserved(struct iet_volume *volume, u64 sid); +extern int is_volume_reserved(struct iet_volume *volume, u64 sid, u8 *scb); /* tio.c */ extern int tio_init(void); @@ -404,6 +416,13 @@ extern int tio_read(struct iet_volume *, struct tio *); extern int tio_write(struct iet_volume *, struct tio *); extern int tio_sync(struct iet_volume *, struct tio *); +extern void tio_init_iterator(struct tio *tio, + struct tio_iterator *iter); + +extern size_t tio_add_data(struct tio_iterator *iter, + const u8 *data, + size_t len); + /* iotype.c */ extern struct iotype *get_iotype(const char *name); extern void put_iotype(struct iotype *iot); @@ -468,6 +487,9 @@ static inline void iscsi_cmnd_set_length(struct iscsi_pdu *pdu) #define cmnd_scsicode(cmnd) cmnd_hdr(cmnd)->scb[0] #define cmnd_immediate(cmnd) ((cmnd)->pdu.bhs.opcode & ISCSI_OP_IMMEDIATE) +#define PAD_TO_4_BYTES(n) \ + (((n) + 3) & -4) + /* default and maximum scsi level block sizes */ #define IET_DEF_BLOCK_SIZE 512 #define IET_MAX_BLOCK_SIZE 4096 diff --git a/kernel/iscsi_dbg.h b/kernel/iscsi_dbg.h index d8d5966..69581d2 100644 --- a/kernel/iscsi_dbg.h +++ b/kernel/iscsi_dbg.h @@ -11,6 +11,7 @@ #define D_TASK_MGT (1UL << 7) #define D_IOMODE (1UL << 8) #define D_UAC (1UL << 9) +#define D_PR (1UL << 10) #define D_DATA (D_READ | D_WRITE) @@ -18,12 +19,15 @@ extern unsigned long debug_enable_flags; #define PFX "iscsi_trgt: " -#define dprintk(debug, fmt, args...) do { \ - if ((debug) & debug_enable_flags) { \ - printk(KERN_DEBUG PFX "%s(%d) " fmt, __FUNCTION__,\ - __LINE__, args);\ - } \ -} while (0) +#define dprintk(debug, fmt, args...) \ + do { \ + if ((debug) & debug_enable_flags) { \ + printk(KERN_DEBUG PFX "%s(%d) " fmt, \ + __FUNCTION__, \ + __LINE__, \ + ##args); \ + } \ + } while (0) #define dprintk_ua(ua, sess, lun) \ dprintk(D_UAC, "sess %llu, lun %u: %p %x %x\n", \ @@ -31,21 +35,34 @@ extern unsigned long debug_enable_flags; (ua) ? (ua)->asc : 0, \ (ua) ? (ua)->ascq : 0) -#define eprintk(fmt, args...) do { \ - printk(KERN_ERR PFX "%s(%d) " fmt, __FUNCTION__, \ - __LINE__, args);\ -} while (0) +#define dprintk_pr(cmd, fmt, args...) \ + dprintk(D_PR, "%#Lx:%hu, lun %u, cmnd %p: " fmt, \ + cmnd->conn->session->sid, \ + cmnd->conn->cid, \ + cmnd->lun->lun, \ + cmnd, \ + ##args) + +#define eprintk(fmt, args...) \ + do { \ + printk(KERN_ERR PFX "%s(%d) " fmt, \ + __FUNCTION__, \ + __LINE__, \ + ##args); \ + } while (0) #define iprintk(X...) printk(KERN_INFO PFX X) -#define assert(p) do { \ - if (!(p)) { \ - printk(KERN_CRIT PFX "BUG at %s:%d assert(%s)\n",\ - __FILE__, __LINE__, #p); \ - dump_stack(); \ - BUG(); \ - } \ -} while (0) +/* this has to go away - use BUG() and friends instead */ +#define assert(p) \ + do { \ + if (!(p)) { \ + printk(KERN_CRIT PFX "BUG at %s:%d assert(%s)\n", \ + __FILE__, __LINE__, #p); \ + dump_stack(); \ + BUG(); \ + } \ + } while (0) #ifdef D_IOV static inline void iscsi_dump_iov(struct msghdr *msg) diff --git a/kernel/persist.c b/kernel/persist.c new file mode 100644 index 0000000..a8d73da --- /dev/null +++ b/kernel/persist.c @@ -0,0 +1,732 @@ +/* + * Copyright (C) 2011 Shivaram U, shivaram.u@quadstor.com + * + * Released under the terms of the GNU GPL v2.0. + */ + +#include +#include "iscsi.h" +#include "iscsi_dbg.h" +#include "persist.h" + +bool +pr_is_reserved_by_session(const struct reservation *res, + const struct iscsi_session *sess) +{ + return (pr_is_reserved(res) && res->sid == sess->sid); +} + +bool +pr_initiator_has_registered(const struct reservation *res, + u64 sid) +{ + struct registration *reg; + + list_for_each_entry(reg, &res->registration_list, r_list) { + if (reg->sid == sid) + return true; + } + + return false; +} + +static const struct pr_in_report_capabilities_data pr_capabilities = { + .length = cpu_to_be16(8), + .crh_sip_atp_ptpl_c = 0, + .tmv_ptpl_a = PR_IN_REPORT_CAP_TMV, + .type_mask = cpu_to_be16(PR_TYPE_WR_EX| + PR_TYPE_EX_AC| + PR_TYPE_WR_EX_RO| + PR_TYPE_EX_AC_RO), +}; + +static void +pr_in_report_capabilities(struct iscsi_cmnd *cmnd, + u16 allocation_length) +{ + struct tio *tio = tio_alloc(1); + u8 *data = page_address(tio->pvec[0]); + const u32 min_len = min_t(u16, allocation_length, sizeof(pr_capabilities)); + + BUG_ON(!data); + cmnd->tio = tio; + + memcpy(data, &pr_capabilities, min_len); + tio_set(tio, min_len, 0); + + dprintk_pr(cmnd, "ret len %u\n", min_len); +} + +static void +pr_in_read_reservation(struct iscsi_cmnd *cmnd, + u16 allocation_length) +{ + struct iet_volume *volume = cmnd->lun; + const struct reservation *reservation = &volume->reservation; + struct tio *tio = tio_alloc(1); + struct pr_in_read_reservation_data *pin_data = + (struct pr_in_read_reservation_data *)page_address(tio->pvec[0]); + u32 size; + + BUG_ON(!pin_data); + memset(pin_data, 0x0, sizeof(*pin_data)); + + cmnd->tio = tio; + + spin_lock(&volume->reserve_lock); + pin_data->generation = cpu_to_be32(reservation->generation); + + if (pr_is_reserved(reservation)) { + pin_data->reservation_key = reservation->reservation_key; + pin_data->scope_type = PR_SCOPE_LU | reservation->persistent_type; + /* + * SPC-3, 6.11.3.2 + * "The ADDITIONAL LENGTH field contains a count of the number of + * bytes to follow and shall be set to 16" + */ + pin_data->additional_length = cpu_to_be32(16); + size = sizeof(*pin_data); + + dprintk_pr(cmnd, "key %#Lx, size %u\n", + pin_data->reservation_key, + size); + } else { + size = 8; /* generation + additional length */ + + dprintk_pr(cmnd, + "size %u\n", + size); + } + + + spin_unlock(&volume->reserve_lock); + + + tio_set(tio, min_t(u32, size, allocation_length), 0); +} + +static void +pr_in_read_full_status(struct iscsi_cmnd *cmnd, + u16 allocation_length) +{ + struct iet_volume *volume = cmnd->lun; + const struct reservation *reservation = &volume->reservation; + struct tio *tio = tio_alloc(get_pgcnt(allocation_length, 0)); + struct tio_iterator tio_it; + struct pr_in_read_full_status_data *pfull = + (struct pr_in_read_full_status_data *)page_address(tio->pvec[0]); + struct registration *reg; + u16 left = allocation_length - 8; + u32 addl_data_len = 0; + + if (allocation_length < 8) { + iscsi_cmnd_set_sense(cmnd, + ILLEGAL_REQUEST, + INVALID_FIELD_IN_CDB_ASC, + INVALID_FIELD_IN_CDB_ASCQ); + return; + } + + cmnd->tio = tio; + tio_init_iterator(tio, &tio_it); + tio_it.pg_off += 8; /* generation + additional length */ + + spin_lock(&volume->reserve_lock); + + pfull->generation = cpu_to_be32(reservation->generation); + + list_for_each_entry(reg, &reservation->registration_list, r_list) { + const size_t init_name_len = PAD_TO_4_BYTES(strlen(reg->init_name)); + const struct iscsi_transport_id tid = { + .fmt_code_proto_id = + TRANSPORT_ID_FMT_CODE_ISCSI|TRANSPORT_ID_PROTO_ID_ISCSI, + .additional_length = cpu_to_be16(init_name_len), + }; + const struct pr_in_full_status_descriptor desc = { + .reservation_key = reg->reservation_key, + .all_tg_pt_r_holder = + pr_is_reserved_by_session(reservation, + cmnd->conn->session), + .scope_type = PR_SCOPE_LU|reservation->persistent_type, + /* only rel_tgt_port_id 1 is supported */ + .rel_tgt_port_id = cpu_to_be16(1), + .additional_desc_length = cpu_to_be32(sizeof(tid) + init_name_len), + }; + + left -= tio_add_data(&tio_it, + (const u8 *)&desc, + min_t(u16, left, sizeof(desc))); + + left -= tio_add_data(&tio_it, + (const u8 *)&tid, + min_t(u16, left, sizeof(tid))); + + left -= tio_add_data(&tio_it, + (const u8 *)reg->init_name, + min_t(u16, left, init_name_len)); + + addl_data_len += sizeof(desc) + sizeof(tid) + init_name_len; + + dprintk_pr(cmnd, + "init name %s, sess %#Lx, key %#Lx, rtype %d, scope_type %x, all_tg_pt_r_holder %x, desc.addlen %u, tid.addlen %u, addlen %u, left %u\n", + reg->init_name, + reg->sid, + reg->reservation_key, + reservation->reservation_type, + desc.scope_type, + desc.all_tg_pt_r_holder, + be32_to_cpu(desc.additional_desc_length), + be16_to_cpu(tid.additional_length), + addl_data_len, + left); + } + + spin_unlock(&volume->reserve_lock); + + dprintk_pr(cmnd, + "dlen %u, tlen %u\n", + addl_data_len, + allocation_length - left); + + pfull->additional_length = cpu_to_be32(addl_data_len); + tio_set(tio, allocation_length - left, 0); +} + +static void +pr_in_read_keys(struct iscsi_cmnd *cmnd, + u16 allocation_length) +{ + struct iet_volume *volume = cmnd->lun; + const struct reservation *reservation = &volume->reservation; + struct tio *tio = tio_alloc(get_pgcnt(allocation_length, 0)); + struct tio_iterator tio_it; + struct pr_in_read_keys_data *kdata = + (struct pr_in_read_keys_data *)page_address(tio->pvec[0]); + struct registration *reg; + u16 left = allocation_length - 8; + u32 addl_data_len = 0; + + if (allocation_length < 8) { + iscsi_cmnd_set_sense(cmnd, + ILLEGAL_REQUEST, + INVALID_FIELD_IN_CDB_ASC, + INVALID_FIELD_IN_CDB_ASCQ); + return; + } + + cmnd->tio = tio; + tio_init_iterator(tio, &tio_it); + tio_it.pg_off += 8; /* generation + additional length */ + + spin_lock(&volume->reserve_lock); + + kdata->generation = cpu_to_be32(reservation->generation); + + list_for_each_entry(reg, &reservation->registration_list, r_list) { + + left -= tio_add_data(&tio_it, + (const u8 *)®->reservation_key, + min_t(u16, + left, + sizeof(reg->reservation_key))); + + addl_data_len += sizeof(reg->reservation_key); + + dprintk_pr(cmnd, + "found reg, init name %s, sess %#Lx, key %#Lx, kdata len %u, left %u\n", + reg->init_name, + reg->sid, + reg->reservation_key, + addl_data_len, + left); + } + + spin_unlock(&volume->reserve_lock); + + dprintk_pr(cmnd, + "dlen %u, tlen %u\n", + addl_data_len, + allocation_length - left); + + kdata->additional_length = cpu_to_be32(addl_data_len); + tio_set(tio, allocation_length - left, 0); + + dprintk_pr(cmnd, + "keys[0]: %#Lx\n", kdata->keys[0]); +} + +void +build_persistent_reserve_in_response(struct iscsi_cmnd *cmnd) +{ + const struct persistent_reserve_in *pr_in = + (const struct persistent_reserve_in *)(cmnd_hdr(cmnd)->scb); + const u16 allocation_length = be16_to_cpu(pr_in->allocation_length); + const enum pr_in_service_actions action = + pr_in->service_action & PR_SERVICE_ACTION_MASK; + + dprintk_pr(cmnd, + "svc action %x, alloc len %u\n", + action, + allocation_length); + + switch (action) { + case SERVICE_ACTION_READ_KEYS: + pr_in_read_keys(cmnd, allocation_length); + break; + case SERVICE_ACTION_READ_RESERVATION: + pr_in_read_reservation(cmnd, allocation_length); + break; + case SERVICE_ACTION_REPORT_CAPABILITIES: + pr_in_report_capabilities(cmnd, allocation_length); + break; + case SERVICE_ACTION_READ_FULL_STATUS: + pr_in_read_full_status(cmnd, allocation_length); + break; + default: + iscsi_cmnd_set_sense(cmnd, + ILLEGAL_REQUEST, + INVALID_FIELD_IN_CDB_ASC, + INVALID_FIELD_IN_CDB_ASCQ); + break; + } +} + +static void +pr_out_register(struct iscsi_cmnd *cmnd, bool ignore) +{ + const struct pr_out_param_list *param = + (const struct pr_out_param_list *)page_address(cmnd->tio->pvec[0]); + struct iscsi_session *session = cmnd->conn->session; + struct iet_volume *volume = cmnd->lun; + struct reservation *reservation = &volume->reservation; + struct registration *reg; + struct registration *new_reg = kzalloc(sizeof(*new_reg), GFP_KERNEL); + + dprintk_pr(cmnd, "rkey %#Lx, skey %#Lx, ignore %d\n", + param->reservation_key, + param->service_action_key, + ignore); + + if (!new_reg) { + eprintk("%#Lx:%hu: failed to alloc new registration\n", + cmnd->conn->session->sid, + cmnd->conn->cid); + + iscsi_cmnd_set_sense(cmnd, + /* TODO: verify sense key / asc / ascq */ + ILLEGAL_REQUEST, + INSUFFICIENT_REGISTRATION_RESOURCES_ASC, + INSUFFICIENT_REGISTRATION_RESOURCES_ASCQ); + return; + } + + spin_lock(&volume->reserve_lock); + + list_for_each_entry(reg, &reservation->registration_list, r_list) { + dprintk_pr(cmnd, + "found reg, init name %s, sess %#Lx, key %#Lx\n", + reg->init_name, + reg->sid, + reg->reservation_key); + + if (reg->sid != session->sid) + continue; + + if (!ignore && param->reservation_key != reg->reservation_key) { + iscsi_cmnd_set_sense(cmnd, + ILLEGAL_REQUEST, + INVALID_COMMAND_OPERATION_CODE_ASC, + INVALID_COMMAND_OPERATION_CODE_ASCQ); + goto out; + } + + if ((param->spec_i_pt_all_tg_pt_aptl & PR_OUT_PARAM_SPEC_I_PT)) { + iscsi_cmnd_set_sense(cmnd, + ILLEGAL_REQUEST, + INVALID_FIELD_IN_CDB_ASC, + INVALID_FIELD_IN_CDB_ASCQ); + goto out; + } + + if (!param->service_action_key) { + if (pr_is_reserved_by_session(reservation, session)) { + reservation->reservation_type = RESERVATION_TYPE_NONE; + reservation->persistent_type = 0; + reservation->reservation_key = 0; + ua_establish_for_other_sessions(session, + volume->lun, + RESERVATIONS_RELEASED_ASC, + RESERVATIONS_RELEASED_ASCQ); + } + list_del(®->r_list); + kfree(reg); + } else { + reg->reservation_key = param->service_action_key; + } + reservation->generation++; + goto out; + } + + if (!param->reservation_key && !param->service_action_key) { + reservation->generation++; + goto out; + } + + if (param->reservation_key) { + cmnd->status = SAM_STAT_RESERVATION_CONFLICT; + goto out; + } + + new_reg->sid = session->sid; + new_reg->reservation_key = param->service_action_key; + strncpy(new_reg->init_name, + cmnd->conn->session->initiator, + sizeof(new_reg->init_name)); + + INIT_LIST_HEAD(&new_reg->r_list); + list_add_tail(&new_reg->r_list, &reservation->registration_list); + reservation->generation++; + + spin_unlock(&volume->reserve_lock); + + dprintk_pr(cmnd, + "init_name %s, key %#Lx, generation %u\n", + new_reg->init_name, + new_reg->reservation_key, + reservation->generation); + + return; +out: + kfree(new_reg); + spin_unlock(&volume->reserve_lock); +} + +static bool +persistent_type_valid(int type) +{ + switch (type) { + case PR_TYPE_WRITE_EXCLUSIVE: + case PR_TYPE_EXCLUSIVE_ACCESS: + case PR_TYPE_WRITE_EXCLUSIVE_REGISTRANTS_ONLY: + case PR_TYPE_EXCLUSIVE_ACCESS_REGISTRANTS_ONLY: + return true; + default: + return false; + } +} + +static void +pr_out_reserve(struct iscsi_cmnd *cmnd, enum persistent_reservation_type type) +{ + const struct pr_out_param_list *param = + (const struct pr_out_param_list *)page_address(cmnd->tio->pvec[0]); + bool registered; + struct iscsi_session *session = cmnd->conn->session; + struct iet_volume *volume = cmnd->lun; + struct reservation *reservation = &volume->reservation; + + spin_lock(&volume->reserve_lock); + + registered = pr_initiator_has_registered(reservation, session->sid); + if (!registered) { + cmnd->status = SAM_STAT_RESERVATION_CONFLICT; + goto out; + } + + if (pr_is_reserved(reservation) && reservation->sid != session->sid) { + cmnd->status = SAM_STAT_RESERVATION_CONFLICT; + goto out; + } + + if (pr_is_reserved(reservation) && + reservation->reservation_key != param->reservation_key) { + cmnd->status = SAM_STAT_RESERVATION_CONFLICT; + goto out; + } + + if (pr_is_reserved(reservation) && reservation->persistent_type != type) { + cmnd->status = SAM_STAT_RESERVATION_CONFLICT; + goto out; + } + + if (pr_is_reserved(reservation)) + goto out; + + if (!persistent_type_valid(type)) { + iscsi_cmnd_set_sense(cmnd, + ILLEGAL_REQUEST, + INVALID_FIELD_IN_CDB_ASC, + INVALID_FIELD_IN_CDB_ASCQ); + goto out; + } + + reservation->reservation_type = RESERVATION_TYPE_PERSISTENT; + reservation->persistent_type = type; + reservation->reservation_key = param->reservation_key; + reservation->sid = session->sid; + + dprintk_pr(cmnd, + "key %#Lx, sess %#Lx, generation %u, rtype %d, ptype %d\n", + reservation->reservation_key, + reservation->sid, + reservation->generation, + reservation->reservation_type, + reservation->persistent_type); + +out: + spin_unlock(&volume->reserve_lock); +} + +static void +pr_out_release(struct iscsi_cmnd *cmnd, + enum persistent_reservation_type type) +{ + const struct pr_out_param_list *param = + (const struct pr_out_param_list *)page_address(cmnd->tio->pvec[0]); + bool registered; + struct iscsi_session *session = cmnd->conn->session; + struct iet_volume *volume = cmnd->lun; + struct reservation *reservation = &volume->reservation; + bool send_ua; + + spin_lock(&volume->reserve_lock); + if (!pr_is_reserved(reservation)) + goto out; + + registered = pr_initiator_has_registered(reservation, session->sid); + if (!registered) { + cmnd->status = SAM_STAT_RESERVATION_CONFLICT; + goto out; + } + + if (reservation->sid != session->sid) + goto out; + + if (reservation->reservation_key != param->reservation_key) { + cmnd->status = SAM_STAT_RESERVATION_CONFLICT; + goto out; + } + + switch (reservation->persistent_type) { + case PR_TYPE_WRITE_EXCLUSIVE_REGISTRANTS_ONLY: + case PR_TYPE_EXCLUSIVE_ACCESS_REGISTRANTS_ONLY: + send_ua = true; + break; + default: + send_ua = false; + } + + dprintk_pr(cmnd, + "key %#Lx, sess %#Lx, generation %u, rtype %d, ptype %d, ua %d\n", + reservation->reservation_key, + reservation->sid, + reservation->generation, + reservation->reservation_type, + reservation->persistent_type, + send_ua); + + reservation->reservation_type = RESERVATION_TYPE_NONE; + reservation->persistent_type = PR_TYPE_NONE; + reservation->reservation_key = 0; + + if (send_ua) + ua_establish_for_other_sessions(session, + volume->lun, + RESERVATIONS_RELEASED_ASC, + RESERVATIONS_RELEASED_ASCQ); +out: + spin_unlock(&volume->reserve_lock); +} + +static void +pr_out_clear(struct iscsi_cmnd *cmnd) +{ + bool registered; + struct iscsi_session *tmp_session, *session = cmnd->conn->session; + struct iscsi_target *target = session->target; + struct iet_volume *volume = cmnd->lun; + struct reservation *reservation = &volume->reservation; + struct registration *reg, *tmp_reg; + + spin_lock(&volume->reserve_lock); + registered = pr_initiator_has_registered(reservation, session->sid); + if (!registered) { + cmnd->status = SAM_STAT_RESERVATION_CONFLICT; + goto out; + } + + list_for_each_entry_safe(reg, tmp_reg, &reservation->registration_list, r_list) { + if (reg->sid != session->sid) { + tmp_session = session_lookup(target, reg->sid); + if (tmp_session) + ua_establish_for_session(session, + volume->lun, + RESERVATIONS_PREEMPTED_ASC, + RESERVATIONS_PREEMPTED_ASCQ); + } + list_del(®->r_list); + kfree(reg); + } + + dprintk_pr(cmnd, + "key %#Lx, sess %#Lx, generation %u, rtype %d, ptype %d\n", + reservation->reservation_key, + reservation->sid, + reservation->generation, + reservation->reservation_type, + reservation->persistent_type); + + reservation->reservation_type = RESERVATION_TYPE_NONE; + reservation->persistent_type = PR_TYPE_NONE; + reservation->reservation_key = 0; + reservation->generation++; +out: + spin_unlock(&volume->reserve_lock); +} + +static void +pr_out_preempt(struct iscsi_cmnd *cmnd, + enum persistent_reservation_type pr_type, + bool abort) +{ + const struct pr_out_param_list *param = + (const struct pr_out_param_list *)page_address(cmnd->tio->pvec[0]); + struct registration *reg, *tmp_reg; + bool registered; + struct iscsi_session *session = cmnd->conn->session; + struct iscsi_session *reserv_session; + struct iscsi_target *target = session->target; + struct iet_volume *volume = cmnd->lun; + struct reservation *reservation = &volume->reservation; + bool all = 0; + + if (!param->service_action_key) { + iscsi_cmnd_set_sense(cmnd, + ILLEGAL_REQUEST, + INVALID_FIELD_IN_PARAMETER_LIST_ASC, + INVALID_FIELD_IN_PARAMETER_LIST_ASCQ); + return; + } + + spin_lock(&volume->reserve_lock); + registered = pr_initiator_has_registered(reservation, session->sid); + if (!registered) { + cmnd->status = SAM_STAT_RESERVATION_CONFLICT; + goto out; + } + + if (pr_is_reserved(reservation) && + reservation->reservation_key == param->service_action_key && + reservation->sid != session->sid) { + if (!persistent_type_valid(pr_type)) { + iscsi_cmnd_set_sense(cmnd, + ILLEGAL_REQUEST, + INVALID_FIELD_IN_CDB_ASC, + INVALID_FIELD_IN_CDB_ASCQ); + goto out; + } + + reserv_session = session_lookup(target, reservation->sid); + if (reserv_session) { + if (abort) + session_abort_tasks(reserv_session, volume->lun); + ua_establish_for_session(reserv_session, + volume->lun, + RESERVATIONS_PREEMPTED_ASC, + RESERVATIONS_PREEMPTED_ASCQ); + } + + reservation->reservation_type = RESERVATION_TYPE_PERSISTENT; + reservation->sid = session->sid; + reservation->reservation_key = param->reservation_key; + reservation->persistent_type = pr_type; + all = true; + } + + list_for_each_entry_safe(reg, tmp_reg, &reservation->registration_list, r_list) { + if (reg->sid == session->sid) + continue; + + if (!all && reg->reservation_key != param->service_action_key) + continue; + + reserv_session = session_lookup(target, reg->sid); + if (reserv_session) + ua_establish_for_session(reserv_session, + volume->lun, + REGISTRATIONS_PREEMPTED_ASC, + REGISTRATIONS_PREEMPTED_ASCQ); + + list_del(®->r_list); + kfree(reg); + } + + reservation->generation++; +out: + spin_unlock(&volume->reserve_lock); +} + +void +build_persistent_reserve_out_response(struct iscsi_cmnd *cmnd) +{ + const struct persistent_reserve_out *pr_out = + (const struct persistent_reserve_out *)(cmnd_hdr(cmnd)->scb); + const enum pr_out_service_actions action = + pr_out->service_action & PR_SERVICE_ACTION_MASK; + + dprintk_pr(cmnd, + "svc action %x, scope_type %x, param len %u\n", + action, + pr_out->scope_type, + be32_to_cpu(pr_out->parameter_list_length)); + + if ((pr_out->scope_type & PR_SCOPE_MASK)) { + iscsi_cmnd_set_sense(cmnd, + ILLEGAL_REQUEST, + INVALID_FIELD_IN_CDB_ASC, + INVALID_FIELD_IN_CDB_ASCQ); + return; + } + + if (be32_to_cpu(pr_out->parameter_list_length) != 24) { + iscsi_cmnd_set_sense(cmnd, + ILLEGAL_REQUEST, + PARAMETER_LIST_LENGTH_ERROR_ASC, + PARAMETER_LIST_LENGTH_ERROR_ASCQ); + return; + } + + switch (action) { + case SERVICE_ACTION_REGISTER: + pr_out_register(cmnd, false); + break; + case SERVICE_ACTION_REGISTER_IGNORE: + pr_out_register(cmnd, true); + break; + case SERVICE_ACTION_RESERVE: + pr_out_reserve(cmnd, pr_out->scope_type & PR_TYPE_MASK); + break; + case SERVICE_ACTION_RELEASE: + pr_out_release(cmnd, pr_out->scope_type & PR_TYPE_MASK); + break; + case SERVICE_ACTION_CLEAR: + pr_out_clear(cmnd); + break; + case SERVICE_ACTION_PREEMPT: + pr_out_preempt(cmnd, + pr_out->scope_type & PR_TYPE_MASK, + false); + break; + case SERVICE_ACTION_PREEMPT_ABORT: + pr_out_preempt(cmnd, + pr_out->scope_type & PR_TYPE_MASK, + true); + break; + default: + iscsi_cmnd_set_sense(cmnd, + ILLEGAL_REQUEST, + INVALID_FIELD_IN_CDB_ASC, + INVALID_FIELD_IN_CDB_ASCQ); + break; + } +} diff --git a/kernel/persist.h b/kernel/persist.h new file mode 100644 index 0000000..23a319b --- /dev/null +++ b/kernel/persist.h @@ -0,0 +1,245 @@ +/* + * Copyright (C) 2011 Shivaram U, shivaram.u@quadstor.com + * + * Released under the terms of the GNU GPL v2.0. + */ +#ifndef IET_PERSIST_H_ +#define IET_PERSIST_H_ + +struct registration { + u64 sid; + __be64 reservation_key; + char init_name[ISCSI_NAME_LEN]; + struct list_head r_list; +}; + +#define PARAMETER_LIST_LENGTH_ERROR_ASC 0x1A +#define PARAMETER_LIST_LENGTH_ERROR_ASCQ 0x00 + +#define INVALID_COMMAND_OPERATION_CODE_ASC 0x20 +#define INVALID_COMMAND_OPERATION_CODE_ASCQ 0x00 + +#define INVALID_FIELD_IN_CDB_ASC 0x24 +#define INVALID_FIELD_IN_CDB_ASCQ 0x00 + +#define INVALID_FIELD_IN_PARAMETER_LIST_ASC 0x26 +#define INVALID_FIELD_IN_PARAMETER_LIST_ASCQ 0x00 + +#define INVALID_RELEASE_OF_PERSISTENT_RESERVATION_ASC 0x26 +#define INVALID_RELEASE_OF_PERSISTENT_RESERVATION_ASCQ 0x04 + +#define RESERVATIONS_PREEMPTED_ASC 0x2A +#define RESERVATIONS_PREEMPTED_ASCQ 0x03 + +#define RESERVATIONS_RELEASED_ASC 0x2A +#define RESERVATIONS_RELEASED_ASCQ 0x04 + +#define REGISTRATIONS_PREEMPTED_ASC 0x2A +#define REGISTRATIONS_PREEMPTED_ASCQ 0x05 + +#define INSUFFICIENT_RESERVATION_RESOURCES_ASC 0x55 +#define INSUFFICIENT_RESERVATION_RESOURCES_ASCQ 0x02 + +#define INSUFFICIENT_REGISTRATION_RESOURCES_ASC 0x55 +#define INSUFFICIENT_REGISTRATION_RESOURCES_ASCQ 0x04 + +enum pr_in_service_actions { + SERVICE_ACTION_READ_KEYS = 0x0, + SERVICE_ACTION_READ_RESERVATION = 0x1, + SERVICE_ACTION_REPORT_CAPABILITIES = 0x2, + SERVICE_ACTION_READ_FULL_STATUS = 0x3 +}; + +struct persistent_reserve_in { + u8 opcode; /* PERSISTENT_RESERVE_IN == 0x5e */ + u8 service_action; + u8 rsvd[5]; + __be16 allocation_length; + u8 control; +} __packed; + + +enum pr_type_mask { + PR_TYPE_WR_EX_AR = 0x8000, /* Write Excl., All Registrants */ + PR_TYPE_EX_AC_RO = 0x4000, /* Excl. Access, Registrants Only */ + PR_TYPE_WR_EX_RO = 0x2000, /* Write Excl., Registrants Only */ + PR_TYPE_EX_AC = 0x800, /* Excl. Access */ + PR_TYPE_WR_EX = 0x200, /* Write Excl. */ + PR_TYPE_EX_AC_AR = 0x1, /* Excl. Access, All Registrants */ +}; + +enum { + PR_IN_REPORT_CAP_PTPL_C = 1, /* Persist Through Power Loss Capable */ + PR_IN_REPORT_CAP_ATP_C = 1 << 2, /* All Target Ports Capable */ + PR_IN_REPORT_CAP_SIP_C = 1 << 3, /* Specify Initiator Ports Capable */ + PR_IN_REPORT_CAP_CRH = 1 << 4 /* Compatible Reservation Handling */ +}; + + +enum { + PR_IN_REPORT_CAP_PTPL_A = 1, /* Persist Through Power Loss Activated */ + PR_IN_REPORT_CAP_TMV = 1 << 7, /* Type Mask Valid */ +}; + +struct pr_in_report_capabilities_data { + __be16 length; + + u8 crh_sip_atp_ptpl_c; + u8 tmv_ptpl_a; /* SPC-4 has allow_commands here - don't care for now */ + + __be16 type_mask; + u8 rsvd4[2]; +} __packed; + +enum { + PR_SERVICE_ACTION_MASK = 0x1f, + PR_TYPE_MASK = 0xf, + PR_SCOPE_MASK = 0xf << 4, +}; + +struct pr_in_read_reservation_data { + __be32 generation; + __be32 additional_length; + __be64 reservation_key; + u8 obsolete1[4]; + u8 rsvd; + u8 scope_type; + u8 obsolete2[2]; +} __packed; + +enum { + TRANSPORT_ID_FMT_CODE_MASK = 0xc0, + TRANSPORT_ID_FMT_CODE_ISCSI = 0x0, + TRANSPORT_ID_PROTO_ID_MASK = 0xf, + TRANSPORT_ID_PROTO_ID_ISCSI = 0x5, +}; + +struct iscsi_transport_id { + u8 fmt_code_proto_id; + u8 rsvd; + __be16 additional_length; + u8 iscsi_name[0]; +} __packed; + +enum { + PR_OUT_STATUS_DESC_R_HOLDER = 1, + PR_OUT_STATUS_DESC_ALL_TG_PT = 1 << 1, +}; + +/* this is iscsi specific */ +struct pr_in_full_status_descriptor { + __be64 reservation_key; + u8 rsvd1[4]; + + u8 all_tg_pt_r_holder; + u8 scope_type; + + u8 rsvd2[4]; + __be16 rel_tgt_port_id; + __be32 additional_desc_length; + struct iscsi_transport_id iscsi_transport_id[0]; +} __packed; + +struct pr_in_read_full_status_data { + __be32 generation; + __be32 additional_length; + struct pr_in_full_status_descriptor descriptors[0]; +} __packed; + +struct pr_in_read_keys_data { + __be32 generation; + __be32 additional_length; + __be64 keys[0]; +} __packed; + +enum pr_out_service_actions { + SERVICE_ACTION_REGISTER = 0x0, + SERVICE_ACTION_RESERVE = 0x1, + SERVICE_ACTION_RELEASE = 0x2, + SERVICE_ACTION_CLEAR = 0x3, + SERVICE_ACTION_PREEMPT = 0x4, + SERVICE_ACTION_PREEMPT_ABORT = 0x5, + SERVICE_ACTION_REGISTER_IGNORE = 0x6, + SERVICE_ACTION_REGISTER_MOVE = 0x7 +}; + +enum persistent_reservation_scope { + PR_SCOPE_LU = 0x0, +}; + +enum persistent_reservation_type { + PR_TYPE_NONE = 0x0, /* "abuse" obsolete value */ + PR_TYPE_WRITE_EXCLUSIVE = 0x1, + PR_TYPE_EXCLUSIVE_ACCESS = 0x3, + PR_TYPE_WRITE_EXCLUSIVE_REGISTRANTS_ONLY = 0x5, + PR_TYPE_EXCLUSIVE_ACCESS_REGISTRANTS_ONLY = 0x6, + PR_TYPE_WRITE_EXCLUSIVE_ALL_REGISTRANTS = 0x7, + PR_TYPE_EXCLUSIVE_ACCESS_ALL_REGISTRANTS = 0x8 +}; + +struct persistent_reserve_out { + u8 opcode; /* PERSISTENT_RESERVE_OUT == 0x5f */ + u8 service_action; + u8 scope_type; + u8 rsvd[2]; + __be32 parameter_list_length; + u8 control; +} __packed; + +enum { + PR_OUT_PARAM_APTPL = 1, + PR_OUT_PARAM_ALL_TG_PT = 1 << 2, + PR_OUT_PARAM_SPEC_I_PT = 1 << 3, +}; + +struct pr_out_param_list { + __be64 reservation_key; + __be64 service_action_key; + u8 obsolete1[4]; + u8 spec_i_pt_all_tg_pt_aptl; + u8 rsvd; + u8 obsolete2[2]; + u8 additional_parameter_data[0]; +} __packed; + +enum reservation_type { + RESERVATION_TYPE_NONE, + RESERVATION_TYPE_RESERVE, + RESERVATION_TYPE_PERSISTENT +}; + +struct reservation { + /* RESERVATION_TYPE_NONE indicates "not reserved" */ + enum reservation_type reservation_type; + enum persistent_reservation_type persistent_type; + u32 generation; + u64 sid; + __be64 reservation_key; + struct list_head registration_list; +}; + +static inline bool +pr_is_reserved(const struct reservation* res) +{ + return res->reservation_type != RESERVATION_TYPE_NONE; +} + +struct iscsi_session; + +bool +pr_is_reserved_by_session(const struct reservation *res, + const struct iscsi_session *sess); + +bool +pr_initiator_has_registered(const struct reservation *res, + u64 sid); + +struct iscsi_cmnd; + +void +build_persistent_reserve_out_response(struct iscsi_cmnd *cmnd); + +void +build_persistent_reserve_in_response(struct iscsi_cmnd *cmnd); + +#endif diff --git a/kernel/session.c b/kernel/session.c index a566d8b..9c9906f 100644 --- a/kernel/session.c +++ b/kernel/session.c @@ -121,6 +121,22 @@ int session_add(struct iscsi_target *target, struct session_info *info) return 0; } +void +session_abort_tasks(struct iscsi_session *session, u32 lun) +{ + struct iscsi_conn *conn; + struct iscsi_cmnd *cmnd, *tmp; + + list_for_each_entry(conn, &session->conn_list, list) { + list_for_each_entry_safe(cmnd, tmp, &conn->pdu_list, conn_list) { + if (translate_lun(cmnd_hdr(cmnd)->lun) != lun) + continue; + + __cmnd_abort(cmnd); + } + } +} + int session_del(struct iscsi_target *target, u64 sid) { struct iscsi_session *session; diff --git a/kernel/target_disk.c b/kernel/target_disk.c index 86e9569..46b4161 100644 --- a/kernel/target_disk.c +++ b/kernel/target_disk.c @@ -498,26 +498,11 @@ static int disk_check_reservation(struct iscsi_cmnd *cmnd) struct iscsi_scsi_cmd_hdr *req = cmnd_hdr(cmnd); int ret = is_volume_reserved(cmnd->lun, - cmnd->conn->session->sid); + cmnd->conn->session->sid, req->scb); if (ret == -EBUSY) { - switch (req->scb[0]) { - case INQUIRY: - case RELEASE: - case REPORT_LUNS: - case REQUEST_SENSE: - case READ_CAPACITY: - /* allowed commands when reserved */ - break; - case SERVICE_ACTION_IN: - if ((cmnd_hdr(cmnd)->scb[1] & 0x1F) == 0x10) - break; - /* fall through */ - default: - /* return reservation conflict for all others */ - send_scsi_rsp(cmnd, - build_reservation_conflict_response); - return 1; - } + send_scsi_rsp(cmnd, + build_reservation_conflict_response); + return 1; } return 0; @@ -580,6 +565,12 @@ static int disk_execute_cmnd(struct iscsi_cmnd *cmnd) case VERIFY_16: send_scsi_rsp(cmnd, build_generic_response); break; + case PERSISTENT_RESERVE_IN: + send_data_rsp(cmnd, build_persistent_reserve_in_response); + break; + case PERSISTENT_RESERVE_OUT: + send_scsi_rsp(cmnd, build_persistent_reserve_out_response); + break; default: eprintk("%s\n", "we should not come here!"); break; diff --git a/kernel/tio.c b/kernel/tio.c index 3c390d3..960a5ae 100644 --- a/kernel/tio.c +++ b/kernel/tio.c @@ -57,6 +57,43 @@ struct tio *tio_alloc(int count) return tio; } +void +tio_init_iterator(struct tio *tio, + struct tio_iterator *iter) +{ + iter->tio = tio; + iter->size = 0; + iter->pg_idx = 0; + iter->pg_off = 0; +} + +size_t +tio_add_data(struct tio_iterator *iter, + const u8 *data, + size_t len) +{ + struct tio *tio = iter->tio; + const size_t to_copy = min(tio->pg_cnt * PAGE_SIZE - iter->size, len); + size_t residual = to_copy; + + BUG_ON(tio->size < iter->size); + + do { + u8 *ptr = page_address(iter->tio->pvec[iter->pg_idx]) + iter->pg_off; + size_t chunk = min(PAGE_SIZE - iter->pg_off, residual); + memcpy(ptr, data, chunk); + residual -= chunk; + if (residual || + iter->pg_off + chunk == PAGE_SIZE) { + ++iter->pg_idx; + iter->pg_off = 0; + } else + iter->pg_off += chunk; + } while (residual); + + return to_copy; +} + static void tio_free(struct tio *tio) { int i; diff --git a/kernel/ua.c b/kernel/ua.c index 97f1ba0..c1f0dfa 100644 --- a/kernel/ua.c +++ b/kernel/ua.c @@ -126,8 +126,8 @@ void ua_establish_for_session(struct iscsi_session *sess, u32 lun, /* One UA per occurrence of an event */ list_for_each_entry(e, l, entry) { if (e->session == sess && e->lun == lun && - e->asc == asc && e->ascq == ascq && - e->session->exp_cmd_sn == sess->exp_cmd_sn) { + e->asc == asc && e->ascq == ascq && + e->session->exp_cmd_sn == sess->exp_cmd_sn) { spin_unlock(&sess->ua_hash_lock); ua_free(ua); return; diff --git a/kernel/volume.c b/kernel/volume.c index f2820c9..6660313 100644 --- a/kernel/volume.c +++ b/kernel/volume.c @@ -7,6 +7,7 @@ #include #include #include +#include #include "iscsi.h" #include "iscsi_dbg.h" @@ -210,6 +211,20 @@ static int parse_volume_params(struct iet_volume *volume, char *params) return err; } +static void +volume_reservation_exit(struct iet_volume *volume) +{ + struct list_head *l, *n; + struct registration *tmp; + struct reservation *reservation = &volume->reservation; + + list_for_each_safe(l, n, &reservation->registration_list) { + tmp = list_entry(l, struct registration, r_list); + list_del(l); + kfree(tmp); + } +} + int volume_add(struct iscsi_target *target, struct volume_info *info) { int ret; @@ -229,6 +244,7 @@ int volume_add(struct iscsi_target *target, struct volume_info *info) volume->target = target; volume->lun = info->lun; + INIT_LIST_HEAD(&volume->reservation.registration_list); args = kzalloc(info->args_len + 1, GFP_KERNEL); if (!args) { @@ -292,6 +308,7 @@ void iscsi_volume_destroy(struct iet_volume *volume) volume->iotype->detach(volume); put_iotype(volume->iotype); list_del(&volume->list); + volume_reservation_exit(volume); kfree(volume); } @@ -333,33 +350,128 @@ void volume_put(struct iet_volume *volume) int volume_reserve(struct iet_volume *volume, u64 sid) { int err = 0; + struct reservation *reservation; if (!volume) return -ENOENT; + reservation = &volume->reservation; spin_lock(&volume->reserve_lock); - if (volume->reserve_sid && volume->reserve_sid != sid) + if (pr_is_reserved(reservation) && reservation->sid != sid) err = -EBUSY; - else - volume->reserve_sid = sid; + else { + reservation->reservation_type = RESERVATION_TYPE_RESERVE; + reservation->sid = sid; + } spin_unlock(&volume->reserve_lock); return err; } -int is_volume_reserved(struct iet_volume *volume, u64 sid) +int is_volume_reserved(struct iet_volume *volume, u64 sid, u8 *scb) { int err = 0; + struct reservation *reservation; + bool registered = false; + bool write_excl = false; + bool excl_access = false; + bool write_excl_ro = false; + bool excl_access_ro = false; if (!volume) return -ENOENT; + reservation = &volume->reservation; + spin_lock(&volume->reserve_lock); - if (!volume->reserve_sid || volume->reserve_sid == sid) - err = 0; - else - err = -EBUSY; + if (!pr_is_reserved(reservation) || reservation->sid == sid) { + spin_unlock(&volume->reserve_lock); + return 0; + } + + if (reservation->reservation_type == RESERVATION_TYPE_RESERVE) { + switch (scb[0]) { + case INQUIRY: + case RELEASE: + case REPORT_LUNS: + case REQUEST_SENSE: + case READ_CAPACITY: + /* allowed commands when reserved */ + break; + case SERVICE_ACTION_IN: + if ((scb[1] & 0x1F) == 0x10) + break; + /* fall through */ + default: + err = -EBUSY; + break; + } + spin_unlock(&volume->reserve_lock); + return err; + } + + registered = pr_initiator_has_registered(reservation, sid); + + switch (reservation->persistent_type) { + case PR_TYPE_WRITE_EXCLUSIVE: + write_excl = true; + break; + case PR_TYPE_EXCLUSIVE_ACCESS: + excl_access = true; + break; + case PR_TYPE_WRITE_EXCLUSIVE_REGISTRANTS_ONLY: + write_excl_ro = true; + break; + case PR_TYPE_EXCLUSIVE_ACCESS_REGISTRANTS_ONLY: + excl_access_ro = true; + break; + default: + break; + } + switch (scb[0]) { + case INQUIRY: + case TEST_UNIT_READY: + case PERSISTENT_RESERVE_IN: + case PERSISTENT_RESERVE_OUT: + case REPORT_LUNS: + case REQUEST_SENSE: + case READ_CAPACITY: + case START_STOP: + break; + case MODE_SENSE: + case WRITE_6: + case WRITE_10: + case WRITE_12: + case WRITE_16: + case WRITE_VERIFY: + case SYNCHRONIZE_CACHE: + if (write_excl || excl_access) + err = -EBUSY; + if ((write_excl_ro || excl_access_ro) && !registered) + err = -EBUSY; + break; + case READ_6: + case READ_10: + case READ_12: + case READ_16: + case VERIFY: + case VERIFY_16: + if (excl_access) + err = -EBUSY; + if (excl_access_ro && !registered) + err = -EBUSY; + break; + case SERVICE_ACTION_IN: + if ((scb[1] & 0x1F) == 0x10) + break; + /* fall through */ + case RELEASE: + case RESERVE: + default: + err = -EBUSY; + break; + } spin_unlock(&volume->reserve_lock); return err; } @@ -367,16 +479,21 @@ int is_volume_reserved(struct iet_volume *volume, u64 sid) int volume_release(struct iet_volume *volume, u64 sid, int force) { int err = 0; + struct reservation *reservation; if (!volume) return -ENOENT; + reservation = &volume->reservation; spin_lock(&volume->reserve_lock); - if (force || volume->reserve_sid == sid) - volume->reserve_sid = 0; - else + if (reservation->reservation_type == RESERVATION_TYPE_RESERVE && + (force || reservation->sid == sid)) { + reservation->reservation_type = RESERVATION_TYPE_NONE; + reservation->sid = 0; + } else { err = -EBUSY; + } spin_unlock(&volume->reserve_lock); return err;