xref: /illumos-gate/usr/src/uts/common/io/scsi/adapters/smrt/smrt.c (revision b8aa3def2e2531e693fba6d1f00a74339a4a663d)
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