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 2008 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 /*
28  * libfmd_agent contains the low-level operations that needed by the fmd
29  * agents, such as page operations (status/retire/unretire), cpu operations
30  * (status/online/offline), etc.
31  *
32  * Some operations are implemented by /dev/fm ioctls.  Those ioctls are
33  * heavily versioned to allow userland patching without requiring a reboot
34  * to get a matched /dev/fm.   All the ioctls use packed nvlist to interact
35  * between userland and kernel.  (see fmd_agent_nvl_ioctl()).
36  */
37 
38 #include <fcntl.h>
39 #include <errno.h>
40 #include <unistd.h>
41 #include <strings.h>
42 #include <libnvpair.h>
43 #include <string.h>
44 #include <sys/types.h>
45 #include <sys/devfm.h>
46 #include <fmd_agent_impl.h>
47 
48 int
49 fmd_agent_errno(fmd_agent_hdl_t *hdl)
50 {
51 	return (hdl->agent_errno);
52 }
53 
54 int
55 fmd_agent_seterrno(fmd_agent_hdl_t *hdl, int err)
56 {
57 	hdl->agent_errno = err;
58 	return (-1);
59 }
60 
61 const char *
62 fmd_agent_strerr(int err)
63 {
64 	return (strerror(err));
65 }
66 
67 const char *
68 fmd_agent_errmsg(fmd_agent_hdl_t *hdl)
69 {
70 	return (fmd_agent_strerr(hdl->agent_errno));
71 }
72 
73 static int
74 cleanup_set_errno(fmd_agent_hdl_t *hdl, nvlist_t *innvl, nvlist_t *outnvl,
75     int err)
76 {
77 	if (innvl != NULL)
78 		nvlist_free(innvl);
79 	if (outnvl != NULL)
80 		nvlist_free(outnvl);
81 	return (fmd_agent_seterrno(hdl, err));
82 }
83 
84 /*
85  * Perform /dev/fm ioctl.  The input and output data are represented by
86  * name-value lists (nvlists).
87  */
88 int
89 fmd_agent_nvl_ioctl(fmd_agent_hdl_t *hdl, int cmd, uint32_t ver,
90     nvlist_t *innvl, nvlist_t **outnvlp)
91 {
92 	fm_ioc_data_t fid;
93 	int err = 0;
94 	char *inbuf = NULL, *outbuf = NULL;
95 	size_t insz = 0, outsz = 0;
96 
97 	if (innvl != NULL) {
98 		if ((err = nvlist_size(innvl, &insz, NV_ENCODE_NATIVE)) != 0)
99 			return (err);
100 		if (insz > FM_IOC_MAXBUFSZ)
101 			return (ENAMETOOLONG);
102 		if ((inbuf = umem_alloc(insz, UMEM_DEFAULT)) == NULL)
103 			return (errno);
104 
105 		if ((err = nvlist_pack(innvl, &inbuf, &insz,
106 		    NV_ENCODE_NATIVE, 0)) != 0) {
107 			umem_free(inbuf, insz);
108 			return (err);
109 		}
110 	}
111 
112 	if (outnvlp != NULL) {
113 		outsz = FM_IOC_MAXBUFSZ;
114 		if ((outbuf = umem_alloc(outsz, UMEM_DEFAULT)) == NULL) {
115 			err = errno;
116 			if (inbuf != NULL)
117 				umem_free(inbuf, insz);
118 			return (err);
119 		}
120 	}
121 
122 	fid.fid_version = ver;
123 	fid.fid_insz = insz;
124 	fid.fid_inbuf = inbuf;
125 	fid.fid_outsz = outsz;
126 	fid.fid_outbuf = outbuf;
127 
128 	if (ioctl(hdl->agent_devfd, cmd, &fid) < 0)
129 		err = errno;
130 	else if (outnvlp != NULL)
131 		err = nvlist_unpack(fid.fid_outbuf, fid.fid_outsz, outnvlp, 0);
132 
133 	if (inbuf != NULL)
134 		umem_free(inbuf, insz);
135 	if (outbuf != NULL)
136 		umem_free(outbuf, outsz);
137 
138 	return (err);
139 }
140 
141 /*
142  * Open /dev/fm and return a handle.  ver is the overall interface version.
143  */
144 static fmd_agent_hdl_t *
145 fmd_agent_open_dev(int ver, int mode)
146 {
147 	fmd_agent_hdl_t *hdl;
148 	int fd, err;
149 	nvlist_t *nvl;
150 
151 	if ((fd = open("/dev/fm", mode)) < 0)
152 		return (NULL); /* errno is set for us */
153 
154 	if ((hdl = umem_alloc(sizeof (fmd_agent_hdl_t),
155 	    UMEM_DEFAULT)) == NULL) {
156 		err = errno;
157 		(void) close(fd);
158 		errno = err;
159 		return (NULL);
160 	}
161 
162 	hdl->agent_devfd = fd;
163 	hdl->agent_version = ver;
164 
165 	/*
166 	 * Get the individual interface versions.
167 	 */
168 	if ((err = fmd_agent_nvl_ioctl(hdl, FM_IOC_VERSIONS, ver, NULL, &nvl))
169 	    < 0) {
170 		(void) close(fd);
171 		umem_free(hdl, sizeof (fmd_agent_hdl_t));
172 		errno = err;
173 		return (NULL);
174 	}
175 
176 	hdl->agent_ioc_versions = nvl;
177 	return (hdl);
178 }
179 
180 fmd_agent_hdl_t *
181 fmd_agent_open(int ver)
182 {
183 	if (ver > FMD_AGENT_VERSION) {
184 		errno = ENOTSUP;
185 		return (NULL);
186 	}
187 	return (fmd_agent_open_dev(ver, O_RDONLY));
188 }
189 
190 void
191 fmd_agent_close(fmd_agent_hdl_t *hdl)
192 {
193 	(void) close(hdl->agent_devfd);
194 	nvlist_free(hdl->agent_ioc_versions);
195 	umem_free(hdl, sizeof (fmd_agent_hdl_t));
196 }
197 
198 /*
199  * Given a interface name, return the kernel interface version.
200  */
201 int
202 fmd_agent_version(fmd_agent_hdl_t *hdl, const char *op, uint32_t *verp)
203 {
204 	int err;
205 
206 	err = nvlist_lookup_uint32(hdl->agent_ioc_versions,
207 	    op, verp);
208 
209 	if (err != 0) {
210 		errno = err;
211 		return (-1);
212 	}
213 	return (0);
214 }
215 
216 static int
217 fmd_agent_pageop_v1(fmd_agent_hdl_t *hdl, int cmd, nvlist_t *fmri)
218 {
219 	int err;
220 	nvlist_t *nvl = NULL;
221 
222 	if ((err = nvlist_alloc(&nvl, NV_UNIQUE_NAME_TYPE, 0)) != 0 ||
223 	    (err = nvlist_add_nvlist(nvl, FM_PAGE_RETIRE_FMRI, fmri)) != 0 ||
224 	    (err = fmd_agent_nvl_ioctl(hdl, cmd, 1, nvl, NULL)) != 0)
225 		return (cleanup_set_errno(hdl, nvl, NULL, err));
226 
227 	nvlist_free(nvl);
228 	return (0);
229 }
230 
231 static int
232 fmd_agent_pageop(fmd_agent_hdl_t *hdl, int cmd, nvlist_t *fmri)
233 {
234 	uint32_t ver;
235 
236 	if (fmd_agent_version(hdl, FM_PAGE_OP_VERSION, &ver) == -1)
237 		return (fmd_agent_seterrno(hdl, errno));
238 
239 	switch (ver) {
240 	case 1:
241 		return (fmd_agent_pageop_v1(hdl, cmd, fmri));
242 
243 	default:
244 		return (fmd_agent_seterrno(hdl, ENOTSUP));
245 	}
246 }
247 
248 int
249 fmd_agent_page_retire(fmd_agent_hdl_t *hdl, nvlist_t *fmri)
250 {
251 	int rc = fmd_agent_pageop(hdl, FM_IOC_PAGE_RETIRE, fmri);
252 	int err = fmd_agent_errno(hdl);
253 
254 	/*
255 	 * FM_IOC_PAGE_RETIRE ioctl returns:
256 	 *   0 - success in retiring page
257 	 *   -1, errno = EIO - page is already retired
258 	 *   -1, errno = EAGAIN - page is scheduled for retirement
259 	 *   -1, errno = EINVAL - page fmri is invalid
260 	 *   -1, errno = any else - error
261 	 */
262 	if (rc == 0 || err == EIO || err == EINVAL) {
263 		if (rc == 0)
264 			(void) fmd_agent_seterrno(hdl, 0);
265 		return (FMD_AGENT_RETIRE_DONE);
266 	}
267 	if (err == EAGAIN)
268 		return (FMD_AGENT_RETIRE_ASYNC);
269 
270 	return (FMD_AGENT_RETIRE_FAIL);
271 }
272 
273 int
274 fmd_agent_page_unretire(fmd_agent_hdl_t *hdl, nvlist_t *fmri)
275 {
276 	int rc = fmd_agent_pageop(hdl, FM_IOC_PAGE_UNRETIRE, fmri);
277 	int err = fmd_agent_errno(hdl);
278 
279 	/*
280 	 * FM_IOC_PAGE_UNRETIRE ioctl returns:
281 	 *   0 - success in unretiring page
282 	 *   -1, errno = EIO - page is already unretired
283 	 *   -1, errno = EAGAIN - page couldn't be locked, still retired
284 	 *   -1, errno = EINVAL - page fmri is invalid
285 	 *   -1, errno = any else - error
286 	 */
287 	if (rc == 0 || err == EIO || err == EINVAL) {
288 		if (rc == 0)
289 			(void) fmd_agent_seterrno(hdl, 0);
290 		return (FMD_AGENT_RETIRE_DONE);
291 	}
292 
293 	return (FMD_AGENT_RETIRE_FAIL);
294 }
295 
296 int
297 fmd_agent_page_isretired(fmd_agent_hdl_t *hdl, nvlist_t *fmri)
298 {
299 	int rc = fmd_agent_pageop(hdl, FM_IOC_PAGE_STATUS, fmri);
300 	int err = fmd_agent_errno(hdl);
301 
302 	/*
303 	 * FM_IOC_PAGE_STATUS returns:
304 	 *   0 - page is retired
305 	 *   -1, errno = EAGAIN - page is scheduled for retirement
306 	 *   -1, errno = EIO - page not scheduled for retirement
307 	 *   -1, errno = EINVAL - page fmri is invalid
308 	 *   -1, errno = any else - error
309 	 */
310 	if (rc == 0 || err == EINVAL) {
311 		if (rc == 0)
312 			(void) fmd_agent_seterrno(hdl, 0);
313 		return (FMD_AGENT_RETIRE_DONE);
314 	}
315 	if (err == EAGAIN)
316 		return (FMD_AGENT_RETIRE_ASYNC);
317 
318 	return (FMD_AGENT_RETIRE_FAIL);
319 }
320