1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21/*
22 * Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
23 * Copyright 2013 Nexenta Systems, Inc. All rights reserved.
24 */
25
26#include <sys/cpuvar.h>
27#include <sys/types.h>
28#include <sys/conf.h>
29#include <sys/file.h>
30#include <sys/ddi.h>
31#include <sys/sunddi.h>
32#include <sys/modctl.h>
33#include <sys/sysmacros.h>
34
35#include <sys/socket.h>
36#include <sys/strsubr.h>
37#include <sys/door.h>
38
39#include <sys/stmf.h>
40#include <sys/stmf_ioctl.h>
41#include <sys/portif.h>
42
43#include "pppt.h"
44
45static void pppt_msg_tgt_register(stmf_ic_msg_t *reg_port);
46
47static void pppt_msg_tgt_deregister(stmf_ic_msg_t *msg);
48
49static void pppt_msg_session_destroy(stmf_ic_msg_t *msg);
50
51static void pppt_msg_scsi_cmd(stmf_ic_msg_t *msg);
52
53static void pppt_msg_data_xfer_done(stmf_ic_msg_t *msg);
54
55static void pppt_msg_handle_status(stmf_ic_msg_t *msg);
56
57void
58pppt_msg_rx(stmf_ic_msg_t *msg)
59{
60	switch (msg->icm_msg_type) {
61	case STMF_ICM_REGISTER_PROXY_PORT:
62		pppt_msg_tgt_register(msg);
63		break;
64	case STMF_ICM_DEREGISTER_PROXY_PORT:
65		pppt_msg_tgt_deregister(msg);
66		break;
67	case STMF_ICM_SESSION_CREATE:
68		pppt_msg_tx_status(msg, STMF_NOT_SUPPORTED);
69		stmf_ic_msg_free(msg);
70		break;
71	case STMF_ICM_SESSION_DESTROY:
72		pppt_msg_session_destroy(msg);
73		break;
74	case STMF_ICM_SCSI_CMD:
75		pppt_msg_scsi_cmd(msg);
76		break;
77	case STMF_ICM_SCSI_DATA_XFER_DONE:
78		pppt_msg_data_xfer_done(msg);
79		break;
80	case STMF_ICM_SCSI_DATA:
81		/* Ignore, all proxy data will be immediate for now */
82		pppt_msg_tx_status(msg, STMF_NOT_SUPPORTED);
83		stmf_ic_msg_free(msg);
84		break;
85	case STMF_ICM_STATUS:
86		pppt_msg_handle_status(msg);
87		break;
88	default:
89		/* Other message types are not allowed */
90		ASSERT(0);
91		break;
92	}
93}
94
95void
96pppt_msg_tx_status(stmf_ic_msg_t *orig_msg, stmf_status_t status)
97{
98	stmf_ic_msg_t	*msg;
99
100	/*
101	 * If TX of status fails it should be treated the same as a loss of
102	 * connection.  We expect the remote node to handle it.
103	 */
104	msg = stmf_ic_status_msg_alloc(status, orig_msg->icm_msg_type,
105	    orig_msg->icm_msgid);
106
107	if (msg != NULL) {
108		(void) stmf_ic_tx_msg(msg);
109	}
110}
111
112static void
113pppt_msg_tgt_register(stmf_ic_msg_t *msg)
114{
115	stmf_ic_reg_port_msg_t	*reg_port;
116	pppt_tgt_t		*result;
117	stmf_status_t		stmf_status;
118
119	reg_port = msg->icm_msg;
120
121	PPPT_GLOBAL_LOCK();
122	if (pppt_global.global_svc_state != PSS_ENABLED) {
123		stmf_status = STMF_FAILURE;
124		PPPT_INC_STAT(es_tgt_reg_svc_disabled);
125		goto pppt_register_tgt_done;
126	}
127
128	/*
129	 * For now we assume that the marshall/unmarshall code is responsible
130	 * for validating the message length and ensuring the resulting
131	 * request structure is self consistent.  Make sure this
132	 * target doesn't already exist.
133	 */
134	if ((result = pppt_tgt_lookup_locked(reg_port->icrp_port_id)) != NULL) {
135		stmf_status = STMF_ALREADY;
136		PPPT_INC_STAT(es_tgt_reg_duplicate);
137		goto pppt_register_tgt_done;
138	}
139
140	result = pppt_tgt_create(reg_port, &stmf_status);
141
142	if (result == NULL) {
143		stmf_status = STMF_TARGET_FAILURE;
144		PPPT_INC_STAT(es_tgt_reg_create_fail);
145		goto pppt_register_tgt_done;
146	}
147
148	avl_add(&pppt_global.global_target_list, result);
149
150	stmf_status = STMF_SUCCESS;
151
152pppt_register_tgt_done:
153	PPPT_GLOBAL_UNLOCK();
154	pppt_msg_tx_status(msg, stmf_status);
155	stmf_ic_msg_free(msg);
156}
157
158static void
159pppt_msg_tgt_deregister(stmf_ic_msg_t *msg)
160{
161	stmf_ic_dereg_port_msg_t	*dereg_port;
162	stmf_status_t			stmf_status;
163	pppt_tgt_t			*tgt;
164
165	PPPT_GLOBAL_LOCK();
166	if (pppt_global.global_svc_state != PSS_ENABLED) {
167		PPPT_GLOBAL_UNLOCK();
168		stmf_status = STMF_FAILURE;
169		PPPT_INC_STAT(es_tgt_dereg_svc_disabled);
170		goto pppt_deregister_tgt_done;
171	}
172
173	dereg_port = msg->icm_msg;
174
175	/* Lookup target */
176	if ((tgt = pppt_tgt_lookup_locked(dereg_port->icdp_port_id)) == NULL) {
177		PPPT_GLOBAL_UNLOCK();
178		stmf_status = STMF_NOT_FOUND;
179		PPPT_INC_STAT(es_tgt_dereg_not_found);
180		goto pppt_deregister_tgt_done;
181	}
182	avl_remove(&pppt_global.global_target_list, tgt);
183	pppt_tgt_async_delete(tgt);
184
185	PPPT_GLOBAL_UNLOCK();
186
187	/* Wait for delete to complete */
188	mutex_enter(&tgt->target_mutex);
189	while ((tgt->target_refcount > 0) ||
190	    (tgt->target_state != TS_DELETING)) {
191		cv_wait(&tgt->target_cv, &tgt->target_mutex);
192	}
193	mutex_exit(&tgt->target_mutex);
194
195	pppt_tgt_destroy(tgt);
196	stmf_status = STMF_SUCCESS;
197
198pppt_deregister_tgt_done:
199	pppt_msg_tx_status(msg, stmf_status);
200	stmf_ic_msg_free(msg);
201}
202
203static void
204pppt_msg_session_destroy(stmf_ic_msg_t *msg)
205{
206	stmf_ic_session_create_destroy_msg_t	*sess_destroy;
207	pppt_tgt_t				*tgt;
208	pppt_sess_t				*ps;
209
210	sess_destroy = msg->icm_msg;
211
212	PPPT_GLOBAL_LOCK();
213
214	/*
215	 * Look for existing session for this ID
216	 */
217	ps = pppt_sess_lookup_locked(sess_destroy->icscd_session_id,
218	    sess_destroy->icscd_tgt_devid, sess_destroy->icscd_rport);
219
220	if (ps == NULL) {
221		PPPT_GLOBAL_UNLOCK();
222		stmf_ic_msg_free(msg);
223		PPPT_INC_STAT(es_sess_destroy_no_session);
224		return;
225	}
226
227	tgt = ps->ps_target;
228
229	mutex_enter(&tgt->target_mutex);
230	mutex_enter(&ps->ps_mutex);
231
232	/* Release the reference from the lookup */
233	pppt_sess_rele_locked(ps);
234
235	/* Make sure another thread is not already closing the session */
236	if (!ps->ps_closed) {
237		/* Found matching open session, quiesce... */
238		pppt_sess_close_locked(ps);
239	}
240	mutex_exit(&ps->ps_mutex);
241	mutex_exit(&tgt->target_mutex);
242	PPPT_GLOBAL_UNLOCK();
243
244	stmf_ic_msg_free(msg);
245}
246
247static void
248pppt_msg_scsi_cmd(stmf_ic_msg_t *msg)
249{
250	pppt_sess_t			*pppt_sess;
251	pppt_buf_t			*pbuf;
252	stmf_ic_scsi_cmd_msg_t		*scmd;
253	pppt_task_t			*ptask;
254	scsi_task_t			*task;
255	pppt_status_t			pppt_status;
256	stmf_local_port_t		*lport;
257	stmf_scsi_session_t		*stmf_sess;
258	stmf_status_t			stmf_status;
259
260	/*
261	 * Get a task context
262	 */
263	ptask = pppt_task_alloc();
264	if (ptask == NULL) {
265		/*
266		 * We must be very low on memory.  Just free the message
267		 * and let the command timeout.
268		 */
269		stmf_ic_msg_free(msg);
270		PPPT_INC_STAT(es_scmd_ptask_alloc_fail);
271		return;
272	}
273
274	scmd = msg->icm_msg;
275
276	/*
277	 * Session are created implicitly on the first use of an
278	 * IT nexus
279	 */
280	pppt_sess = pppt_sess_lookup_create(scmd->icsc_tgt_devid,
281	    scmd->icsc_ini_devid, scmd->icsc_rport,
282	    scmd->icsc_session_id, &stmf_status);
283	if (pppt_sess == NULL) {
284		pppt_task_free(ptask);
285		pppt_msg_tx_status(msg, stmf_status);
286		stmf_ic_msg_free(msg);
287		PPPT_INC_STAT(es_scmd_sess_create_fail);
288		return;
289	}
290
291	ptask->pt_sess = pppt_sess;
292	ptask->pt_task_id = scmd->icsc_task_msgid;
293	stmf_sess = pppt_sess->ps_stmf_sess;
294	lport = stmf_sess->ss_lport;
295
296	/*
297	 * Add task to our internal task set.
298	 */
299	pppt_status = pppt_task_start(ptask);
300
301	if (pppt_status != 0) {
302		/* Release hold from pppt_sess_lookup_create() */
303		PPPT_LOG(CE_WARN, "Duplicate taskid from remote node 0x%llx",
304		    (longlong_t)scmd->icsc_task_msgid);
305		pppt_task_free(ptask);
306		pppt_sess_rele(pppt_sess);
307		pppt_msg_tx_status(msg, STMF_ALREADY);
308		stmf_ic_msg_free(msg);
309		PPPT_INC_STAT(es_scmd_dup_task_count);
310		return;
311	}
312
313	/*
314	 * Allocate STMF task context
315	 */
316	ptask->pt_stmf_task = stmf_task_alloc(lport, stmf_sess,
317	    scmd->icsc_task_lun_no,
318	    scmd->icsc_task_cdb_length, 0);
319	if (ptask->pt_stmf_task == NULL) {
320		/* NOTE: pppt_task_done() will free ptask. */
321		(void) pppt_task_done(ptask);
322		pppt_sess_rele(pppt_sess);
323		pppt_msg_tx_status(msg, STMF_ALLOC_FAILURE);
324		stmf_ic_msg_free(msg);
325		PPPT_INC_STAT(es_scmd_stask_alloc_fail);
326		return;
327	}
328
329	task = ptask->pt_stmf_task;
330	/* task_port_private reference is a real reference. */
331	(void) pppt_task_hold(ptask);
332	task->task_port_private = ptask;
333	task->task_flags = scmd->icsc_task_flags;
334	task->task_additional_flags = TASK_AF_PPPT_TASK;
335	task->task_priority = 0;
336
337	/*
338	 * Set task->task_mgmt_function to TM_NONE for a normal SCSI task
339	 * or one of these values for a task management command:
340	 *
341	 * TM_ABORT_TASK ***
342	 * TM_ABORT_TASK_SET
343	 * TM_CLEAR_ACA
344	 * TM_CLEAR_TASK_SET
345	 * TM_LUN_RESET
346	 * TM_TARGET_WARM_RESET
347	 * TM_TARGET_COLD_RESET
348	 *
349	 * *** Note that STMF does not currently support TM_ABORT_TASK so
350	 * port providers must implement this command on their own
351	 * (e.g. lookup the desired task and call stmf_abort).
352	 */
353	task->task_mgmt_function = scmd->icsc_task_mgmt_function;
354
355	task->task_max_nbufs = 1; /* Don't allow parallel xfers */
356	task->task_cmd_seq_no = msg->icm_msgid;
357	task->task_expected_xfer_length =
358	    scmd->icsc_task_expected_xfer_length;
359
360	if (scmd->icsc_task_cdb_length) {
361		bcopy(scmd->icsc_task_cdb, task->task_cdb,
362		    scmd->icsc_task_cdb_length);
363	}
364	bcopy(scmd->icsc_lun_id, ptask->pt_lun_id, 16);
365
366	if (scmd->icsc_immed_data_len) {
367		pbuf = ptask->pt_immed_data;
368		pbuf->pbuf_immed_msg = msg;
369		pbuf->pbuf_stmf_buf->db_data_size = scmd->icsc_immed_data_len;
370		pbuf->pbuf_stmf_buf->db_buf_size = scmd->icsc_immed_data_len;
371		pbuf->pbuf_stmf_buf->db_relative_offset = 0;
372		pbuf->pbuf_stmf_buf->db_sglist[0].seg_length =
373		    scmd->icsc_immed_data_len;
374		pbuf->pbuf_stmf_buf->db_sglist[0].seg_addr =
375		    scmd->icsc_immed_data;
376
377		stmf_post_task(task, pbuf->pbuf_stmf_buf);
378	} else {
379		stmf_post_task(task, NULL);
380		stmf_ic_msg_free(msg);
381	}
382}
383
384static void
385pppt_msg_data_xfer_done(stmf_ic_msg_t *msg)
386{
387	pppt_task_t				*pppt_task;
388	stmf_ic_scsi_data_xfer_done_msg_t	*data_xfer_done;
389
390	data_xfer_done = msg->icm_msg;
391
392	/*
393	 * Find task
394	 */
395	pppt_task = pppt_task_lookup(data_xfer_done->icsx_task_msgid);
396
397	/* If we found one, complete the transfer */
398	if (pppt_task != NULL) {
399		pppt_xfer_read_complete(pppt_task, data_xfer_done->icsx_status);
400	}
401
402	stmf_ic_msg_free(msg);
403}
404
405static void
406pppt_msg_handle_status(stmf_ic_msg_t *msg)
407{
408	/* Don't care for now */
409	stmf_ic_msg_free(msg);
410}
411