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
lmrc_drv_ioctl_drv_version(lmrc_t * lmrc,void * ubuf,size_t len,int mode)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
lmrc_drv_ioctl_pci_info(lmrc_t * lmrc,void * ubuf,size_t len,int mode)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
lmrc_drv_ioctl(lmrc_t * lmrc,lmrc_ioctl_t * ioc,int mode)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
lmrc_mfi_ioctl_scsi_io(lmrc_t * lmrc,lmrc_ioctl_t * ioc,lmrc_mfi_cmd_t * mfi,uintptr_t * sgloff,uintptr_t * senseoff)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
lmrc_mfi_ioctl_dcmd(lmrc_t * lmrc,lmrc_ioctl_t * ioc,lmrc_mfi_cmd_t * mfi,uintptr_t * sgloff)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
lmrc_mfi_ioctl(lmrc_t * lmrc,lmrc_ioctl_t * ioc,int mode)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 } else {
333 mfi_hdr->mh_flags &= ~MFI_FRAME_DIR_BOTH;
334 }
335
336 if (mfi_hdr->mh_sense_len != 0) {
337 /* This ioctl needs a sense buffer. */
338 ret = lmrc_dma_alloc(lmrc, lmrc->l_dma_attr, &sense,
339 mfi_hdr->mh_sense_len, 1, DDI_DMA_CONSISTENT);
340 if (ret != DDI_SUCCESS) {
341 ret = EINVAL;
342 goto out;
343 }
344
345 lmrc_dma_set_addr64(&sense, mfi_senseaddr);
346 }
347
348 mutex_enter(&mfi->mfi_lock);
349 lmrc_issue_mfi(lmrc, mfi, lmrc_wakeup_mfi);
350 ret = lmrc_wait_mfi(lmrc, mfi, LMRC_INTERNAL_CMD_WAIT_TIME);
351 mutex_exit(&mfi->mfi_lock);
352
353 if (ret != DDI_SUCCESS) {
354 ret = EAGAIN;
355 goto out;
356 }
357
358 /* If this ioctl did a DMA read, copy out to the user buffer. */
359 if (xferlen != 0 && (mfi_hdr->mh_flags & MFI_FRAME_DIR_READ) != 0) {
360 ret = ddi_copyout(mfi->mfi_data_dma.ld_buf, xferbuf, xferlen,
361 mode);
362 if (ret != DDI_SUCCESS) {
363 ret = EFAULT;
364 goto out;
365 }
366 }
367
368 /* If there is sense data, copy out to the user sense buffer. */
369 if (mfi_hdr->mh_sense_len != 0) {
370 void *sensebuf = (void *)(uintptr_t)*ioc_senseaddr;
371
372 (void) ddi_dma_sync(sense.ld_hdl, 0, sense.ld_len,
373 DDI_DMA_SYNC_FORKERNEL);
374 ret = ddi_copyout(sense.ld_buf, sensebuf, sense.ld_len, mode);
375 if (ret != DDI_SUCCESS) {
376 ret = EFAULT;
377 goto out;
378 }
379 }
380
381 out:
382 ioc_hdr->mh_cmd_status = mfi_hdr->mh_cmd_status;
383 ioc_hdr->mh_scsi_status = mfi_hdr->mh_scsi_status;
384
385 if (xferlen != 0)
386 lmrc_dma_free(&mfi->mfi_data_dma);
387
388 if (mfi_hdr->mh_sense_len != 0)
389 lmrc_dma_free(&sense);
390
391 lmrc_put_mfi(mfi);
392 if (ret != 0)
393 dev_err(lmrc->l_dip, CE_WARN,
394 "%s: failing MFI ioctl, ret = %d",
395 __func__, ret);
396 return (ret);
397 }
398
399 /*
400 * lmrc_fw_ioctl
401 *
402 * Process a firmware ioctl request. This includes driver ioctls (which are
403 * actually handled by the driver) and MFI passthru ioctls.
404 */
405 static int
lmrc_fw_ioctl(lmrc_t * lmrc,intptr_t arg,int mode)406 lmrc_fw_ioctl(lmrc_t *lmrc, intptr_t arg, int mode)
407 {
408 lmrc_ioctl_t *ioc;
409 int ret = EINVAL;
410
411 ioc = kmem_zalloc(sizeof (lmrc_ioctl_t), KM_SLEEP);
412 if (ddi_copyin((void *)arg, ioc, sizeof (*ioc), mode) != 0) {
413 ret = EFAULT;
414 goto out;
415 }
416
417 if (ioc->ioc_control_code == LMRC_DRIVER_IOCTL_COMMON) {
418 ret = lmrc_drv_ioctl(lmrc, ioc, mode);
419 } else {
420 sema_p(&lmrc->l_ioctl_sema);
421 ret = lmrc_mfi_ioctl(lmrc, ioc, mode);
422 sema_v(&lmrc->l_ioctl_sema);
423 }
424
425 if (ddi_copyout(ioc, (void *)arg, sizeof (*ioc) - 1, mode) != 0) {
426 ret = EFAULT;
427 goto out;
428 }
429
430 out:
431 kmem_free(ioc, sizeof (lmrc_ioctl_t));
432 return (ret);
433 }
434
435 /*
436 * lmrc_mfi_aen_ioctl
437 *
438 * Supposedly, this will one day send an AEN to the firmware on behalf of
439 * user space.
440 */
441 static int
lmrc_mfi_aen_ioctl(lmrc_t * lmrc,lmrc_aen_t * aen)442 lmrc_mfi_aen_ioctl(lmrc_t *lmrc, lmrc_aen_t *aen)
443 {
444 dev_err(lmrc->l_dip, CE_WARN, "!unimplemented ioctl: MFI AEN");
445 return (EINVAL);
446 }
447
448 /*
449 * lmrc_aen_ioctl
450 *
451 * Process a AEN ioctl request.
452 */
453 static int
lmrc_aen_ioctl(lmrc_t * lmrc,intptr_t arg,int mode)454 lmrc_aen_ioctl(lmrc_t *lmrc, intptr_t arg, int mode)
455 {
456 int ret = EINVAL;
457 lmrc_aen_t aen;
458
459 if (ddi_copyin((void *)arg, &aen, sizeof (aen), mode) != 0)
460 return (EFAULT);
461
462 ret = lmrc_mfi_aen_ioctl(lmrc, &aen);
463 if (ret != 0)
464 goto out;
465
466 if (ddi_copyout(&aen, (void *)arg, sizeof (aen), mode) != 0)
467 return (EFAULT);
468 out:
469 return (ret);
470 }
471
472 /*
473 * DDI ioctl(9e) entry point.
474 *
475 * Get the ioctl cmd and call the appropriate handlers.
476 */
477 int
lmrc_ioctl(dev_t dev,int cmd,intptr_t arg,int mode,cred_t * credp,int * rval)478 lmrc_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp,
479 int *rval)
480 {
481 lmrc_t *lmrc;
482 int inst = MINOR2INST(getminor(dev));
483 int ret;
484
485 if (secpolicy_sys_config(credp, B_FALSE) != 0)
486 return (EPERM);
487
488 ret = scsi_hba_ioctl(dev, cmd, arg, mode, credp, rval);
489 if (ret != ENOTTY)
490 return (ret);
491
492 lmrc = ddi_get_soft_state(lmrc_state, inst);
493 if (lmrc == NULL)
494 return (ENXIO);
495
496 if (lmrc->l_fw_fault)
497 return (EIO);
498
499 switch ((uint_t)cmd) {
500 case LMRC_IOCTL_FIRMWARE:
501 ret = lmrc_fw_ioctl(lmrc, arg, mode);
502 break;
503
504 case LMRC_IOCTL_AEN:
505 ret = lmrc_aen_ioctl(lmrc, arg, mode);
506 break;
507
508 default:
509 ret = ENOTTY;
510 break;
511 }
512
513 return (ret);
514 }
515