xref: /illumos-gate/usr/src/uts/common/io/scsi/adapters/lmrc/lmrc_ioctl.c (revision 9726429325e0e93eb1a1f724f6db6a9fd6ad63ce)
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 2023 Racktop Systems, Inc.
14  */
15 
16 /*
17  * This file implements the ioctl interface as employed by closed-source
18  * the closed-source RAID management utility storcli. As there is no source
19  * and no documentation, this closely follows the ioctl implementation of
20  * the existing mr_sas(4D) driver for older MegaRAID HBAs.
21  *
22  * This driver supports three kinds of ioctls:
23  * - SCSA HBA ioctls, which are handled by scsi_hba_ioctl()
24  * - AEN ioctls, which currently have no known consumer as it seems storcli
25  *   doesn't use them. They are left unimplemented for now, logging a warning
26  *   if used.
27  * - Firmware ioctls as used by storcli, which can be divided into two kinds
28  *   - MFI passthru ioctls which are used to send MFI frames containing DCMDs,
29  *     LD SCSI I/O, or PD SCSI I/O requests from userspace directly to the HBA.
30  *     See the comment at the beginning of lmrc.c for a description of the MFI.
31  *   - Driver ioctls, which look like MFI DCMD frames but are actually handled
32  *     by the driver. They are used by storcli to query the driver version and
33  *     get PCI information of the HBA, including PCI config space header.
34  */
35 #include <sys/cred.h>
36 #include <sys/file.h>
37 #include <sys/types.h>
38 #include <sys/errno.h>
39 #include <sys/ddi.h>
40 #include <sys/sunddi.h>
41 #include <sys/policy.h>
42 
43 #include <sys/ddifm.h>
44 #include <sys/fm/io/ddi.h>
45 
46 #include "lmrc.h"
47 #include "lmrc_reg.h"
48 #include "lmrc_raid.h"
49 #include "lmrc_ioctl.h"
50 
51 static int lmrc_drv_ioctl_drv_version(lmrc_t *, void *, size_t, int);
52 static int lmrc_drv_ioctl_pci_info(lmrc_t *, void *, size_t, int);
53 static int lmrc_drv_ioctl(lmrc_t *, lmrc_ioctl_t *, int);
54 
55 static void lmrc_mfi_ioctl_scsi_io(lmrc_t *, lmrc_ioctl_t *, lmrc_mfi_cmd_t *,
56     uintptr_t *, uintptr_t *);
57 static void lmrc_mfi_ioctl_dcmd(lmrc_t *, lmrc_ioctl_t *, lmrc_mfi_cmd_t *,
58     uintptr_t *);
59 static int lmrc_mfi_ioctl(lmrc_t *, lmrc_ioctl_t *, int);
60 static int lmrc_mfi_aen_ioctl(lmrc_t *, lmrc_aen_t *);
61 static int lmrc_fw_ioctl(lmrc_t *, intptr_t, int);
62 static int lmrc_aen_ioctl(lmrc_t *, intptr_t, int);
63 
64 /*
65  * lmrc_drv_ioctl_drv_version
66  *
67  * Return the driver version information back to userspace.
68  */
69 static int
70 lmrc_drv_ioctl_drv_version(lmrc_t *lmrc, void *ubuf, size_t len, int mode)
71 {
72 	static lmrc_drv_ver_t dv = {
73 		.dv_signature = "$ILLUMOS$",
74 		.dv_os_name = "illumos",
75 		.dv_drv_name = "lmrc",
76 		.dv_drv_ver = "0.1",
77 		.dv_drv_rel_date = "Feb 09, 2023"
78 	};
79 
80 	int ret;
81 
82 	ret = ddi_copyout(&dv, ubuf, len, mode);
83 	if (ret != DDI_SUCCESS)
84 		return (EFAULT);
85 
86 	return (0);
87 }
88 
89 /*
90  * lmrc_drv_ioctl_drv_version
91  *
92  * Return PCI bus interface information back to userspace.
93  */
94 static int
95 lmrc_drv_ioctl_pci_info(lmrc_t *lmrc, void *ubuf, size_t len, int mode)
96 {
97 	int *props = NULL;
98 	ddi_acc_handle_t pcih;
99 	lmrc_pci_info_t pi;
100 	uint_t nprop;
101 	int ret;
102 	int i;
103 
104 	ret = ddi_prop_lookup_int_array(DDI_DEV_T_ANY, lmrc->l_dip, 0, "reg",
105 	    &props, &nprop);
106 	if (ret != DDI_SUCCESS)
107 		return (EINVAL);
108 
109 	bzero(&pi, sizeof (pi));
110 	pi.pi_bus = (props[0] >> 16) & 0xff;
111 	pi.pi_dev = (props[0] >> 11) & 0x1f;
112 	pi.pi_func = (props[0] >> 8) & 0x7;
113 
114 	ddi_prop_free(props);
115 
116 	if (pci_config_setup(lmrc->l_dip, &pcih) != DDI_SUCCESS)
117 		return (EINVAL);
118 
119 	for (i = 0; i != ARRAY_SIZE(pi.pi_header); i++)
120 		pi.pi_header[i] = pci_config_get8(pcih, i);
121 
122 	if (lmrc_check_acc_handle(lmrc->l_reghandle) != DDI_SUCCESS) {
123 		pci_config_teardown(&pcih);
124 		lmrc_fm_ereport(lmrc, DDI_FM_DEVICE_NO_RESPONSE);
125 		ddi_fm_service_impact(lmrc->l_dip, DDI_SERVICE_LOST);
126 		return (EIO);
127 	}
128 
129 	pci_config_teardown(&pcih);
130 
131 	ret = ddi_copyout(&pi, ubuf, len, mode);
132 	if (ret != DDI_SUCCESS)
133 		return (EFAULT);
134 
135 	return (0);
136 }
137 
138 /*
139  * lmrc_drv_ioctl
140  *
141  * Process a driver information ioctl request. These come in the form of a
142  * MFI DCMD but are processed by the driver and not sent to the hardware.
143  */
144 static int
145 lmrc_drv_ioctl(lmrc_t *lmrc, lmrc_ioctl_t *ioc, int mode)
146 {
147 	lmrc_mfi_header_t *hdr = &ioc->ioc_frame.mf_hdr;
148 	lmrc_mfi_dcmd_payload_t *dcmd = &ioc->ioc_frame.mf_dcmd;
149 	size_t xferlen = dcmd->md_sgl.ms64_length;
150 	void *ubuf = (void *)dcmd->md_sgl.ms64_phys_addr;
151 	int ret = EINVAL;
152 
153 #ifdef _MULTI_DATAMODEL
154 	if (ddi_model_convert_from(mode & FMODELS) == DDI_MODEL_ILP32) {
155 		xferlen = dcmd->md_sgl.ms32_length;
156 		ubuf = (void *)(uintptr_t)dcmd->md_sgl.ms32_phys_addr;
157 	} else {
158 #endif
159 		xferlen = dcmd->md_sgl.ms64_length;
160 		ubuf = (void *)(uintptr_t)dcmd->md_sgl.ms64_phys_addr;
161 #ifdef _MULTI_DATAMODEL
162 	}
163 #endif
164 
165 	switch (dcmd->md_opcode) {
166 	case LMRC_DRIVER_IOCTL_DRIVER_VERSION:
167 		ret = lmrc_drv_ioctl_drv_version(lmrc, ubuf, xferlen, mode);
168 		break;
169 
170 	case LMRC_DRIVER_IOCTL_PCI_INFORMATION:
171 		ret = lmrc_drv_ioctl_pci_info(lmrc, ubuf, xferlen, mode);
172 		break;
173 
174 	default:
175 		dev_err(lmrc->l_dip, CE_WARN,
176 		    "!%s: invalid driver ioctl, cmd = %d",
177 		    __func__, dcmd->md_opcode);
178 
179 		ret = EINVAL;
180 		break;
181 	}
182 
183 	if (ret != 0)
184 		hdr->mh_cmd_status = MFI_STAT_INVALID_CMD;
185 	else
186 		hdr->mh_cmd_status = MFI_STAT_OK;
187 
188 	return (ret);
189 }
190 
191 /*
192  * lmrc_mfi_ioctl_scsi_io
193  *
194  * Prepare MFI cmd for SCSI I/O passthru.
195  */
196 static void
197 lmrc_mfi_ioctl_scsi_io(lmrc_t *lmrc, lmrc_ioctl_t *ioc,
198     lmrc_mfi_cmd_t *mfi, uintptr_t *sgloff, uintptr_t *senseoff)
199 {
200 	lmrc_mfi_pthru_payload_t *ioc_pthru = &ioc->ioc_frame.mf_pthru;
201 	lmrc_mfi_pthru_payload_t *mfi_pthru = &mfi->mfi_frame->mf_pthru;
202 
203 	bcopy(ioc_pthru->mp_cdb, mfi_pthru->mp_cdb, sizeof (mfi_pthru->mp_cdb));
204 
205 	*sgloff = offsetof(lmrc_mfi_pthru_payload_t, mp_sgl);
206 	*senseoff = offsetof(lmrc_mfi_pthru_payload_t, mp_sense_buf_phys_addr);
207 }
208 
209 /*
210  * lmrc_mfi_ioctl_dcmd
211  *
212  * Prepare MFI cmd for DMCD passthru.
213  */
214 static void
215 lmrc_mfi_ioctl_dcmd(lmrc_t *lmrc, lmrc_ioctl_t *ioc,
216     lmrc_mfi_cmd_t *mfi, uintptr_t *sgloff)
217 {
218 	lmrc_mfi_dcmd_payload_t *ioc_dcmd = &ioc->ioc_frame.mf_dcmd;
219 	lmrc_mfi_dcmd_payload_t *mfi_dcmd = &mfi->mfi_frame->mf_dcmd;
220 
221 	mfi_dcmd->md_opcode = ioc_dcmd->md_opcode;
222 	bcopy(ioc_dcmd->md_mbox_8, mfi_dcmd->md_mbox_8,
223 	    sizeof (mfi_dcmd->md_mbox_8));
224 
225 	*sgloff = offsetof(lmrc_mfi_dcmd_payload_t, md_sgl);
226 }
227 
228 /*
229  * lmrc_mfi_ioctl
230  *
231  * Process a MFI passthru ioctl request. Handle DMA read/write and sense data
232  * in a uniform way for all supported MFI commands.
233  */
234 static int
235 lmrc_mfi_ioctl(lmrc_t *lmrc, lmrc_ioctl_t *ioc, int mode)
236 {
237 	uint64_t *mfi_senseaddr = NULL, *ioc_senseaddr = NULL;
238 	lmrc_dma_t sense;
239 	size_t xferlen = 0;
240 
241 	lmrc_mfi_header_t *mfi_hdr, *ioc_hdr;
242 	lmrc_mfi_sgl_t *mfi_sgl, *ioc_sgl;
243 	lmrc_mfi_cmd_t *mfi;
244 	uintptr_t sgloff;
245 	void *xferbuf;
246 	int ret;
247 
248 	ioc_hdr = &ioc->ioc_frame.mf_hdr;
249 	if (ioc_hdr->mh_sense_len > LMRC_IOC_SENSE_LEN)
250 		return (EINVAL);
251 
252 	mfi = lmrc_get_mfi(lmrc);
253 	mfi_hdr = &mfi->mfi_frame->mf_hdr;
254 
255 	mfi_hdr->mh_cmd = ioc_hdr->mh_cmd;
256 	mfi_hdr->mh_sense_len = ioc_hdr->mh_sense_len;
257 	mfi_hdr->mh_drv_opts = ioc_hdr->mh_drv_opts;
258 	mfi_hdr->mh_flags = ioc_hdr->mh_flags & ~MFI_FRAME_SGL64;
259 	mfi_hdr->mh_timeout = ioc_hdr->mh_timeout;
260 	mfi_hdr->mh_data_xfer_len = ioc_hdr->mh_data_xfer_len;
261 
262 	switch (mfi_hdr->mh_cmd) {
263 	case MFI_CMD_LD_SCSI_IO:
264 	case MFI_CMD_PD_SCSI_IO: {
265 		uintptr_t senseoff;
266 
267 		lmrc_mfi_ioctl_scsi_io(lmrc, ioc, mfi, &sgloff, &senseoff);
268 
269 		mfi_senseaddr = (uint64_t *)&mfi->mfi_frame->mf_raw[senseoff];
270 		ioc_senseaddr = (uint64_t *)&ioc->ioc_frame.mf_raw[senseoff];
271 
272 		break;
273 	}
274 	case MFI_CMD_DCMD:
275 		if (mfi_hdr->mh_sense_len != 0) {
276 			ret = EINVAL;
277 			goto out;
278 		}
279 
280 		lmrc_mfi_ioctl_dcmd(lmrc, ioc, mfi, &sgloff);
281 		break;
282 
283 	default:
284 		dev_err(lmrc->l_dip, CE_WARN,
285 		    "!%s: invalid MFI ioctl, cmd = %d",
286 		    __func__, mfi_hdr->mh_cmd);
287 		ret = EINVAL;
288 		goto out;
289 
290 	}
291 
292 	ASSERT3U(sgloff, !=, 0);
293 	ioc_sgl = (lmrc_mfi_sgl_t *)&ioc->ioc_frame.mf_raw[sgloff];
294 	mfi_sgl = (lmrc_mfi_sgl_t *)&mfi->mfi_frame->mf_raw[sgloff];
295 
296 #ifdef _MULTI_DATAMODEL
297 	if (ddi_model_convert_from(mode & FMODELS) == DDI_MODEL_ILP32) {
298 		xferlen = ioc_sgl->ms32_length;
299 		xferbuf = (void *)(uintptr_t)ioc_sgl->ms32_phys_addr;
300 	} else {
301 #endif
302 		xferlen = ioc_sgl->ms64_length;
303 		xferbuf = (void *)(uintptr_t)ioc_sgl->ms64_phys_addr;
304 #ifdef _MULTI_DATAMODEL
305 	}
306 #endif
307 
308 	if (xferlen != 0) {
309 		/* This ioctl uses DMA. */
310 		ret = lmrc_dma_alloc(lmrc, lmrc->l_dma_attr,
311 		    &mfi->mfi_data_dma, xferlen, 1, DDI_DMA_CONSISTENT);
312 		if (ret != DDI_SUCCESS) {
313 			ret = EINVAL;
314 			goto out;
315 		}
316 
317 		/* If this ioctl does a DMA write, copy in the user buffer. */
318 		if ((mfi_hdr->mh_flags & MFI_FRAME_DIR_WRITE) != 0) {
319 			ret = ddi_copyin(xferbuf, mfi->mfi_data_dma.ld_buf,
320 			    xferlen, mode);
321 			if (ret != DDI_SUCCESS) {
322 				ret = EFAULT;
323 				goto out;
324 			}
325 		}
326 
327 		mfi_hdr->mh_flags |= MFI_FRAME_SGL64;
328 
329 		lmrc_dma_set_addr64(&mfi->mfi_data_dma,
330 		    &mfi_sgl->ms64_phys_addr);
331 		mfi_sgl->ms64_length = lmrc_dma_get_size(&mfi->mfi_data_dma);
332 	}
333 
334 	if (mfi_hdr->mh_sense_len != 0) {
335 		/* This ioctl needs a sense buffer. */
336 		ret = lmrc_dma_alloc(lmrc, lmrc->l_dma_attr, &sense,
337 		    mfi_hdr->mh_sense_len, 1, DDI_DMA_CONSISTENT);
338 		if (ret != DDI_SUCCESS) {
339 			ret = EINVAL;
340 			goto out;
341 		}
342 
343 		lmrc_dma_set_addr64(&sense, mfi_senseaddr);
344 	}
345 
346 	mutex_enter(&mfi->mfi_lock);
347 	lmrc_issue_mfi(lmrc, mfi, lmrc_wakeup_mfi);
348 	ret = lmrc_wait_mfi(lmrc, mfi, LMRC_INTERNAL_CMD_WAIT_TIME);
349 	mutex_exit(&mfi->mfi_lock);
350 
351 	if (ret != DDI_SUCCESS) {
352 		ret = EAGAIN;
353 		goto out;
354 	}
355 
356 	/* If this ioctl did a DMA read, copy out to the user buffer. */
357 	if (xferlen != 0 && (mfi_hdr->mh_flags & MFI_FRAME_DIR_READ) != 0) {
358 		ret = ddi_copyout(mfi->mfi_data_dma.ld_buf, xferbuf, xferlen,
359 		    mode);
360 		if (ret != DDI_SUCCESS) {
361 			ret = EFAULT;
362 			goto out;
363 		}
364 	}
365 
366 	/* If there is sense data, copy out to the user sense buffer. */
367 	if (mfi_hdr->mh_sense_len != 0) {
368 		void *sensebuf = (void *)(uintptr_t)*ioc_senseaddr;
369 
370 		(void) ddi_dma_sync(sense.ld_hdl, 0, sense.ld_len,
371 		    DDI_DMA_SYNC_FORKERNEL);
372 		ret = ddi_copyout(sense.ld_buf, sensebuf, sense.ld_len, mode);
373 		if (ret != DDI_SUCCESS) {
374 			ret = EFAULT;
375 			goto out;
376 		}
377 	}
378 
379 out:
380 	ioc_hdr->mh_cmd_status = mfi_hdr->mh_cmd_status;
381 	ioc_hdr->mh_scsi_status = mfi_hdr->mh_scsi_status;
382 
383 	if (xferlen != 0)
384 		lmrc_dma_free(&mfi->mfi_data_dma);
385 
386 	if (mfi_hdr->mh_sense_len != 0)
387 		lmrc_dma_free(&sense);
388 
389 	lmrc_put_mfi(mfi);
390 	if (ret != 0)
391 		dev_err(lmrc->l_dip, CE_WARN,
392 		    "%s: failing MFI ioctl, ret = %d",
393 		    __func__, ret);
394 	return (ret);
395 }
396 
397 /*
398  * lmrc_fw_ioctl
399  *
400  * Process a firmware ioctl request. This includes driver ioctls (which are
401  * actually handled by the driver) and MFI passthru ioctls.
402  */
403 static int
404 lmrc_fw_ioctl(lmrc_t *lmrc, intptr_t arg, int mode)
405 {
406 	lmrc_ioctl_t *ioc;
407 	int ret = EINVAL;
408 
409 	ioc = kmem_zalloc(sizeof (lmrc_ioctl_t), KM_SLEEP);
410 	if (ddi_copyin((void *)arg, ioc, sizeof (*ioc), mode) != 0) {
411 		ret = EFAULT;
412 		goto out;
413 	}
414 
415 	if (ioc->ioc_control_code == LMRC_DRIVER_IOCTL_COMMON) {
416 		ret = lmrc_drv_ioctl(lmrc, ioc, mode);
417 	} else {
418 		sema_p(&lmrc->l_ioctl_sema);
419 		ret = lmrc_mfi_ioctl(lmrc, ioc, mode);
420 		sema_v(&lmrc->l_ioctl_sema);
421 	}
422 
423 	if (ddi_copyout(ioc, (void *)arg, sizeof (*ioc) - 1, mode) != 0) {
424 		ret = EFAULT;
425 		goto out;
426 	}
427 
428 out:
429 	kmem_free(ioc, sizeof (lmrc_ioctl_t));
430 	return (ret);
431 }
432 
433 /*
434  * lmrc_mfi_aen_ioctl
435  *
436  * Supposedly, this will one day send an AEN to the firmware on behalf of
437  * user space.
438  */
439 static int
440 lmrc_mfi_aen_ioctl(lmrc_t *lmrc, lmrc_aen_t *aen)
441 {
442 	dev_err(lmrc->l_dip, CE_WARN, "!unimplemented ioctl: MFI AEN");
443 	return (EINVAL);
444 }
445 
446 /*
447  * lmrc_aen_ioctl
448  *
449  * Process a AEN ioctl request.
450  */
451 static int
452 lmrc_aen_ioctl(lmrc_t *lmrc, intptr_t arg, int mode)
453 {
454 	int ret = EINVAL;
455 	lmrc_aen_t	aen;
456 
457 	if (ddi_copyin((void *)arg, &aen, sizeof (aen), mode) != 0)
458 		return (EFAULT);
459 
460 	ret = lmrc_mfi_aen_ioctl(lmrc, &aen);
461 	if (ret != 0)
462 		goto out;
463 
464 	if (ddi_copyout(&aen, (void *)arg, sizeof (aen), mode) != 0)
465 		return (EFAULT);
466 out:
467 	return (ret);
468 }
469 
470 /*
471  * DDI ioctl(9e) entry point.
472  *
473  * Get the ioctl cmd and call the appropriate handlers.
474  */
475 int
476 lmrc_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp,
477     int *rval)
478 {
479 	lmrc_t *lmrc;
480 	int inst = MINOR2INST(getminor(dev));
481 	int ret;
482 
483 	if (secpolicy_sys_config(credp, B_FALSE) != 0)
484 		return (EPERM);
485 
486 	ret = scsi_hba_ioctl(dev, cmd, arg, mode, credp, rval);
487 	if (ret != ENOTTY)
488 		return (ret);
489 
490 	lmrc = ddi_get_soft_state(lmrc_state, inst);
491 	if (lmrc == NULL)
492 		return (ENXIO);
493 
494 	if (lmrc->l_fw_fault)
495 		return (EIO);
496 
497 	switch ((uint_t)cmd) {
498 	case LMRC_IOCTL_FIRMWARE:
499 		ret = lmrc_fw_ioctl(lmrc, arg, mode);
500 		break;
501 
502 	case LMRC_IOCTL_AEN:
503 		ret = lmrc_aen_ioctl(lmrc, arg, mode);
504 		break;
505 
506 	default:
507 		ret = ENOTTY;
508 		break;
509 	}
510 
511 	return (ret);
512 }
513