mps_cmd.c revision 286180
1/*-
2 * Copyright (c) 2008 Yahoo!, Inc.
3 * All rights reserved.
4 * Written by: John Baldwin <jhb@FreeBSD.org>
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 * 3. Neither the name of the author nor the names of any co-contributors
15 *    may be used to endorse or promote products derived from this software
16 *    without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28 * SUCH DAMAGE.
29 */
30
31#include <sys/cdefs.h>
32__RCSID("$FreeBSD: projects/mpsutil/usr.sbin/mpsutil/mps_cmd.c 286180 2015-08-02 03:52:51Z scottl $");
33
34#include <sys/param.h>
35#include <sys/errno.h>
36#include <sys/ioctl.h>
37#if 0
38#include <sys/mps_ioctl.h>
39#else
40#include "mps_ioctl.h"
41#endif
42#include <sys/sysctl.h>
43#include <sys/uio.h>
44
45#include <err.h>
46#include <fcntl.h>
47#include <stdio.h>
48#include <stdlib.h>
49#include <string.h>
50#include <unistd.h>
51
52#include "mpsutil.h"
53
54#ifndef USE_MPT_IOCTLS
55#define USE_MPT_IOCTLS
56#endif
57
58static const char *mps_ioc_status_codes[] = {
59	"Success",				/* 0x0000 */
60	"Invalid function",
61	"Busy",
62	"Invalid scatter-gather list",
63	"Internal error",
64	"Reserved",
65	"Insufficient resources",
66	"Invalid field",
67	"Invalid state",			/* 0x0008 */
68	"Operation state not supported",
69	NULL,
70	NULL,
71	NULL,
72	NULL,
73	NULL,
74	NULL,
75	NULL,					/* 0x0010 */
76	NULL,
77	NULL,
78	NULL,
79	NULL,
80	NULL,
81	NULL,
82	NULL,
83	NULL,					/* 0x0018 */
84	NULL,
85	NULL,
86	NULL,
87	NULL,
88	NULL,
89	NULL,
90	NULL,
91	"Invalid configuration action",		/* 0x0020 */
92	"Invalid configuration type",
93	"Invalid configuration page",
94	"Invalid configuration data",
95	"No configuration defaults",
96	"Unable to commit configuration change",
97	NULL,
98	NULL,
99	NULL,					/* 0x0028 */
100	NULL,
101	NULL,
102	NULL,
103	NULL,
104	NULL,
105	NULL,
106	NULL,
107	NULL,					/* 0x0030 */
108	NULL,
109	NULL,
110	NULL,
111	NULL,
112	NULL,
113	NULL,
114	NULL,
115	NULL,					/* 0x0038 */
116	NULL,
117	NULL,
118	NULL,
119	NULL,
120	NULL,
121	NULL,
122	NULL,
123	"Recovered SCSI error",			/* 0x0040 */
124	"Invalid SCSI bus",
125	"Invalid SCSI target ID",
126	"SCSI device not there",
127	"SCSI data overrun",
128	"SCSI data underrun",
129	"SCSI I/O error",
130	"SCSI protocol error",
131	"SCSI task terminated",			/* 0x0048 */
132	"SCSI residual mismatch",
133	"SCSI task management failed",
134	"SCSI I/O controller terminated",
135	"SCSI external controller terminated",
136	"EEDP guard error",
137	"EEDP reference tag error",
138	"EEDP application tag error",
139	NULL,					/* 0x0050 */
140	NULL,
141	NULL,
142	NULL,
143	NULL,
144	NULL,
145	NULL,
146	NULL,
147	NULL,					/* 0x0058 */
148	NULL,
149	NULL,
150	NULL,
151	NULL,
152	NULL,
153	NULL,
154	NULL,
155	"SCSI target priority I/O",		/* 0x0060 */
156	"Invalid SCSI target port",
157	"Invalid SCSI target I/O index",
158	"SCSI target aborted",
159	"No connection retryable",
160	"No connection",
161	"FC aborted",
162	"Invalid FC receive ID",
163	"FC did invalid",			/* 0x0068 */
164	"FC node logged out",
165	"Transfer count mismatch",
166	"STS data not set",
167	"FC exchange canceled",
168	"Data offset error",
169	"Too much write data",
170	"IU too short",
171	"ACK NAK timeout",			/* 0x0070 */
172	"NAK received",
173	NULL,
174	NULL,
175	NULL,
176	NULL,
177	NULL,
178	NULL,
179	NULL,					/* 0x0078 */
180	NULL,
181	NULL,
182	NULL,
183	NULL,
184	NULL,
185	NULL,
186	NULL,
187	"LAN device not found",			/* 0x0080 */
188	"LAN device failure",
189	"LAN transmit error",
190	"LAN transmit aborted",
191	"LAN receive error",
192	"LAN receive aborted",
193	"LAN partial packet",
194	"LAN canceled",
195	NULL,					/* 0x0088 */
196	NULL,
197	NULL,
198	NULL,
199	NULL,
200	NULL,
201	NULL,
202	NULL,
203	"SAS SMP request failed",		/* 0x0090 */
204	"SAS SMP data overrun",
205	NULL,
206	NULL,
207	NULL,
208	NULL,
209	NULL,
210	NULL,
211	"Inband aborted",			/* 0x0098 */
212	"No inband connection",
213	NULL,
214	NULL,
215	NULL,
216	NULL,
217	NULL,
218	NULL,
219	"Diagnostic released",			/* 0x00A0 */
220};
221
222const char *
223mps_ioc_status(U16 IOCStatus)
224{
225	static char buffer[16];
226
227	IOCStatus &= MPI2_IOCSTATUS_MASK;
228	if (IOCStatus < sizeof(mps_ioc_status_codes) / sizeof(char *) &&
229	    mps_ioc_status_codes[IOCStatus] != NULL)
230		return (mps_ioc_status_codes[IOCStatus]);
231	snprintf(buffer, sizeof(buffer), "Status: 0x%04x", IOCStatus);
232	return (buffer);
233}
234
235#ifdef USE_MPT_IOCTLS
236int
237mps_map_btdh(int fd, uint16_t *devhandle, uint16_t *bus, uint16_t *target)
238{
239	int error;
240	struct mps_btdh_mapping map;
241
242	bzero(&map, sizeof(map));
243	map.Bus = *bus;
244	map.TargetID = *target;
245	map.DevHandle = *devhandle;
246
247	if ((error = ioctl(fd, MPTIOCTL_BTDH_MAPPING, &map)) != 0) {
248		error = errno;
249		warn("Failed to map bus/target/device");
250		return (error);
251	}
252
253	*bus = map.Bus;
254	*target = map.TargetID;
255	*devhandle = map.DevHandle;
256
257	return (0);
258}
259
260int
261mps_read_config_page_header(int fd, U8 PageType, U8 PageNumber, U32 PageAddress,
262    MPI2_CONFIG_PAGE_HEADER *header, U16 *IOCStatus)
263{
264	MPI2_CONFIG_REQUEST req;
265	MPI2_CONFIG_REPLY reply;
266
267	bzero(&req, sizeof(req));
268	req.Function = MPI2_FUNCTION_CONFIG;
269	req.Action = MPI2_CONFIG_ACTION_PAGE_HEADER;
270	req.Header.PageType = PageType;
271	req.Header.PageNumber = PageNumber;
272	req.PageAddress = PageAddress;
273
274	if (mps_pass_command(fd, &req, sizeof(req), &reply, sizeof(reply),
275	    NULL, 0, NULL, 0, 30))
276		return (errno);
277
278	if (!IOC_STATUS_SUCCESS(reply.IOCStatus)) {
279		if (IOCStatus != NULL)
280			*IOCStatus = reply.IOCStatus;
281		return (EIO);
282	}
283	if (header == NULL)
284		return (EINVAL);
285	*header = reply.Header;
286	return (0);
287}
288
289int
290mps_read_ext_config_page_header(int fd, U8 ExtPageType, U8 PageNumber, U32 PageAddress, MPI2_CONFIG_PAGE_HEADER *header, U16 *ExtPageLength, U16 *IOCStatus)
291{
292	MPI2_CONFIG_REQUEST req;
293	MPI2_CONFIG_REPLY reply;
294
295	bzero(&req, sizeof(req));
296	req.Function = MPI2_FUNCTION_CONFIG;
297	req.Action = MPI2_CONFIG_ACTION_PAGE_HEADER;
298	req.Header.PageType = MPI2_CONFIG_PAGETYPE_EXTENDED;
299	req.ExtPageType = ExtPageType;
300	req.Header.PageNumber = PageNumber;
301	req.PageAddress = PageAddress;
302
303	if (mps_pass_command(fd, &req, sizeof(req), &reply, sizeof(reply),
304	    NULL, 0, NULL, 0, 30))
305		return (errno);
306
307	if (!IOC_STATUS_SUCCESS(reply.IOCStatus)) {
308		if (IOCStatus != NULL)
309			*IOCStatus = reply.IOCStatus;
310		return (EIO);
311	}
312	if ((header == NULL) || (ExtPageLength == NULL))
313		return (EINVAL);
314	*header = reply.Header;
315	*ExtPageLength = reply.ExtPageLength;
316	return (0);
317}
318
319void *
320mps_read_config_page(int fd, U8 PageType, U8 PageNumber, U32 PageAddress,
321    U16 *IOCStatus)
322{
323	MPI2_CONFIG_REQUEST req;
324	MPI2_CONFIG_PAGE_HEADER header;
325	MPI2_CONFIG_REPLY reply;
326	void *buf;
327	int error, len;
328
329	bzero(&header, sizeof(header));
330	error = mps_read_config_page_header(fd, PageType, PageNumber,
331	    PageAddress, &header, IOCStatus);
332	if (error) {
333		errno = error;
334		return (NULL);
335	}
336
337	bzero(&req, sizeof(req));
338	req.Function = MPI2_FUNCTION_CONFIG;
339	req.Action = MPI2_CONFIG_ACTION_PAGE_READ_CURRENT;
340	req.PageAddress = PageAddress;
341	req.Header = header;
342	req.Header.PageLength = reply.Header.PageLength;
343	if (reply.Header.PageLength == 0)
344		req.Header.PageLength = 4;
345
346	len = req.Header.PageLength * 4;
347	buf = malloc(len);
348	if (mps_pass_command(fd, &req, sizeof(req), &reply, sizeof(reply),
349	    buf, len, NULL, 0, 30)) {
350		error = errno;
351		free(buf);
352		errno = error;
353		return (NULL);
354	}
355	if (!IOC_STATUS_SUCCESS(reply.IOCStatus)) {
356		if (IOCStatus != NULL)
357			*IOCStatus = reply.IOCStatus;
358		else
359			warnx("Reading config page failed: 0x%x %s",
360			    reply.IOCStatus, mps_ioc_status(reply.IOCStatus));
361		free(buf);
362		errno = EIO;
363		return (NULL);
364	}
365	return (buf);
366}
367
368void *
369mps_read_extended_config_page(int fd, U8 ExtPageType, U8 PageVersion,
370    U8 PageNumber, U32 PageAddress, U16 *IOCStatus)
371{
372	MPI2_CONFIG_REQUEST req;
373	MPI2_CONFIG_PAGE_HEADER header;
374	MPI2_CONFIG_REPLY reply;
375	U16 pagelen;
376	void *buf;
377	int error, len;
378
379	if (IOCStatus != NULL)
380		*IOCStatus = MPI2_IOCSTATUS_SUCCESS;
381	bzero(&header, sizeof(header));
382	error = mps_read_ext_config_page_header(fd, ExtPageType, PageNumber,
383	    PageAddress, &header, &pagelen, IOCStatus);
384	if (error) {
385		errno = error;
386		return (NULL);
387	}
388
389	bzero(&req, sizeof(req));
390	req.Function = MPI2_FUNCTION_CONFIG;
391	req.Action = MPI2_CONFIG_ACTION_PAGE_READ_CURRENT;
392	req.PageAddress = PageAddress;
393	req.Header = header;
394	if (pagelen == 0)
395		pagelen = 4;
396	req.ExtPageLength = pagelen;
397	req.ExtPageType = ExtPageType;
398
399	len = pagelen * 4;
400	buf = malloc(len);
401	if (mps_pass_command(fd, &req, sizeof(req), &reply, sizeof(reply),
402	    buf, len, NULL, 0, 30)) {
403		error = errno;
404		free(buf);
405		errno = error;
406		return (NULL);
407	}
408	if (!IOC_STATUS_SUCCESS(reply.IOCStatus)) {
409		if (IOCStatus != NULL)
410			*IOCStatus = reply.IOCStatus;
411		else
412			warnx("Reading extended config page failed: %s",
413			    mps_ioc_status(reply.IOCStatus));
414		free(buf);
415		errno = EIO;
416		return (NULL);
417	}
418	return (buf);
419}
420
421#else
422
423int
424mps_read_config_page_header(int fd, U8 PageType, U8 PageNumber, U32 PageAddress,
425    MPI2_CONFIG_PAGE_HEADER *header, U16 *IOCStatus)
426{
427	struct mps_cfg_page_req req;
428
429	if (IOCStatus != NULL)
430		*IOCStatus = MPI2_IOCSTATUS_SUCCESS;
431	if (header == NULL)
432		return (EINVAL);
433	bzero(&req, sizeof(req));
434	req.header.PageType = PageType;
435	req.header.PageNumber = PageNumber;
436	req.page_address = PageAddress;
437	if (ioctl(fd, MPSIO_READ_CFG_HEADER, &req) < 0)
438		return (errno);
439	if (!IOC_STATUS_SUCCESS(req.ioc_status)) {
440		if (IOCStatus != NULL)
441			*IOCStatus = req.ioc_status;
442		return (EIO);
443	}
444	bcopy(&req.header, header, sizeof(*header));
445	return (0);
446}
447
448void *
449mps_read_config_page(int fd, U8 PageType, U8 PageNumber, U32 PageAddress,
450    U16 *IOCStatus)
451{
452	struct mps_cfg_page_req req;
453	void *buf;
454	int error;
455
456	error = mps_read_config_page_header(fd, PageType, PageNumber,
457	    PageAddress, &req.header, IOCStatus);
458	if (error) {
459		errno = error;
460		return (NULL);
461	}
462
463	if (req.header.PageLength == 0)
464		req.header.PageLength = 4;
465	req.len = req.header.PageLength * 4;
466	buf = malloc(req.len);
467	req.buf = buf;
468	bcopy(&req.header, buf, sizeof(req.header));
469	if (ioctl(fd, MPSIO_READ_CFG_PAGE, &req) < 0) {
470		error = errno;
471		free(buf);
472		errno = error;
473		return (NULL);
474	}
475	if (!IOC_STATUS_SUCCESS(req.ioc_status)) {
476		if (IOCStatus != NULL)
477			*IOCStatus = req.ioc_status;
478		else
479			warnx("Reading config page failed: 0x%x %s",
480			    req.ioc_status, mps_ioc_status(req.ioc_status));
481		free(buf);
482		errno = EIO;
483		return (NULL);
484	}
485	return (buf);
486}
487
488void *
489mps_read_extended_config_page(int fd, U8 ExtPageType, U8 PageVersion,
490    U8 PageNumber, U32 PageAddress, U16 *IOCStatus)
491{
492	struct mps_ext_cfg_page_req req;
493	void *buf;
494	int error;
495
496	if (IOCStatus != NULL)
497		*IOCStatus = MPI2_IOCSTATUS_SUCCESS;
498	bzero(&req, sizeof(req));
499	req.header.PageVersion = PageVersion;
500	req.header.PageNumber = PageNumber;
501	req.header.ExtPageType = ExtPageType;
502	req.page_address = PageAddress;
503	if (ioctl(fd, MPSIO_READ_EXT_CFG_HEADER, &req) < 0)
504		return (NULL);
505	if (!IOC_STATUS_SUCCESS(req.ioc_status)) {
506		if (IOCStatus != NULL)
507			*IOCStatus = req.ioc_status;
508		else
509			warnx("Reading extended config page header failed: %s",
510			    mps_ioc_status(req.ioc_status));
511		errno = EIO;
512		return (NULL);
513	}
514	req.len = req.header.ExtPageLength * 4;
515	buf = malloc(req.len);
516	req.buf = buf;
517	bcopy(&req.header, buf, sizeof(req.header));
518	if (ioctl(fd, MPSIO_READ_EXT_CFG_PAGE, &req) < 0) {
519		error = errno;
520		free(buf);
521		errno = error;
522		return (NULL);
523	}
524	if (!IOC_STATUS_SUCCESS(req.ioc_status)) {
525		if (IOCStatus != NULL)
526			*IOCStatus = req.ioc_status;
527		else
528			warnx("Reading extended config page failed: %s",
529			    mps_ioc_status(req.ioc_status));
530		free(buf);
531		errno = EIO;
532		return (NULL);
533	}
534	return (buf);
535}
536#endif
537
538#if 0
539int
540mpt_write_config_page(int fd, void *buf, U16 *IOCStatus)
541{
542	CONFIG_PAGE_HEADER *hdr;
543	struct mpt_cfg_page_req req;
544
545	if (IOCStatus != NULL)
546		*IOCStatus = MPI_IOCSTATUS_SUCCESS;
547	bzero(&req, sizeof(req));
548	req.buf = buf;
549	hdr = buf;
550	req.len = hdr->PageLength * 4;
551	if (ioctl(fd, MPTIO_WRITE_CFG_PAGE, &req) < 0)
552		return (errno);
553	if (!IOC_STATUS_SUCCESS(req.ioc_status)) {
554		if (IOCStatus != NULL) {
555			*IOCStatus = req.ioc_status;
556			return (0);
557		}
558		warnx("Writing config page failed: %s",
559		    mpt_ioc_status(req.ioc_status));
560		return (EIO);
561	}
562	return (0);
563}
564
565int
566mpt_raid_action(int fd, U8 Action, U8 VolumeBus, U8 VolumeID, U8 PhysDiskNum,
567    U32 ActionDataWord, void *buf, int len, RAID_VOL0_STATUS *VolumeStatus,
568    U32 *ActionData, int datalen, U16 *IOCStatus, U16 *ActionStatus, int write)
569{
570	struct mpt_raid_action raid_act;
571
572	if (IOCStatus != NULL)
573		*IOCStatus = MPI_IOCSTATUS_SUCCESS;
574	if (datalen < 0 || (unsigned)datalen > sizeof(raid_act.action_data))
575		return (EINVAL);
576	bzero(&raid_act, sizeof(raid_act));
577	raid_act.action = Action;
578	raid_act.volume_bus = VolumeBus;
579	raid_act.volume_id = VolumeID;
580	raid_act.phys_disk_num = PhysDiskNum;
581	raid_act.action_data_word = ActionDataWord;
582	if (buf != NULL && len != 0) {
583		raid_act.buf = buf;
584		raid_act.len = len;
585		raid_act.write = write;
586	}
587
588	if (ioctl(fd, MPTIO_RAID_ACTION, &raid_act) < 0)
589		return (errno);
590
591	if (!IOC_STATUS_SUCCESS(raid_act.ioc_status)) {
592		if (IOCStatus != NULL) {
593			*IOCStatus = raid_act.ioc_status;
594			return (0);
595		}
596		warnx("RAID action failed: %s",
597		    mpt_ioc_status(raid_act.ioc_status));
598		return (EIO);
599	}
600
601	if (ActionStatus != NULL)
602		*ActionStatus = raid_act.action_status;
603	if (raid_act.action_status != MPI_RAID_ACTION_ASTATUS_SUCCESS) {
604		if (ActionStatus != NULL)
605			return (0);
606		warnx("RAID action failed: %s",
607		    mpt_raid_status(raid_act.action_status));
608		return (EIO);
609	}
610
611	if (VolumeStatus != NULL)
612		*((U32 *)VolumeStatus) = raid_act.volume_status;
613	if (ActionData != NULL)
614		bcopy(raid_act.action_data, ActionData, datalen);
615	return (0);
616}
617#endif
618
619int
620mps_open(int unit)
621{
622	char path[MAXPATHLEN];
623
624	snprintf(path, sizeof(path), "/dev/mps%d", unit);
625	return (open(path, O_RDWR));
626}
627
628int
629mps_user_command(int fd, void *req, uint32_t req_len, void *reply,
630        uint32_t reply_len, void *buffer, int len, uint32_t flags)
631{
632	struct mps_usr_command cmd;
633
634	bzero(&cmd, sizeof(struct mps_usr_command));
635	cmd.req = req;
636	cmd.req_len = req_len;
637	cmd.rpl = reply;
638	cmd.rpl_len = reply_len;
639	cmd.buf = buffer;
640	cmd.len = len;
641	cmd.flags = flags;
642
643	if (ioctl(fd, MPSIO_MPS_COMMAND, &cmd) < 0)
644		return (errno);
645	return (0);
646}
647
648int
649mps_pass_command(int fd, void *req, uint32_t req_len, void *reply,
650	uint32_t reply_len, void *data_in, uint32_t datain_len, void *data_out,
651	uint32_t dataout_len, uint32_t timeout)
652{
653	struct mps_pass_thru pass;
654
655	pass.PtrRequest = (uint64_t)(uintptr_t)req;
656	pass.PtrReply = (uint64_t)(uintptr_t)reply;
657	pass.PtrData = (uint64_t)(uintptr_t)data_in;
658	pass.PtrDataOut = (uint64_t)(uintptr_t)data_out;
659	pass.RequestSize = req_len;
660	pass.ReplySize = reply_len;
661	pass.DataSize = datain_len;
662	pass.DataOutSize = dataout_len;
663	if (datain_len && dataout_len)
664		pass.DataDirection = MPS_PASS_THRU_DIRECTION_BOTH;
665	else if (datain_len)
666		pass.DataDirection = MPS_PASS_THRU_DIRECTION_READ;
667	else if (dataout_len)
668		pass.DataDirection = MPS_PASS_THRU_DIRECTION_WRITE;
669	else
670		pass.DataDirection = MPS_PASS_THRU_DIRECTION_NONE;
671	pass.Timeout = timeout;
672
673	if (ioctl(fd, MPTIOCTL_PASS_THRU, &pass) < 0)
674		return (errno);
675	return (0);
676}
677
678MPI2_IOC_FACTS_REPLY *
679mps_get_iocfacts(int fd)
680{
681	MPI2_IOC_FACTS_REPLY *facts;
682	MPI2_IOC_FACTS_REQUEST req;
683	int error;
684
685	facts = malloc(sizeof(MPI2_IOC_FACTS_REPLY));
686	if (facts == NULL) {
687		errno = ENOMEM;
688		return (NULL);
689	}
690
691	bzero(&req, sizeof(MPI2_IOC_FACTS_REQUEST));
692	req.Function = MPI2_FUNCTION_IOC_FACTS;
693
694#if 1
695	error = mps_pass_command(fd, &req, sizeof(MPI2_IOC_FACTS_REQUEST),
696	    facts, sizeof(MPI2_IOC_FACTS_REPLY), NULL, 0, NULL, 0, 10);
697#else
698	error = mps_user_command(fd, &req, sizeof(MPI2_IOC_FACTS_REQUEST),
699	    facts, sizeof(MPI2_IOC_FACTS_REPLY), NULL, 0, 0);
700#endif
701	if (error) {
702		free(facts);
703		return (NULL);
704	}
705
706	if (!IOC_STATUS_SUCCESS(facts->IOCStatus)) {
707		free(facts);
708		errno = EINVAL;
709		return (NULL);
710	}
711	return (facts);
712}
713
714