1 /* 2 * This file and its contents are supplied under the terms of the 3 * Common Development and Distribution License ("CDDL"), version 1.0. 4 * You may only use this file in accordance with the terms of version 5 * 1.0 of the CDDL. 6 * 7 * A full copy of the text of the CDDL should have accompanied this 8 * source. A copy of the CDDL is also available via the Internet at 9 * http://www.illumos.org/license/CDDL. 10 */ 11 12 /* 13 * Copyright (c) 2017, Joyent, Inc. 14 */ 15 16 #include <sys/scsi/adapters/smrt/smrt.h> 17 18 static int smrt_attach(dev_info_t *, ddi_attach_cmd_t); 19 static int smrt_detach(dev_info_t *, ddi_detach_cmd_t); 20 static int smrt_ioctl(dev_t, int, intptr_t, int, cred_t *, int *); 21 static void smrt_cleanup(smrt_t *); 22 static int smrt_command_comparator(const void *, const void *); 23 24 /* 25 * Controller soft state. Each entry is an object of type "smrt_t". 26 */ 27 void *smrt_state; 28 29 /* 30 * DMA attributes template. Each controller will make a copy of this template 31 * with appropriate customisations; e.g., the Scatter/Gather List Length. 32 */ 33 static ddi_dma_attr_t smrt_dma_attr_template = { 34 .dma_attr_version = DMA_ATTR_V0, 35 .dma_attr_addr_lo = 0x0000000000000000, 36 .dma_attr_addr_hi = 0xFFFFFFFFFFFFFFFF, 37 .dma_attr_count_max = 0x00FFFFFF, 38 .dma_attr_align = 0x20, 39 .dma_attr_burstsizes = 0x20, 40 .dma_attr_minxfer = DMA_UNIT_8, 41 .dma_attr_maxxfer = 0xFFFFFFFF, 42 /* 43 * There is some suggestion that at least some, possibly older, Smart 44 * Array controllers cannot tolerate a DMA segment that straddles a 4GB 45 * boundary. 46 */ 47 .dma_attr_seg = 0xFFFFFFFF, 48 .dma_attr_sgllen = 1, 49 .dma_attr_granular = 512, 50 .dma_attr_flags = 0 51 }; 52 53 /* 54 * Device memory access attributes for device control registers. 55 */ 56 ddi_device_acc_attr_t smrt_dev_attributes = { 57 .devacc_attr_version = DDI_DEVICE_ATTR_V0, 58 .devacc_attr_endian_flags = DDI_STRUCTURE_LE_ACC, 59 .devacc_attr_dataorder = DDI_STRICTORDER_ACC, 60 .devacc_attr_access = 0 61 }; 62 63 /* 64 * Character/Block Operations Structure 65 */ 66 static struct cb_ops smrt_cb_ops = { 67 .cb_rev = CB_REV, 68 .cb_flag = D_NEW | D_MP, 69 70 .cb_open = scsi_hba_open, 71 .cb_close = scsi_hba_close, 72 73 .cb_ioctl = smrt_ioctl, 74 75 .cb_strategy = nodev, 76 .cb_print = nodev, 77 .cb_dump = nodev, 78 .cb_read = nodev, 79 .cb_write = nodev, 80 .cb_devmap = nodev, 81 .cb_mmap = nodev, 82 .cb_segmap = nodev, 83 .cb_chpoll = nochpoll, 84 .cb_prop_op = ddi_prop_op, 85 .cb_str = NULL, 86 .cb_aread = nodev, 87 .cb_awrite = nodev 88 }; 89 90 /* 91 * Device Operations Structure 92 */ 93 static struct dev_ops smrt_dev_ops = { 94 .devo_rev = DEVO_REV, 95 .devo_refcnt = 0, 96 97 .devo_attach = smrt_attach, 98 .devo_detach = smrt_detach, 99 100 .devo_cb_ops = &smrt_cb_ops, 101 102 .devo_getinfo = nodev, 103 .devo_identify = nulldev, 104 .devo_probe = nulldev, 105 .devo_reset = nodev, 106 .devo_bus_ops = NULL, 107 .devo_power = nodev, 108 .devo_quiesce = nodev 109 }; 110 111 /* 112 * Linkage structures 113 */ 114 static struct modldrv smrt_modldrv = { 115 .drv_modops = &mod_driverops, 116 .drv_linkinfo = "HP Smart Array", 117 .drv_dev_ops = &smrt_dev_ops 118 }; 119 120 static struct modlinkage smrt_modlinkage = { 121 .ml_rev = MODREV_1, 122 .ml_linkage = { &smrt_modldrv, NULL } 123 }; 124 125 126 int 127 _init() 128 { 129 int r; 130 131 VERIFY0(ddi_soft_state_init(&smrt_state, sizeof (smrt_t), 0)); 132 133 if ((r = scsi_hba_init(&smrt_modlinkage)) != 0) { 134 goto fail; 135 } 136 137 if ((r = mod_install(&smrt_modlinkage)) != 0) { 138 scsi_hba_fini(&smrt_modlinkage); 139 goto fail; 140 } 141 142 return (r); 143 144 fail: 145 ddi_soft_state_fini(&smrt_state); 146 return (r); 147 } 148 149 int 150 _fini() 151 { 152 int r; 153 154 if ((r = mod_remove(&smrt_modlinkage)) == 0) { 155 scsi_hba_fini(&smrt_modlinkage); 156 ddi_soft_state_fini(&smrt_state); 157 } 158 159 return (r); 160 } 161 162 int 163 _info(struct modinfo *modinfop) 164 { 165 return (mod_info(&smrt_modlinkage, modinfop)); 166 } 167 168 static int 169 smrt_iport_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) 170 { 171 const char *addr; 172 dev_info_t *pdip; 173 int instance; 174 smrt_t *smrt; 175 176 if (cmd != DDI_ATTACH) 177 return (DDI_FAILURE); 178 179 /* 180 * Note, we cannot get to our parent via the tran's tran_hba_private 181 * member. This pointer is reset to NULL when the scsi_hba_tran_t 182 * structure is duplicated. 183 */ 184 addr = scsi_hba_iport_unit_address(dip); 185 VERIFY(addr != NULL); 186 pdip = ddi_get_parent(dip); 187 instance = ddi_get_instance(pdip); 188 smrt = ddi_get_soft_state(smrt_state, instance); 189 VERIFY(smrt != NULL); 190 191 if (strcmp(addr, SMRT_IPORT_VIRT) == 0) { 192 if (smrt_logvol_hba_setup(smrt, dip) != DDI_SUCCESS) 193 return (DDI_FAILURE); 194 smrt->smrt_virt_iport = dip; 195 } else if (strcmp(addr, SMRT_IPORT_PHYS) == 0) { 196 if (smrt_phys_hba_setup(smrt, dip) != DDI_SUCCESS) 197 return (DDI_FAILURE); 198 smrt->smrt_phys_iport = dip; 199 } else { 200 return (DDI_FAILURE); 201 } 202 203 return (DDI_SUCCESS); 204 } 205 206 static int 207 smrt_iport_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) 208 { 209 const char *addr; 210 scsi_hba_tran_t *tran; 211 smrt_t *smrt; 212 213 if (cmd != DDI_DETACH) 214 return (DDI_FAILURE); 215 216 tran = ddi_get_driver_private(dip); 217 VERIFY(tran != NULL); 218 smrt = tran->tran_hba_private; 219 VERIFY(smrt != NULL); 220 221 addr = scsi_hba_iport_unit_address(dip); 222 VERIFY(addr != NULL); 223 224 if (strcmp(addr, SMRT_IPORT_VIRT) == 0) { 225 smrt_logvol_hba_teardown(smrt, dip); 226 smrt->smrt_virt_iport = NULL; 227 } else if (strcmp(addr, SMRT_IPORT_PHYS) == 0) { 228 smrt_phys_hba_teardown(smrt, dip); 229 smrt->smrt_phys_iport = NULL; 230 } else { 231 return (DDI_FAILURE); 232 } 233 234 return (DDI_SUCCESS); 235 } 236 237 static int 238 smrt_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) 239 { 240 uint32_t instance; 241 smrt_t *smrt; 242 boolean_t check_for_interrupts = B_FALSE; 243 int r; 244 char taskq_name[64]; 245 246 if (scsi_hba_iport_unit_address(dip) != NULL) 247 return (smrt_iport_attach(dip, cmd)); 248 249 if (cmd != DDI_ATTACH) { 250 return (DDI_FAILURE); 251 } 252 253 /* 254 * Allocate the per-controller soft state object and get 255 * a pointer to it. 256 */ 257 instance = ddi_get_instance(dip); 258 if (ddi_soft_state_zalloc(smrt_state, instance) != DDI_SUCCESS) { 259 dev_err(dip, CE_WARN, "could not allocate soft state"); 260 return (DDI_FAILURE); 261 } 262 if ((smrt = ddi_get_soft_state(smrt_state, instance)) == NULL) { 263 dev_err(dip, CE_WARN, "could not get soft state"); 264 ddi_soft_state_free(smrt_state, instance); 265 return (DDI_FAILURE); 266 } 267 268 /* 269 * Initialise per-controller state object. 270 */ 271 smrt->smrt_dip = dip; 272 smrt->smrt_instance = instance; 273 smrt->smrt_next_tag = SMRT_MIN_TAG_NUMBER; 274 list_create(&smrt->smrt_commands, sizeof (smrt_command_t), 275 offsetof(smrt_command_t, smcm_link)); 276 list_create(&smrt->smrt_finishq, sizeof (smrt_command_t), 277 offsetof(smrt_command_t, smcm_link_finish)); 278 list_create(&smrt->smrt_abortq, sizeof (smrt_command_t), 279 offsetof(smrt_command_t, smcm_link_abort)); 280 list_create(&smrt->smrt_volumes, sizeof (smrt_volume_t), 281 offsetof(smrt_volume_t, smlv_link)); 282 list_create(&smrt->smrt_physicals, sizeof (smrt_physical_t), 283 offsetof(smrt_physical_t, smpt_link)); 284 list_create(&smrt->smrt_targets, sizeof (smrt_target_t), 285 offsetof(smrt_target_t, smtg_link_ctlr)); 286 avl_create(&smrt->smrt_inflight, smrt_command_comparator, 287 sizeof (smrt_command_t), offsetof(smrt_command_t, 288 smcm_node)); 289 cv_init(&smrt->smrt_cv_finishq, NULL, CV_DRIVER, NULL); 290 291 smrt->smrt_init_level |= SMRT_INITLEVEL_BASIC; 292 293 /* 294 * Perform basic device setup, including identifying the board, mapping 295 * the I2O registers and the Configuration Table. 296 */ 297 if (smrt_device_setup(smrt) != DDI_SUCCESS) { 298 dev_err(dip, CE_WARN, "device setup failed"); 299 goto fail; 300 } 301 302 /* 303 * Select a Transport Method (e.g. Simple or Performant) and update 304 * the Configuration Table. This function also waits for the 305 * controller to become ready. 306 */ 307 if (smrt_ctlr_init(smrt) != DDI_SUCCESS) { 308 dev_err(dip, CE_WARN, "controller initialisation failed"); 309 goto fail; 310 } 311 312 /* 313 * Each controller may have a different Scatter/Gather Element count. 314 * Configure a per-controller set of DMA attributes with the 315 * appropriate S/G size. 316 */ 317 VERIFY(smrt->smrt_sg_cnt > 0); 318 smrt->smrt_dma_attr = smrt_dma_attr_template; 319 smrt->smrt_dma_attr.dma_attr_sgllen = smrt->smrt_sg_cnt; 320 321 /* 322 * Now that we have selected a Transport Method, we can configure 323 * the appropriate interrupt handlers. 324 */ 325 if (smrt_interrupts_setup(smrt) != DDI_SUCCESS) { 326 dev_err(dip, CE_WARN, "interrupt handler setup failed"); 327 goto fail; 328 } 329 330 /* 331 * Now that we have the correct interrupt priority, we can initialise 332 * the mutex. This must be done before the interrupt handler is 333 * enabled. 334 */ 335 mutex_init(&smrt->smrt_mutex, NULL, MUTEX_DRIVER, 336 DDI_INTR_PRI(smrt->smrt_interrupt_pri)); 337 smrt->smrt_init_level |= SMRT_INITLEVEL_MUTEX; 338 339 /* 340 * From this point forward, the controller is able to accept commands 341 * and (at least by polling) return command submissions. Setting this 342 * flag allows the rest of the driver to interact with the device. 343 */ 344 smrt->smrt_status |= SMRT_CTLR_STATUS_RUNNING; 345 346 if (smrt_interrupts_enable(smrt) != DDI_SUCCESS) { 347 dev_err(dip, CE_WARN, "interrupt handler could not be enabled"); 348 goto fail; 349 } 350 351 if (smrt_ctrl_hba_setup(smrt) != DDI_SUCCESS) { 352 dev_err(dip, CE_WARN, "SCSI framework setup failed"); 353 goto fail; 354 } 355 356 /* 357 * Set the appropriate Interrupt Mask Register bits to start 358 * command completion interrupts from the controller. 359 */ 360 smrt_intr_set(smrt, B_TRUE); 361 check_for_interrupts = B_TRUE; 362 363 /* 364 * Register the maintenance routine for periodic execution: 365 */ 366 smrt->smrt_periodic = ddi_periodic_add(smrt_periodic, smrt, 367 SMRT_PERIODIC_RATE * NANOSEC, DDI_IPL_0); 368 smrt->smrt_init_level |= SMRT_INITLEVEL_PERIODIC; 369 370 (void) snprintf(taskq_name, sizeof (taskq_name), "smrt_discover_%u", 371 instance); 372 smrt->smrt_discover_taskq = ddi_taskq_create(smrt->smrt_dip, taskq_name, 373 1, TASKQ_DEFAULTPRI, 0); 374 if (smrt->smrt_discover_taskq == NULL) { 375 dev_err(dip, CE_WARN, "failed to create discovery task queue"); 376 goto fail; 377 } 378 smrt->smrt_init_level |= SMRT_INITLEVEL_TASKQ; 379 380 if ((r = smrt_event_init(smrt)) != 0) { 381 dev_err(dip, CE_WARN, "could not initialize event subsystem " 382 "(%d)", r); 383 goto fail; 384 } 385 smrt->smrt_init_level |= SMRT_INITLEVEL_ASYNC_EVENT; 386 387 if (scsi_hba_iport_register(dip, SMRT_IPORT_VIRT) != DDI_SUCCESS) 388 goto fail; 389 390 if (scsi_hba_iport_register(dip, SMRT_IPORT_PHYS) != DDI_SUCCESS) 391 goto fail; 392 393 /* 394 * Announce the attachment of this controller. 395 */ 396 ddi_report_dev(dip); 397 398 return (DDI_SUCCESS); 399 400 fail: 401 if (check_for_interrupts) { 402 if (smrt->smrt_stats.smrts_claimed_interrupts == 0) { 403 dev_err(dip, CE_WARN, "controller did not interrupt " 404 "during attach"); 405 } 406 } 407 smrt_cleanup(smrt); 408 return (DDI_FAILURE); 409 } 410 411 static int 412 smrt_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) 413 { 414 scsi_hba_tran_t *tran = (scsi_hba_tran_t *)ddi_get_driver_private(dip); 415 smrt_t *smrt = (smrt_t *)tran->tran_hba_private; 416 417 if (scsi_hba_iport_unit_address(dip) != NULL) 418 return (smrt_iport_detach(dip, cmd)); 419 420 if (cmd != DDI_DETACH) { 421 return (DDI_FAILURE); 422 } 423 424 /* 425 * First, check to make sure that all SCSI framework targets have 426 * detached. 427 */ 428 mutex_enter(&smrt->smrt_mutex); 429 if (!list_is_empty(&smrt->smrt_targets)) { 430 mutex_exit(&smrt->smrt_mutex); 431 dev_err(smrt->smrt_dip, CE_WARN, "cannot detach; targets still " 432 "using HBA"); 433 return (DDI_FAILURE); 434 } 435 436 if (smrt->smrt_virt_iport != NULL || smrt->smrt_phys_iport != NULL) { 437 mutex_exit(&smrt->smrt_mutex); 438 dev_err(smrt->smrt_dip, CE_WARN, "cannot detach: iports still " 439 "attached"); 440 return (DDI_FAILURE); 441 } 442 443 /* 444 * Prevent new targets from attaching now: 445 */ 446 smrt->smrt_status |= SMRT_CTLR_STATUS_DETACHING; 447 mutex_exit(&smrt->smrt_mutex); 448 449 /* 450 * Clean up all remaining resources. 451 */ 452 smrt_cleanup(smrt); 453 454 return (DDI_SUCCESS); 455 } 456 457 static int 458 smrt_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp, 459 int *rval) 460 { 461 int inst = MINOR2INST(getminor(dev)); 462 int status; 463 464 if (secpolicy_sys_config(credp, B_FALSE) != 0) { 465 return (EPERM); 466 } 467 468 /* 469 * Ensure that we have a soft state object for this instance. 470 */ 471 if (ddi_get_soft_state(smrt_state, inst) == NULL) { 472 return (ENXIO); 473 } 474 475 switch (cmd) { 476 default: 477 status = scsi_hba_ioctl(dev, cmd, arg, mode, credp, rval); 478 break; 479 } 480 481 return (status); 482 } 483 484 static void 485 smrt_cleanup(smrt_t *smrt) 486 { 487 if (smrt->smrt_init_level & SMRT_INITLEVEL_ASYNC_EVENT) { 488 smrt_event_fini(smrt); 489 smrt->smrt_init_level &= ~SMRT_INITLEVEL_ASYNC_EVENT; 490 } 491 492 smrt_interrupts_teardown(smrt); 493 494 if (smrt->smrt_init_level & SMRT_INITLEVEL_TASKQ) { 495 ddi_taskq_destroy(smrt->smrt_discover_taskq); 496 smrt->smrt_discover_taskq = NULL; 497 smrt->smrt_init_level &= ~SMRT_INITLEVEL_TASKQ; 498 } 499 500 if (smrt->smrt_init_level & SMRT_INITLEVEL_PERIODIC) { 501 ddi_periodic_delete(smrt->smrt_periodic); 502 smrt->smrt_init_level &= ~SMRT_INITLEVEL_PERIODIC; 503 } 504 505 smrt_ctrl_hba_teardown(smrt); 506 507 smrt_ctlr_teardown(smrt); 508 509 smrt_device_teardown(smrt); 510 511 if (smrt->smrt_init_level & SMRT_INITLEVEL_BASIC) { 512 smrt_logvol_teardown(smrt); 513 smrt_phys_teardown(smrt); 514 515 cv_destroy(&smrt->smrt_cv_finishq); 516 517 VERIFY(list_is_empty(&smrt->smrt_commands)); 518 list_destroy(&smrt->smrt_commands); 519 list_destroy(&smrt->smrt_finishq); 520 list_destroy(&smrt->smrt_abortq); 521 522 VERIFY(list_is_empty(&smrt->smrt_volumes)); 523 list_destroy(&smrt->smrt_volumes); 524 525 VERIFY(list_is_empty(&smrt->smrt_physicals)); 526 list_destroy(&smrt->smrt_physicals); 527 528 VERIFY(list_is_empty(&smrt->smrt_targets)); 529 list_destroy(&smrt->smrt_targets); 530 531 VERIFY(avl_is_empty(&smrt->smrt_inflight)); 532 avl_destroy(&smrt->smrt_inflight); 533 534 smrt->smrt_init_level &= ~SMRT_INITLEVEL_BASIC; 535 } 536 537 if (smrt->smrt_init_level & SMRT_INITLEVEL_MUTEX) { 538 mutex_destroy(&smrt->smrt_mutex); 539 540 smrt->smrt_init_level &= ~SMRT_INITLEVEL_MUTEX; 541 } 542 543 VERIFY0(smrt->smrt_init_level); 544 545 ddi_soft_state_free(smrt_state, ddi_get_instance(smrt->smrt_dip)); 546 } 547 548 /* 549 * Comparator for the "smrt_inflight" AVL tree in a "smrt_t". This AVL tree 550 * allows a tag ID to be mapped back to the relevant "smrt_command_t". 551 */ 552 static int 553 smrt_command_comparator(const void *lp, const void *rp) 554 { 555 const smrt_command_t *l = lp; 556 const smrt_command_t *r = rp; 557 558 if (l->smcm_tag > r->smcm_tag) { 559 return (1); 560 } else if (l->smcm_tag < r->smcm_tag) { 561 return (-1); 562 } else { 563 return (0); 564 } 565 } 566