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
_init()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
_fini()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
_info(struct modinfo * modinfop)163 _info(struct modinfo *modinfop)
164 {
165 return (mod_info(&smrt_modlinkage, modinfop));
166 }
167
168 static int
smrt_iport_attach(dev_info_t * dip,ddi_attach_cmd_t cmd)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
smrt_iport_detach(dev_info_t * dip,ddi_detach_cmd_t cmd)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
smrt_attach(dev_info_t * dip,ddi_attach_cmd_t cmd)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
smrt_detach(dev_info_t * dip,ddi_detach_cmd_t cmd)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
smrt_ioctl(dev_t dev,int cmd,intptr_t arg,int mode,cred_t * credp,int * rval)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
smrt_cleanup(smrt_t * smrt)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
smrt_command_comparator(const void * lp,const void * rp)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