1/*-
2 * Copyright (c) 2010 by Panasas, Inc.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice immediately at the beginning of the file, without modification,
10 *    this list of conditions, and the following disclaimer.
11 * 2. The name of the author may not be used to endorse or promote products
12 *    derived from this software without specific prior written permission.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
18 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 */
26/* $FreeBSD$ */
27/*
28 * "Faulty" Multipath Device. Creates to devices to be set up as multipath,
29 * makes one or both of them non existent (or re existent) on demand.
30 */
31#include "vhba.h"
32#include <sys/sysctl.h>
33
34static int vhba_stop_lun;
35static int vhba_start_lun = 0;
36static int vhba_notify_stop = 1;
37static int vhba_notify_start = 1;
38static int vhba_inject_hwerr = 0;
39SYSCTL_INT(_debug, OID_AUTO, vhba_stop_lun, CTLFLAG_RW, &vhba_stop_lun, 0, "stop lun bitmap");
40SYSCTL_INT(_debug, OID_AUTO, vhba_start_lun, CTLFLAG_RW, &vhba_start_lun, 0, "start lun bitmap");
41SYSCTL_INT(_debug, OID_AUTO, vhba_notify_stop, CTLFLAG_RW, &vhba_notify_stop, 1, "notify when luns go away");
42SYSCTL_INT(_debug, OID_AUTO, vhba_notify_start, CTLFLAG_RW, &vhba_notify_start, 1, "notify when luns arrive");
43SYSCTL_INT(_debug, OID_AUTO, vhba_inject_hwerr, CTLFLAG_RW, &vhba_inject_hwerr, 0, "inject hardware error on lost luns");
44
45#define	MAX_TGT		1
46#define	MAX_LUN		2
47#define	VMP_TIME	hz
48
49#define	DISK_SIZE	32
50#define	DISK_SHIFT	9
51#define	DISK_NBLKS	((DISK_SIZE << 20) >> DISK_SHIFT)
52#define	PSEUDO_SPT	64
53#define	PSEUDO_HDS	64
54#define	PSEUDO_SPC	(PSEUDO_SPT * PSEUDO_HDS)
55
56typedef struct {
57	vhba_softc_t *	vhba;
58	uint8_t *	disk;
59	size_t		disk_size;
60	int		luns[2];
61	struct callout	tick;
62	struct task	qt;
63	TAILQ_HEAD(, ccb_hdr)   inproc;
64	int		nact, nact_high;
65} mptest_t;
66
67static timeout_t vhba_iodelay;
68static timeout_t vhba_timer;
69static void vhba_task(void *, int);
70static void mptest_act(mptest_t *, struct ccb_scsiio *);
71
72void
73vhba_init(vhba_softc_t *vhba)
74{
75	static mptest_t vhbastatic;
76
77	vhbastatic.vhba = vhba;
78	vhbastatic.disk_size = DISK_SIZE << 20;
79	vhbastatic.disk = malloc(vhbastatic.disk_size, M_DEVBUF, M_WAITOK|M_ZERO);
80	vhba->private = &vhbastatic;
81	callout_init_mtx(&vhbastatic.tick, &vhba->lock, 0);
82	callout_reset(&vhbastatic.tick, VMP_TIME, vhba_timer, vhba);
83	TAILQ_INIT(&vhbastatic.inproc);
84	TASK_INIT(&vhbastatic.qt, 0, vhba_task, &vhbastatic);
85	vhbastatic.luns[0] = 1;
86	vhbastatic.luns[1] = 1;
87}
88
89void
90vhba_fini(vhba_softc_t *vhba)
91{
92	mptest_t *vhbas = vhba->private;
93	callout_stop(&vhbas->tick);
94	vhba->private = NULL;
95	free(vhbas->disk, M_DEVBUF);
96}
97
98void
99vhba_kick(vhba_softc_t *vhba)
100{
101	mptest_t *vhbas = vhba->private;
102	taskqueue_enqueue(taskqueue_swi, &vhbas->qt);
103}
104
105static void
106vhba_task(void *arg, int pending)
107{
108	mptest_t *vhbas = arg;
109	struct ccb_hdr *ccbh;
110	int nadded = 0;
111
112	mtx_lock(&vhbas->vhba->lock);
113	while ((ccbh = TAILQ_FIRST(&vhbas->vhba->actv)) != NULL) {
114		TAILQ_REMOVE(&vhbas->vhba->actv, ccbh, sim_links.tqe);
115                mptest_act(vhbas, (struct ccb_scsiio *)ccbh);
116		nadded++;
117		ccbh->sim_priv.entries[0].ptr = vhbas;
118		callout_handle_init(&ccbh->timeout_ch);
119	}
120	if (nadded) {
121		vhba_kick(vhbas->vhba);
122	} else {
123		while ((ccbh = TAILQ_FIRST(&vhbas->vhba->done)) != NULL) {
124			TAILQ_REMOVE(&vhbas->vhba->done, ccbh, sim_links.tqe);
125			xpt_done((union ccb *)ccbh);
126		}
127	}
128	mtx_unlock(&vhbas->vhba->lock);
129}
130
131static void
132mptest_act(mptest_t *vhbas, struct ccb_scsiio *csio)
133{
134	char junk[128];
135	cam_status camstatus;
136	uint8_t *cdb, *ptr, status;
137	uint32_t data_len, blkcmd;
138	uint64_t off;
139
140	blkcmd = data_len = 0;
141	status = SCSI_STATUS_OK;
142
143	memset(&csio->sense_data, 0, sizeof (csio->sense_data));
144	cdb = csio->cdb_io.cdb_bytes;
145
146	if (csio->ccb_h.target_id >= MAX_TGT) {
147		vhba_set_status(&csio->ccb_h, CAM_SEL_TIMEOUT);
148		TAILQ_INSERT_TAIL(&vhbas->vhba->done, &csio->ccb_h, sim_links.tqe);
149		return;
150	}
151	if (vhba_inject_hwerr && csio->ccb_h.target_lun < MAX_LUN && vhbas->luns[csio->ccb_h.target_lun] == 0) {
152		vhba_fill_sense(csio, SSD_KEY_HARDWARE_ERROR, 0x44, 0x0);
153		TAILQ_INSERT_TAIL(&vhbas->vhba->done, &csio->ccb_h, sim_links.tqe);
154		return;
155	}
156	if ((csio->ccb_h.target_lun >= MAX_LUN || vhbas->luns[csio->ccb_h.target_lun] == 0) && cdb[0] != INQUIRY && cdb[0] != REPORT_LUNS && cdb[0] != REQUEST_SENSE) {
157		vhba_fill_sense(csio, SSD_KEY_ILLEGAL_REQUEST, 0x25, 0x0);
158		TAILQ_INSERT_TAIL(&vhbas->vhba->done, &csio->ccb_h, sim_links.tqe);
159		return;
160	}
161
162	switch (cdb[0]) {
163	case MODE_SENSE:
164	case MODE_SENSE_10:
165	{
166		unsigned int nbyte;
167		uint8_t page = cdb[2] & SMS_PAGE_CODE;
168		uint8_t pgctl = cdb[2] & SMS_PAGE_CTRL_MASK;
169
170		switch (page) {
171		case SMS_FORMAT_DEVICE_PAGE:
172		case SMS_GEOMETRY_PAGE:
173		case SMS_CACHE_PAGE:
174		case SMS_CONTROL_MODE_PAGE:
175		case SMS_ALL_PAGES_PAGE:
176			break;
177		default:
178			vhba_fill_sense(csio, SSD_KEY_ILLEGAL_REQUEST, 0x24, 0x0);
179			TAILQ_INSERT_TAIL(&vhbas->vhba->done, &csio->ccb_h, sim_links.tqe);
180			return;
181		}
182		memset(junk, 0, sizeof (junk));
183		if (cdb[1] & SMS_DBD) {
184			ptr = &junk[4];
185		} else {
186			ptr = junk;
187			ptr[3] = 8;
188			ptr[4] = ((1 << DISK_SHIFT) >> 24) & 0xff;
189			ptr[5] = ((1 << DISK_SHIFT) >> 16) & 0xff;
190			ptr[6] = ((1 << DISK_SHIFT) >>  8) & 0xff;
191			ptr[7] = ((1 << DISK_SHIFT)) & 0xff;
192
193			ptr[8] = (DISK_NBLKS >> 24) & 0xff;
194			ptr[9] = (DISK_NBLKS >> 16) & 0xff;
195			ptr[10] = (DISK_NBLKS >> 8) & 0xff;
196			ptr[11] = DISK_NBLKS & 0xff;
197			ptr += 12;
198		}
199
200		if (page == SMS_ALL_PAGES_PAGE || page == SMS_FORMAT_DEVICE_PAGE) {
201			ptr[0] = SMS_FORMAT_DEVICE_PAGE;
202			ptr[1] = 24;
203			if (pgctl != SMS_PAGE_CTRL_CHANGEABLE) {
204				/* tracks per zone */
205				/* ptr[2] = 0; */
206				/* ptr[3] = 0; */
207				/* alternate sectors per zone */
208				/* ptr[4] = 0; */
209				/* ptr[5] = 0; */
210				/* alternate tracks per zone */
211				/* ptr[6] = 0; */
212				/* ptr[7] = 0; */
213				/* alternate tracks per logical unit */
214				/* ptr[8] = 0; */
215				/* ptr[9] = 0; */
216				/* sectors per track */
217				ptr[10] = (PSEUDO_SPT >> 8) & 0xff;
218				ptr[11] = PSEUDO_SPT & 0xff;
219				/* data bytes per physical sector */
220				ptr[12] = ((1 << DISK_SHIFT) >> 8) & 0xff;
221				ptr[13] = (1 << DISK_SHIFT) & 0xff;
222				/* interleave */
223				/* ptr[14] = 0; */
224				/* ptr[15] = 1; */
225				/* track skew factor */
226				/* ptr[16] = 0; */
227				/* ptr[17] = 0; */
228				/* cylinder skew factor */
229				/* ptr[18] = 0; */
230				/* ptr[19] = 0; */
231				/* SSRC, HSEC, RMB, SURF */
232			}
233			ptr += 26;
234		}
235
236		if (page == SMS_ALL_PAGES_PAGE || page == SMS_GEOMETRY_PAGE) {
237			ptr[0] = SMS_GEOMETRY_PAGE;
238			ptr[1] = 24;
239			if (pgctl != SMS_PAGE_CTRL_CHANGEABLE) {
240				uint32_t cyl = (DISK_NBLKS + ((PSEUDO_SPC - 1))) / PSEUDO_SPC;
241				/* number of cylinders */
242				ptr[2] = (cyl >> 24) & 0xff;
243				ptr[3] = (cyl >> 16) & 0xff;
244				ptr[4] = cyl & 0xff;
245				/* number of heads */
246				ptr[5] = PSEUDO_HDS;
247				/* starting cylinder- write precompensation */
248				/* ptr[6] = 0; */
249				/* ptr[7] = 0; */
250				/* ptr[8] = 0; */
251				/* starting cylinder- reduced write current */
252				/* ptr[9] = 0; */
253				/* ptr[10] = 0; */
254				/* ptr[11] = 0; */
255				/* drive step rate */
256				/* ptr[12] = 0; */
257				/* ptr[13] = 0; */
258				/* landing zone cylinder */
259				/* ptr[14] = 0; */
260				/* ptr[15] = 0; */
261				/* ptr[16] = 0; */
262				/* RPL */
263				/* ptr[17] = 0; */
264				/* rotational offset */
265				/* ptr[18] = 0; */
266				/* medium rotation rate -  7200 RPM */
267				ptr[20] = 0x1c;
268				ptr[21] = 0x20;
269			}
270			ptr += 26;
271		}
272
273		if (page == SMS_ALL_PAGES_PAGE || page == SMS_CACHE_PAGE) {
274			ptr[0] = SMS_CACHE_PAGE;
275			ptr[1] = 18;
276			ptr[2] = 1 << 2;
277			ptr += 20;
278		}
279
280		if (page == SMS_ALL_PAGES_PAGE || page == SMS_CONTROL_MODE_PAGE) {
281			ptr[0] = SMS_CONTROL_MODE_PAGE;
282			ptr[1] = 10;
283			if (pgctl != SMS_PAGE_CTRL_CHANGEABLE) {
284				ptr[3] = 1 << 4; /* unrestricted reordering allowed */
285				ptr[8] = 0x75;   /* 30000 ms */
286				ptr[9] = 0x30;
287			}
288			ptr += 12;
289		}
290		nbyte = (char *)ptr - &junk[0];
291		ptr[0] = nbyte - 4;
292
293		if (cdb[0] == MODE_SENSE) {
294			data_len = min(cdb[4], csio->dxfer_len);
295		} else {
296			uint16_t tw = (cdb[7] << 8) | cdb[8];
297			data_len = min(tw, csio->dxfer_len);
298		}
299		data_len = min(data_len, nbyte);
300		if (data_len) {
301			memcpy(csio->data_ptr, junk, data_len);
302		}
303		csio->resid = csio->dxfer_len - data_len;
304		break;
305	}
306	case READ_6:
307	case READ_10:
308	case READ_12:
309	case READ_16:
310	case WRITE_6:
311	case WRITE_10:
312	case WRITE_12:
313	case WRITE_16:
314		if (vhba_rwparm(cdb, &off, &data_len, DISK_NBLKS, DISK_SHIFT)) {
315			vhba_fill_sense(csio, SSD_KEY_ILLEGAL_REQUEST, 0x24, 0x0);
316			break;
317		}
318		blkcmd++;
319		if (++vhbas->nact > vhbas->nact_high) {
320			vhbas->nact_high = vhbas->nact;
321			printf("%s: high block count now %d\n", __func__, vhbas->nact);
322		}
323		if (data_len) {
324			if ((cdb[0] & 0xf) == 8) {
325				memcpy(csio->data_ptr, &vhbas->disk[off], data_len);
326			} else {
327				memcpy(&vhbas->disk[off], csio->data_ptr, data_len);
328			}
329			csio->resid = csio->dxfer_len - data_len;
330		} else {
331			csio->resid = csio->dxfer_len;
332		}
333		break;
334
335	case READ_CAPACITY:
336		if (cdb[2] || cdb[3] || cdb[4] || cdb[5]) {
337			vhba_fill_sense(csio, SSD_KEY_UNIT_ATTENTION, 0x24, 0x0);
338			break;
339		}
340		if (cdb[8] & 0x1) { /* PMI */
341			csio->data_ptr[0] = 0xff;
342			csio->data_ptr[1] = 0xff;
343			csio->data_ptr[2] = 0xff;
344			csio->data_ptr[3] = 0xff;
345		} else {
346			uint64_t last_blk = DISK_NBLKS - 1;
347			if (last_blk < 0xffffffffULL) {
348			    csio->data_ptr[0] = (last_blk >> 24) & 0xff;
349			    csio->data_ptr[1] = (last_blk >> 16) & 0xff;
350			    csio->data_ptr[2] = (last_blk >>  8) & 0xff;
351			    csio->data_ptr[3] = (last_blk) & 0xff;
352			} else {
353			    csio->data_ptr[0] = 0xff;
354			    csio->data_ptr[1] = 0xff;
355			    csio->data_ptr[2] = 0xff;
356			    csio->data_ptr[3] = 0xff;
357			}
358		}
359		csio->data_ptr[4] = ((1 << DISK_SHIFT) >> 24) & 0xff;
360		csio->data_ptr[5] = ((1 << DISK_SHIFT) >> 16) & 0xff;
361		csio->data_ptr[6] = ((1 << DISK_SHIFT) >>  8) & 0xff;
362		csio->data_ptr[7] = ((1 << DISK_SHIFT)) & 0xff;
363		break;
364	default:
365		vhba_default_cmd(csio, MAX_LUN, NULL);
366		break;
367	}
368	if (csio->scsi_status != SCSI_STATUS_OK) {
369		camstatus = CAM_SCSI_STATUS_ERROR;
370		if (csio->scsi_status == SCSI_STATUS_CHECK_COND) {
371			camstatus |= CAM_AUTOSNS_VALID;
372		}
373	} else {
374		csio->scsi_status = SCSI_STATUS_OK;
375		camstatus = CAM_REQ_CMP;
376	}
377	vhba_set_status(&csio->ccb_h, camstatus);
378	if (blkcmd) {
379		int ticks;
380		struct timeval t;
381
382		TAILQ_INSERT_TAIL(&vhbas->inproc, &csio->ccb_h, sim_links.tqe);
383		t.tv_sec = 0;
384		t.tv_usec = (500 + arc4random());
385		if (t.tv_usec > 10000) {
386			t.tv_usec = 10000;
387		}
388		ticks = tvtohz(&t);
389		csio->ccb_h.timeout_ch = timeout(vhba_iodelay, &csio->ccb_h, ticks);
390	} else {
391		TAILQ_INSERT_TAIL(&vhbas->vhba->done, &csio->ccb_h, sim_links.tqe);
392	}
393}
394
395static void
396vhba_iodelay(void *arg)
397{
398	struct ccb_hdr *ccbh = arg;
399	mptest_t *vhbas = ccbh->sim_priv.entries[0].ptr;
400
401	mtx_lock(&vhbas->vhba->lock);
402	TAILQ_REMOVE(&vhbas->inproc, ccbh, sim_links.tqe);
403	TAILQ_INSERT_TAIL(&vhbas->vhba->done, ccbh, sim_links.tqe);
404	vhbas->nact -= 1;
405	vhba_kick(vhbas->vhba);
406	mtx_unlock(&vhbas->vhba->lock);
407}
408
409static void
410vhba_timer(void *arg)
411{
412	int lun;
413	vhba_softc_t *vhba = arg;
414	mptest_t *vhbas = vhba->private;
415	if (vhba_stop_lun) {
416		lun = (vhba_stop_lun & 1)? 0 : 1;
417		if (lun == 0 || lun == 1) {
418			if (vhbas->luns[lun]) {
419				struct cam_path *tp;
420				if (vhba_notify_stop) {
421					if (xpt_create_path(&tp, xpt_periph, cam_sim_path(vhba->sim), 0, lun) != CAM_REQ_CMP) {
422						goto out;
423					}
424					vhbas->luns[lun] = 0;
425					xpt_async(AC_LOST_DEVICE, tp, NULL);
426					xpt_free_path(tp);
427				} else {
428					vhbas->luns[lun] = 0;
429				}
430			}
431		}
432		vhba_stop_lun &= ~(1 << lun);
433	} else if (vhba_start_lun) {
434		lun = (vhba_start_lun & 1)? 0 : 1;
435		if (lun == 0 || lun == 1) {
436			if (vhbas->luns[lun] == 0) {
437				if (vhba_notify_start) {
438					union ccb *ccb;
439					ccb = xpt_alloc_ccb_nowait();
440					if (ccb == NULL) {
441						goto out;
442					}
443					if (xpt_create_path(&ccb->ccb_h.path, xpt_periph, cam_sim_path(vhba->sim), CAM_TARGET_WILDCARD, CAM_LUN_WILDCARD) != CAM_REQ_CMP) {
444						xpt_free_ccb(ccb);
445						goto out;
446					}
447					vhbas->luns[lun] = 1;
448					xpt_rescan(ccb);
449				} else {
450					vhbas->luns[lun] = 1;
451				}
452			}
453		}
454		vhba_start_lun &= ~(1 << lun);
455	}
456out:
457	callout_reset(&vhbas->tick, VMP_TIME, vhba_timer, vhba);
458}
459DEV_MODULE(vhba_mptest, vhba_modprobe, NULL);
460