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 * "Simple" VHBA device
29 */
30#include "vhba.h"
31
32#define	MAX_TGT		1
33#define	MAX_LUN		1
34
35#define	DISK_SIZE	32
36#define	DISK_SHIFT	9
37#define	DISK_NBLKS	((DISK_SIZE << 20) >> DISK_SHIFT)
38#define	PSEUDO_SPT	64
39#define	PSEUDO_HDS	64
40#define	PSEUDO_SPC	(PSEUDO_SPT * PSEUDO_HDS)
41
42typedef struct {
43	vhba_softc_t *	vhba;
44	uint8_t *	disk;
45	size_t		disk_size;
46	struct task	qt;
47} vhbasimple_t;
48
49static void vhba_task(void *, int);
50static void vhbasimple_act(vhbasimple_t *, struct ccb_scsiio *);
51
52void
53vhba_init(vhba_softc_t *vhba)
54{
55	static vhbasimple_t vhbas;
56	vhbas.vhba = vhba;
57	vhbas.disk_size = DISK_SIZE << 20;
58	vhbas.disk = malloc(vhbas.disk_size, M_DEVBUF, M_WAITOK|M_ZERO);
59	vhba->private = &vhbas;
60	TASK_INIT(&vhbas.qt, 0, vhba_task, &vhbas);
61}
62
63
64void
65vhba_fini(vhba_softc_t *vhba)
66{
67	vhbasimple_t *vhbas = vhba->private;
68	vhba->private = NULL;
69	free(vhbas->disk, M_DEVBUF);
70}
71
72void
73vhba_kick(vhba_softc_t *vhba)
74{
75	vhbasimple_t *vhbas = vhba->private;
76	taskqueue_enqueue(taskqueue_swi, &vhbas->qt);
77}
78
79static void
80vhba_task(void *arg, int pending)
81{
82	vhbasimple_t *vhbas = arg;
83	struct ccb_hdr *ccbh;
84
85	mtx_lock(&vhbas->vhba->lock);
86	while ((ccbh = TAILQ_FIRST(&vhbas->vhba->actv)) != NULL) {
87		TAILQ_REMOVE(&vhbas->vhba->actv, ccbh, sim_links.tqe);
88                vhbasimple_act(vhbas, (struct ccb_scsiio *)ccbh);
89	}
90	while ((ccbh = TAILQ_FIRST(&vhbas->vhba->done)) != NULL) {
91		TAILQ_REMOVE(&vhbas->vhba->done, ccbh, sim_links.tqe);
92		xpt_done((union ccb *)ccbh);
93	}
94	mtx_unlock(&vhbas->vhba->lock);
95}
96
97static void
98vhbasimple_act(vhbasimple_t *vhbas, struct ccb_scsiio *csio)
99{
100	char junk[128];
101	uint8_t *cdb, *ptr, status;
102	uint32_t data_len;
103	uint64_t off;
104
105	data_len = 0;
106	status = SCSI_STATUS_OK;
107
108	memset(&csio->sense_data, 0, sizeof (csio->sense_data));
109	cdb = csio->cdb_io.cdb_bytes;
110
111	if (csio->ccb_h.target_id >=  MAX_TGT) {
112		csio->ccb_h.status = CAM_SEL_TIMEOUT;
113		TAILQ_INSERT_TAIL(&vhbas->vhba->done, &csio->ccb_h, sim_links.tqe);
114		return;
115	}
116	if (csio->ccb_h.target_lun >= MAX_LUN && cdb[0] != INQUIRY && cdb[0] != REPORT_LUNS && cdb[0] != REQUEST_SENSE) {
117		vhba_fill_sense(csio, SSD_KEY_ILLEGAL_REQUEST, 0x25, 0x0);
118		TAILQ_INSERT_TAIL(&vhbas->vhba->done, &csio->ccb_h, sim_links.tqe);
119		return;
120	}
121
122	switch (cdb[0]) {
123	case MODE_SENSE:
124	case MODE_SENSE_10:
125	{
126		unsigned int nbyte;
127		uint8_t page = cdb[2] & SMS_PAGE_CODE;
128		uint8_t pgctl = cdb[2] & SMS_PAGE_CTRL_MASK;
129
130		switch (page) {
131		case SMS_FORMAT_DEVICE_PAGE:
132		case SMS_GEOMETRY_PAGE:
133		case SMS_CACHE_PAGE:
134		case SMS_CONTROL_MODE_PAGE:
135		case SMS_ALL_PAGES_PAGE:
136			break;
137		default:
138			vhba_fill_sense(csio, SSD_KEY_ILLEGAL_REQUEST, 0x24, 0x0);
139			TAILQ_INSERT_TAIL(&vhbas->vhba->done, &csio->ccb_h, sim_links.tqe);
140			return;
141		}
142		memset(junk, 0, sizeof (junk));
143		if (cdb[1] & SMS_DBD) {
144			ptr = &junk[4];
145		} else {
146			ptr = junk;
147			ptr[3] = 8;
148			ptr[4] = ((1 << DISK_SHIFT) >> 24) & 0xff;
149			ptr[5] = ((1 << DISK_SHIFT) >> 16) & 0xff;
150			ptr[6] = ((1 << DISK_SHIFT) >>  8) & 0xff;
151			ptr[7] = ((1 << DISK_SHIFT)) & 0xff;
152
153			ptr[8] = (DISK_NBLKS >> 24) & 0xff;
154			ptr[9] = (DISK_NBLKS >> 16) & 0xff;
155			ptr[10] = (DISK_NBLKS >> 8) & 0xff;
156			ptr[11] = DISK_NBLKS & 0xff;
157			ptr += 12;
158		}
159
160		if (page == SMS_ALL_PAGES_PAGE || page == SMS_FORMAT_DEVICE_PAGE) {
161			ptr[0] = SMS_FORMAT_DEVICE_PAGE;
162			ptr[1] = 24;
163			if (pgctl != SMS_PAGE_CTRL_CHANGEABLE) {
164				/* tracks per zone */
165				/* ptr[2] = 0; */
166				/* ptr[3] = 0; */
167				/* alternate sectors per zone */
168				/* ptr[4] = 0; */
169				/* ptr[5] = 0; */
170				/* alternate tracks per zone */
171				/* ptr[6] = 0; */
172				/* ptr[7] = 0; */
173				/* alternate tracks per logical unit */
174				/* ptr[8] = 0; */
175				/* ptr[9] = 0; */
176				/* sectors per track */
177				ptr[10] = (PSEUDO_SPT >> 8) & 0xff;
178				ptr[11] = PSEUDO_SPT & 0xff;
179				/* data bytes per physical sector */
180				ptr[12] = ((1 << DISK_SHIFT) >> 8) & 0xff;
181				ptr[13] = (1 << DISK_SHIFT) & 0xff;
182				/* interleave */
183				/* ptr[14] = 0; */
184				/* ptr[15] = 1; */
185				/* track skew factor */
186				/* ptr[16] = 0; */
187				/* ptr[17] = 0; */
188				/* cylinder skew factor */
189				/* ptr[18] = 0; */
190				/* ptr[19] = 0; */
191				/* SSRC, HSEC, RMB, SURF */
192			}
193			ptr += 26;
194		}
195
196		if (page == SMS_ALL_PAGES_PAGE || page == SMS_GEOMETRY_PAGE) {
197			ptr[0] = SMS_GEOMETRY_PAGE;
198			ptr[1] = 24;
199			if (pgctl != SMS_PAGE_CTRL_CHANGEABLE) {
200				uint32_t cyl = (DISK_NBLKS + ((PSEUDO_SPC - 1))) / PSEUDO_SPC;
201				/* number of cylinders */
202				ptr[2] = (cyl >> 24) & 0xff;
203				ptr[3] = (cyl >> 16) & 0xff;
204				ptr[4] = cyl & 0xff;
205				/* number of heads */
206				ptr[5] = PSEUDO_HDS;
207				/* starting cylinder- write precompensation */
208				/* ptr[6] = 0; */
209				/* ptr[7] = 0; */
210				/* ptr[8] = 0; */
211				/* starting cylinder- reduced write current */
212				/* ptr[9] = 0; */
213				/* ptr[10] = 0; */
214				/* ptr[11] = 0; */
215				/* drive step rate */
216				/* ptr[12] = 0; */
217				/* ptr[13] = 0; */
218				/* landing zone cylinder */
219				/* ptr[14] = 0; */
220				/* ptr[15] = 0; */
221				/* ptr[16] = 0; */
222				/* RPL */
223				/* ptr[17] = 0; */
224				/* rotational offset */
225				/* ptr[18] = 0; */
226				/* medium rotation rate -  7200 RPM */
227				ptr[20] = 0x1c;
228				ptr[21] = 0x20;
229			}
230			ptr += 26;
231		}
232
233		if (page == SMS_ALL_PAGES_PAGE || page == SMS_CACHE_PAGE) {
234			ptr[0] = SMS_CACHE_PAGE;
235			ptr[1] = 18;
236			ptr[2] = 1 << 2;
237			ptr += 20;
238		}
239
240		if (page == SMS_ALL_PAGES_PAGE || page == SMS_CONTROL_MODE_PAGE) {
241			ptr[0] = SMS_CONTROL_MODE_PAGE;
242			ptr[1] = 10;
243			if (pgctl != SMS_PAGE_CTRL_CHANGEABLE) {
244				ptr[3] = 1 << 4; /* unrestricted reordering allowed */
245				ptr[8] = 0x75;   /* 30000 ms */
246				ptr[9] = 0x30;
247			}
248			ptr += 12;
249		}
250		nbyte = (char *)ptr - &junk[0];
251		ptr[0] = nbyte - 4;
252
253		if (cdb[0] == MODE_SENSE) {
254			data_len = min(cdb[4], csio->dxfer_len);
255		} else {
256			uint16_t tw = (cdb[7] << 8) | cdb[8];
257			data_len = min(tw, csio->dxfer_len);
258		}
259		data_len = min(data_len, nbyte);
260		if (data_len) {
261			memcpy(csio->data_ptr, junk, data_len);
262		}
263		csio->resid = csio->dxfer_len - data_len;
264		break;
265	}
266	case READ_6:
267	case READ_10:
268	case READ_12:
269	case READ_16:
270	case WRITE_6:
271	case WRITE_10:
272	case WRITE_12:
273	case WRITE_16:
274		if (vhba_rwparm(cdb, &off, &data_len, DISK_NBLKS, DISK_SHIFT)) {
275			vhba_fill_sense(csio, SSD_KEY_ILLEGAL_REQUEST, 0x24, 0x0);
276			break;
277		}
278		if (data_len) {
279			if ((cdb[0] & 0xf) == 8) {
280				memcpy(csio->data_ptr, &vhbas->disk[off], data_len);
281			} else {
282				memcpy(&vhbas->disk[off], csio->data_ptr, data_len);
283			}
284			csio->resid = csio->dxfer_len - data_len;
285		} else {
286			csio->resid = csio->dxfer_len;
287		}
288		break;
289		break;
290
291	case READ_CAPACITY:
292		if (cdb[2] || cdb[3] || cdb[4] || cdb[5]) {
293			vhba_fill_sense(csio, SSD_KEY_UNIT_ATTENTION, 0x24, 0x0);
294			break;
295		}
296		if (cdb[8] & 0x1) { /* PMI */
297			csio->data_ptr[0] = 0xff;
298			csio->data_ptr[1] = 0xff;
299			csio->data_ptr[2] = 0xff;
300			csio->data_ptr[3] = 0xff;
301		} else {
302			uint64_t last_blk = DISK_NBLKS - 1;
303			if (last_blk < 0xffffffffULL) {
304			    csio->data_ptr[0] = (last_blk >> 24) & 0xff;
305			    csio->data_ptr[1] = (last_blk >> 16) & 0xff;
306			    csio->data_ptr[2] = (last_blk >>  8) & 0xff;
307			    csio->data_ptr[3] = (last_blk) & 0xff;
308			} else {
309			    csio->data_ptr[0] = 0xff;
310			    csio->data_ptr[1] = 0xff;
311			    csio->data_ptr[2] = 0xff;
312			    csio->data_ptr[3] = 0xff;
313			}
314		}
315		csio->data_ptr[4] = ((1 << DISK_SHIFT) >> 24) & 0xff;
316		csio->data_ptr[5] = ((1 << DISK_SHIFT) >> 16) & 0xff;
317		csio->data_ptr[6] = ((1 << DISK_SHIFT) >>  8) & 0xff;
318		csio->data_ptr[7] = ((1 << DISK_SHIFT)) & 0xff;
319		break;
320
321	default:
322		vhba_default_cmd(csio, MAX_LUN, NULL);
323		break;
324	}
325	csio->ccb_h.status &= ~CAM_STATUS_MASK;
326	if (csio->scsi_status != SCSI_STATUS_OK) {
327		csio->ccb_h.status |= CAM_SCSI_STATUS_ERROR;
328		if (csio->scsi_status == SCSI_STATUS_CHECK_COND) {
329			csio->ccb_h.status |= CAM_AUTOSNS_VALID;
330		}
331	} else {
332		csio->scsi_status = SCSI_STATUS_OK;
333		csio->ccb_h.status |= CAM_REQ_CMP;
334	}
335	TAILQ_INSERT_TAIL(&vhbas->vhba->done, &csio->ccb_h, sim_links.tqe);
336}
337DEV_MODULE(vhba_simple, vhba_modprobe, NULL);
338