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