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