1/*-
2 * SPDX-License-Identifier: BSD-3-Clause
3 *
4 * Copyright (c) 2008, 2009 Yahoo!, Inc.
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 * 3. The names of the authors may not be used to endorse or promote
16 *    products derived from this software without specific prior written
17 *    permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * SUCH DAMAGE.
30 *
31 * $FreeBSD$
32 */
33
34#include <sys/param.h>
35#include <sys/ioctl.h>
36#include <sys/sysctl.h>
37#include <sys/uio.h>
38
39#include <err.h>
40#include <errno.h>
41#include <fcntl.h>
42#include <stdio.h>
43#include <stdlib.h>
44#include <string.h>
45#include <time.h>
46#include <unistd.h>
47
48#include "mfiutil.h"
49#include <dev/mfi/mfi_ioctl.h>
50
51static const char *mfi_status_codes[] = {
52	"Command completed successfully",
53	"Invalid command",
54	"Invalid DMCD opcode",
55	"Invalid parameter",
56	"Invalid Sequence Number",
57	"Abort isn't possible for the requested command",
58	"Application 'host' code not found",
59	"Application in use",
60	"Application not initialized",
61	"Array index invalid",
62	"Array row not empty",
63	"Configuration resource conflict",
64	"Device not found",
65	"Drive too small",
66	"Flash memory allocation failed",
67	"Flash download already in progress",
68	"Flash operation failed",
69	"Bad flash image",
70	"Incomplete flash image",
71	"Flash not open",
72	"Flash not started",
73	"Flush failed",
74	"Specified application doesn't have host-resident code",
75	"Volume consistency check in progress",
76	"Volume initialization in progress",
77	"Volume LBA out of range",
78	"Maximum number of volumes are already configured",
79	"Volume is not OPTIMAL",
80	"Volume rebuild in progress",
81	"Volume reconstruction in progress",
82	"Volume RAID level is wrong for requested operation",
83	"Too many spares assigned",
84	"Scratch memory not available",
85	"Error writing MFC data to SEEPROM",
86	"Required hardware is missing",
87	"Item not found",
88	"Volume drives are not within an enclosure",
89	"Drive clear in progress",
90	"Drive type mismatch (SATA vs SAS)",
91	"Patrol read disabled",
92	"Invalid row index",
93	"SAS Config - Invalid action",
94	"SAS Config - Invalid data",
95	"SAS Config - Invalid page",
96	"SAS Config - Invalid type",
97	"SCSI command completed with error",
98	"SCSI I/O request failed",
99	"SCSI RESERVATION_CONFLICT",
100	"One or more flush operations during shutdown failed",
101	"Firmware time is not set",
102	"Wrong firmware or drive state",
103	"Volume is offline",
104	"Peer controller rejected request",
105	"Unable to inform peer of communication changes",
106	"Volume reservation already in progress",
107	"I2C errors were detected",
108	"PCI errors occurred during XOR/DMA operation",
109	"Diagnostics failed",
110	"Unable to process command as boot messages are pending",
111	"Foreign configuration is incomplete"
112};
113
114const char *
115mfi_status(u_int status_code)
116{
117	static char buffer[16];
118
119	if (status_code == MFI_STAT_INVALID_STATUS)
120		return ("Invalid status");
121	if (status_code < sizeof(mfi_status_codes) / sizeof(char *))
122		return (mfi_status_codes[status_code]);
123	snprintf(buffer, sizeof(buffer), "Status: 0x%02x", status_code);
124	return (buffer);
125}
126
127const char *
128mfi_raid_level(uint8_t primary_level, uint8_t secondary_level)
129{
130	static char buf[16];
131
132	switch (primary_level) {
133	case DDF_RAID0:
134		return ("RAID-0");
135	case DDF_RAID1:
136		if (secondary_level != 0)
137			return ("RAID-10");
138		else
139			return ("RAID-1");
140	case DDF_RAID1E:
141		return ("RAID-1E");
142	case DDF_RAID3:
143		return ("RAID-3");
144	case DDF_RAID5:
145		if (secondary_level != 0)
146			return ("RAID-50");
147		else
148			return ("RAID-5");
149	case DDF_RAID5E:
150		return ("RAID-5E");
151	case DDF_RAID5EE:
152		return ("RAID-5EE");
153	case DDF_RAID6:
154		if (secondary_level != 0)
155			return ("RAID-60");
156		else
157			return ("RAID-6");
158	case DDF_JBOD:
159		return ("JBOD");
160	case DDF_CONCAT:
161		return ("CONCAT");
162	default:
163		sprintf(buf, "LVL 0x%02x", primary_level);
164		return (buf);
165	}
166}
167
168static int
169mfi_query_disk(int fd, uint8_t target_id, struct mfi_query_disk *info)
170{
171
172	bzero(info, sizeof(*info));
173	info->array_id = target_id;
174	if (ioctl(fd, MFIIO_QUERY_DISK, info) < 0)
175		return (-1);
176	if (!info->present) {
177		errno = ENXIO;
178		return (-1);
179	}
180	return (0);
181}
182
183const char *
184mfi_volume_name(int fd, uint8_t target_id)
185{
186	static struct mfi_query_disk info;
187	static char buf[4];
188
189	if (mfi_query_disk(fd, target_id, &info) < 0) {
190		snprintf(buf, sizeof(buf), "%d", target_id);
191		return (buf);
192	}
193	return (info.devname);
194}
195
196int
197mfi_volume_busy(int fd, uint8_t target_id)
198{
199	struct mfi_query_disk info;
200
201	/* Assume it isn't mounted if we can't get information. */
202	if (mfi_query_disk(fd, target_id, &info) < 0)
203		return (0);
204	return (info.open != 0);
205}
206
207/*
208 * Check if the running kernel supports changing the RAID
209 * configuration of the mfi controller.
210 */
211int
212mfi_reconfig_supported(void)
213{
214	char mibname[64];
215	size_t len;
216	int dummy;
217
218	len = sizeof(dummy);
219	snprintf(mibname, sizeof(mibname), "dev.mfi.%d.delete_busy_volumes",
220	    mfi_unit);
221	return (sysctlbyname(mibname, &dummy, &len, NULL, 0) == 0);
222}
223
224int
225mfi_lookup_volume(int fd, const char *name, uint8_t *target_id)
226{
227	struct mfi_query_disk info;
228	struct mfi_ld_list list;
229	char *cp;
230	long val;
231	u_int i;
232
233	/* If it's a valid number, treat it as a raw target ID. */
234	val = strtol(name, &cp, 0);
235	if (*cp == '\0') {
236		*target_id = val;
237		return (0);
238	}
239
240	if (mfi_dcmd_command(fd, MFI_DCMD_LD_GET_LIST, &list, sizeof(list),
241	    NULL, 0, NULL) < 0)
242		return (-1);
243
244	for (i = 0; i < list.ld_count; i++) {
245		if (mfi_query_disk(fd, list.ld_list[i].ld.v.target_id,
246		    &info) < 0)
247			continue;
248		if (strcmp(name, info.devname) == 0) {
249			*target_id = list.ld_list[i].ld.v.target_id;
250			return (0);
251		}
252	}
253	errno = EINVAL;
254	return (-1);
255}
256
257int
258mfi_dcmd_command(int fd, uint32_t opcode, void *buf, size_t bufsize,
259    uint8_t *mbox, size_t mboxlen, uint8_t *statusp)
260{
261	struct mfi_ioc_passthru ioc;
262	struct mfi_dcmd_frame *dcmd;
263	int r;
264
265	if ((mbox != NULL && (mboxlen == 0 || mboxlen > MFI_MBOX_SIZE)) ||
266	    (mbox == NULL && mboxlen != 0)) {
267		errno = EINVAL;
268		return (-1);
269	}
270
271	bzero(&ioc, sizeof(ioc));
272	dcmd = &ioc.ioc_frame;
273	if (mbox)
274		bcopy(mbox, dcmd->mbox, mboxlen);
275	dcmd->header.cmd = MFI_CMD_DCMD;
276	dcmd->header.timeout = 0;
277	dcmd->header.flags = 0;
278	dcmd->header.data_len = bufsize;
279	dcmd->opcode = opcode;
280
281	ioc.buf = buf;
282	ioc.buf_size = bufsize;
283	r = ioctl(fd, MFIIO_PASSTHRU, &ioc);
284	if (r < 0)
285		return (r);
286
287	if (statusp != NULL)
288		*statusp = dcmd->header.cmd_status;
289	else if (dcmd->header.cmd_status != MFI_STAT_OK) {
290		warnx("Command failed: %s",
291		    mfi_status(dcmd->header.cmd_status));
292		errno = EIO;
293		return (-1);
294	}
295	return (0);
296}
297
298int
299mfi_ctrl_get_info(int fd, struct mfi_ctrl_info *info, uint8_t *statusp)
300{
301
302	return (mfi_dcmd_command(fd, MFI_DCMD_CTRL_GETINFO, info,
303	    sizeof(struct mfi_ctrl_info), NULL, 0, statusp));
304}
305
306int
307mfi_open(int unit, int acs)
308{
309	char path[MAXPATHLEN];
310
311	snprintf(path, sizeof(path), "/dev/mfi%d", unit);
312	return (open(path, acs));
313}
314
315static void
316print_time_humanized(uint seconds)
317{
318
319	if (seconds > 3600) {
320		printf("%u:", seconds / 3600);
321	}
322	if (seconds > 60) {
323		seconds %= 3600;
324		printf("%02u:%02u", seconds / 60, seconds % 60);
325	} else {
326		printf("%us", seconds);
327	}
328}
329
330void
331mfi_display_progress(const char *label, struct mfi_progress *prog)
332{
333	uint seconds;
334
335	printf("%s: %.2f%% complete after ", label,
336	    (float)prog->progress * 100 / 0xffff);
337	print_time_humanized(prog->elapsed_seconds);
338	if (prog->progress != 0 && prog->elapsed_seconds > 10) {
339		printf(" finished in ");
340		seconds = (0x10000 * (uint32_t)prog->elapsed_seconds) /
341		    prog->progress - prog->elapsed_seconds;
342		print_time_humanized(seconds);
343	}
344	printf("\n");
345}
346
347int
348mfi_table_handler(struct mfiutil_command **start, struct mfiutil_command **end,
349    int ac, char **av)
350{
351	struct mfiutil_command **cmd;
352
353	if (ac < 2) {
354		warnx("The %s command requires a sub-command.", av[0]);
355		return (EINVAL);
356	}
357	for (cmd = start; cmd < end; cmd++) {
358		if (strcmp((*cmd)->name, av[1]) == 0)
359			return ((*cmd)->handler(ac - 1, av + 1));
360	}
361
362	warnx("%s is not a valid sub-command of %s.", av[1], av[0]);
363	return (ENOENT);
364}
365