1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21
22/*
23 * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
24 */
25
26#include <sys/types.h>
27#include <sys/stat.h>
28#include <sys/scsi/scsi_address.h>
29#include <sys/scsi/impl/usmp.h>
30#include <sys/libdevid.h>
31
32#include <unistd.h>
33#include <fcntl.h>
34#include <errno.h>
35#include <string.h>
36#include <strings.h>
37#include <stdio.h>
38#include <limits.h>
39
40#include <scsi/libsmp.h>
41#include <scsi/libsmp_plugin.h>
42
43#include <libdevinfo.h>
44
45struct usmp_dev {
46	int ud_fd;
47	char *ud_dev;
48	uint64_t ud_addr;
49};
50
51struct di_walk_arg {
52	dev_t dev;
53	uint64_t addr;
54};
55
56static int
57di_walk(di_node_t node, di_minor_t minor, void *arg)
58{
59	struct di_walk_arg *wp = arg;
60	char *wwn;
61
62	if (di_minor_spectype(minor) != S_IFCHR)
63		return (DI_WALK_CONTINUE);
64
65	if (di_minor_devt(minor) == wp->dev) {
66		if (di_prop_lookup_strings(DDI_DEV_T_ANY, node,
67		    SCSI_ADDR_PROP_TARGET_PORT, &wwn) != 1 &&
68		    di_prop_lookup_strings(DDI_DEV_T_ANY, node,
69		    "smp-wwn", &wwn) != 1)
70			return (DI_WALK_CONTINUE);
71
72		if (scsi_wwnstr_to_wwn(wwn, &wp->addr) != DDI_SUCCESS)
73			return (DI_WALK_CONTINUE);
74
75		return (DI_WALK_TERMINATE);
76	}
77
78	return (DI_WALK_CONTINUE);
79}
80
81static void *
82usmp_open(const void *target)
83{
84	struct usmp_dev *dp;
85	const char *target_name = (const char *)target;
86
87	struct stat64 st;
88	di_node_t root, smp;
89	struct di_walk_arg walk;
90
91	if ((dp = smp_zalloc(sizeof (struct usmp_dev))) == NULL)
92		return (NULL);
93
94	if ((dp->ud_dev = smp_strdup(target_name)) == NULL) {
95		smp_free(dp);
96		return (NULL);
97	}
98
99	if ((dp->ud_fd = open(target_name, O_RDONLY)) < 0) {
100		(void) smp_error(ESMP_BADTARGET,
101		    "failed to open %s for reading: %s",
102		    target_name, strerror(errno));
103		smp_free(dp->ud_dev);
104		smp_free(dp);
105		return (NULL);
106	}
107
108	if (fstat64(dp->ud_fd, &st) != 0) {
109		(void) smp_error(ESMP_BADTARGET,
110		    "failed to stat %s: %s", target_name, strerror(errno));
111		(void) close(dp->ud_fd);
112		smp_free(dp->ud_dev);
113		smp_free(dp);
114		return (NULL);
115	}
116
117	if ((root = di_init("/", DINFOCACHE)) != DI_NODE_NIL) {
118		for (smp = di_drv_first_node("smp", root); smp != DI_NODE_NIL;
119		    smp = di_drv_next_node(smp)) {
120			bzero(&walk, sizeof (walk));
121			walk.dev = st.st_rdev;
122			(void) di_walk_minor(smp, NULL, 0, &walk, di_walk);
123			if (walk.addr != 0) {
124				dp->ud_addr = walk.addr;
125				break;
126			}
127		}
128		di_fini(root);
129	}
130
131	return (dp);
132}
133
134static void
135usmp_close(void *private)
136{
137	struct usmp_dev *dp = (struct usmp_dev *)private;
138
139	if (dp == NULL)
140		return;
141
142	if (dp->ud_fd > 0)
143		(void) close(dp->ud_fd);
144
145	smp_free(dp->ud_dev);
146	smp_free(dp);
147}
148
149static int
150usmp_exec(void *private, smp_action_t *ap)
151{
152	struct usmp_dev *dp = (struct usmp_dev *)private;
153	struct usmp_cmd cmd;
154	void *req, *resp;
155	size_t reqlen, resplen;
156
157	bzero(&cmd, sizeof (cmd));
158
159	smp_action_get_request_frame(ap, &req, &reqlen);
160	smp_action_get_response_frame(ap, &resp, &resplen);
161
162	ASSERT(req != NULL);
163	ASSERT(resp != NULL);
164	ASSERT(reqlen != 0);
165	ASSERT(resplen != 0);
166
167	cmd.usmp_req = req;
168	cmd.usmp_reqsize = reqlen;
169	cmd.usmp_rsp = resp;
170	cmd.usmp_rspsize = resplen;
171	cmd.usmp_timeout = (int)smp_action_get_timeout(ap);
172
173	if (ioctl(dp->ud_fd, USMPFUNC, &cmd) < 0) {
174		ASSERT(errno != EFAULT);
175		switch (errno) {
176		case EINVAL:
177			return (smp_error(ESMP_BADFUNC, "internal usmp error"));
178		case EPERM:
179			return (smp_error(ESMP_PERM,
180			    "insufficient privileges"));
181		case EIO:
182			return (smp_error(ESMP_IO, "I/O error"));
183		default:
184			return (smp_error(ESMP_SYS, "usmp ioctl failed: %s",
185			    strerror(errno)));
186		}
187	}
188
189	/*
190	 * There is no way to determine the amount of data actually transferred
191	 * so we will just place the upper bound at the allocated size.
192	 */
193	smp_action_set_response_len(ap, resplen);
194
195	return (0);
196}
197
198static void
199usmp_target_name(void *private, char *buf, size_t len)
200{
201	struct usmp_dev *dp = (struct usmp_dev *)private;
202
203	(void) strlcpy(buf, dp->ud_dev, len);
204}
205
206static uint64_t
207usmp_target_addr(void *private)
208{
209	struct usmp_dev *dp = (struct usmp_dev *)private;
210
211	return (dp->ud_addr);
212}
213
214static const smp_engine_ops_t usmp_ops = {
215	.seo_open = usmp_open,
216	.seo_close = usmp_close,
217	.seo_exec = usmp_exec,
218	.seo_target_name = usmp_target_name,
219	.seo_target_addr = usmp_target_addr
220};
221
222int
223_smp_init(smp_engine_t *ep)
224{
225	smp_engine_config_t config = {
226		.sec_name = "usmp",
227		.sec_ops = &usmp_ops
228	};
229
230	return (smp_engine_register(ep, LIBSMP_ENGINE_VERSION, &config));
231}
232