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 2009 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
fmd_agent_errno(fmd_agent_hdl_t * hdl)49 fmd_agent_errno(fmd_agent_hdl_t *hdl)
50 {
51 return (hdl->agent_errno);
52 }
53
54 int
fmd_agent_seterrno(fmd_agent_hdl_t * hdl,int err)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 *
fmd_agent_strerr(int err)62 fmd_agent_strerr(int err)
63 {
64 return (strerror(err));
65 }
66
67 const char *
fmd_agent_errmsg(fmd_agent_hdl_t * hdl)68 fmd_agent_errmsg(fmd_agent_hdl_t *hdl)
69 {
70 return (fmd_agent_strerr(hdl->agent_errno));
71 }
72
73 static int
cleanup_set_errno(fmd_agent_hdl_t * hdl,nvlist_t * innvl,nvlist_t * outnvl,int err)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
fmd_agent_nvl_ioctl(fmd_agent_hdl_t * hdl,int cmd,uint32_t ver,nvlist_t * innvl,nvlist_t ** outnvlp)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_OUT_BUFSZ;
114 }
115 for (;;) {
116 if (outnvlp != NULL) {
117 outbuf = umem_alloc(outsz, UMEM_DEFAULT);
118 if (outbuf == NULL) {
119 err = errno;
120 break;
121 }
122 }
123
124 fid.fid_version = ver;
125 fid.fid_insz = insz;
126 fid.fid_inbuf = inbuf;
127 fid.fid_outsz = outsz;
128 fid.fid_outbuf = outbuf;
129
130 if (ioctl(hdl->agent_devfd, cmd, &fid) < 0) {
131 if (errno == ENAMETOOLONG && outsz != 0 &&
132 outsz < (FM_IOC_OUT_MAXBUFSZ / 2)) {
133 umem_free(outbuf, outsz);
134 outsz *= 2;
135 outbuf = umem_alloc(outsz, UMEM_DEFAULT);
136 if (outbuf == NULL) {
137 err = errno;
138 break;
139 }
140 } else {
141 err = errno;
142 break;
143 }
144 } else if (outnvlp != NULL) {
145 err = nvlist_unpack(fid.fid_outbuf, fid.fid_outsz,
146 outnvlp, 0);
147 break;
148 } else {
149 break;
150 }
151 }
152
153 if (inbuf != NULL)
154 umem_free(inbuf, insz);
155 if (outbuf != NULL)
156 umem_free(outbuf, outsz);
157
158 return (err);
159 }
160
161 /*
162 * Open /dev/fm and return a handle. ver is the overall interface version.
163 */
164 static fmd_agent_hdl_t *
fmd_agent_open_dev(int ver,int mode)165 fmd_agent_open_dev(int ver, int mode)
166 {
167 fmd_agent_hdl_t *hdl;
168 int fd, err;
169 nvlist_t *nvl;
170
171 if ((fd = open("/dev/fm", mode)) < 0)
172 return (NULL); /* errno is set for us */
173
174 if ((hdl = umem_alloc(sizeof (fmd_agent_hdl_t),
175 UMEM_DEFAULT)) == NULL) {
176 err = errno;
177 (void) close(fd);
178 errno = err;
179 return (NULL);
180 }
181
182 hdl->agent_devfd = fd;
183 hdl->agent_version = ver;
184
185 /*
186 * Get the individual interface versions.
187 */
188 if ((err = fmd_agent_nvl_ioctl(hdl, FM_IOC_VERSIONS, ver, NULL, &nvl))
189 < 0) {
190 (void) close(fd);
191 umem_free(hdl, sizeof (fmd_agent_hdl_t));
192 errno = err;
193 return (NULL);
194 }
195
196 hdl->agent_ioc_versions = nvl;
197 return (hdl);
198 }
199
200 fmd_agent_hdl_t *
fmd_agent_open(int ver)201 fmd_agent_open(int ver)
202 {
203 if (ver > FMD_AGENT_VERSION) {
204 errno = ENOTSUP;
205 return (NULL);
206 }
207 return (fmd_agent_open_dev(ver, O_RDONLY));
208 }
209
210 void
fmd_agent_close(fmd_agent_hdl_t * hdl)211 fmd_agent_close(fmd_agent_hdl_t *hdl)
212 {
213 (void) close(hdl->agent_devfd);
214 nvlist_free(hdl->agent_ioc_versions);
215 umem_free(hdl, sizeof (fmd_agent_hdl_t));
216 }
217
218 /*
219 * Given a interface name, return the kernel interface version.
220 */
221 int
fmd_agent_version(fmd_agent_hdl_t * hdl,const char * op,uint32_t * verp)222 fmd_agent_version(fmd_agent_hdl_t *hdl, const char *op, uint32_t *verp)
223 {
224 int err;
225
226 err = nvlist_lookup_uint32(hdl->agent_ioc_versions,
227 op, verp);
228
229 if (err != 0) {
230 errno = err;
231 return (-1);
232 }
233 return (0);
234 }
235
236 static int
fmd_agent_pageop_v1(fmd_agent_hdl_t * hdl,int cmd,nvlist_t * fmri)237 fmd_agent_pageop_v1(fmd_agent_hdl_t *hdl, int cmd, nvlist_t *fmri)
238 {
239 int err;
240 nvlist_t *nvl = NULL;
241
242 if ((err = nvlist_alloc(&nvl, NV_UNIQUE_NAME_TYPE, 0)) != 0 ||
243 (err = nvlist_add_nvlist(nvl, FM_PAGE_RETIRE_FMRI, fmri)) != 0 ||
244 (err = fmd_agent_nvl_ioctl(hdl, cmd, 1, nvl, NULL)) != 0)
245 return (cleanup_set_errno(hdl, nvl, NULL, err));
246
247 nvlist_free(nvl);
248 return (0);
249 }
250
251 static int
fmd_agent_pageop(fmd_agent_hdl_t * hdl,int cmd,nvlist_t * fmri)252 fmd_agent_pageop(fmd_agent_hdl_t *hdl, int cmd, nvlist_t *fmri)
253 {
254 uint32_t ver;
255
256 if (fmd_agent_version(hdl, FM_PAGE_OP_VERSION, &ver) == -1)
257 return (fmd_agent_seterrno(hdl, errno));
258
259 switch (ver) {
260 case 1:
261 return (fmd_agent_pageop_v1(hdl, cmd, fmri));
262
263 default:
264 return (fmd_agent_seterrno(hdl, ENOTSUP));
265 }
266 }
267
268 int
fmd_agent_page_retire(fmd_agent_hdl_t * hdl,nvlist_t * fmri)269 fmd_agent_page_retire(fmd_agent_hdl_t *hdl, nvlist_t *fmri)
270 {
271 int rc = fmd_agent_pageop(hdl, FM_IOC_PAGE_RETIRE, fmri);
272 int err = fmd_agent_errno(hdl);
273
274 /*
275 * FM_IOC_PAGE_RETIRE ioctl returns:
276 * 0 - success in retiring page
277 * -1, errno = EIO - page is already retired
278 * -1, errno = EAGAIN - page is scheduled for retirement
279 * -1, errno = EINVAL - page fmri is invalid
280 * -1, errno = any else - error
281 */
282 if (rc == 0 || err == EIO || err == EINVAL) {
283 if (rc == 0)
284 (void) fmd_agent_seterrno(hdl, 0);
285 return (FMD_AGENT_RETIRE_DONE);
286 }
287 if (err == EAGAIN)
288 return (FMD_AGENT_RETIRE_ASYNC);
289
290 return (FMD_AGENT_RETIRE_FAIL);
291 }
292
293 int
fmd_agent_page_unretire(fmd_agent_hdl_t * hdl,nvlist_t * fmri)294 fmd_agent_page_unretire(fmd_agent_hdl_t *hdl, nvlist_t *fmri)
295 {
296 int rc = fmd_agent_pageop(hdl, FM_IOC_PAGE_UNRETIRE, fmri);
297 int err = fmd_agent_errno(hdl);
298
299 /*
300 * FM_IOC_PAGE_UNRETIRE ioctl returns:
301 * 0 - success in unretiring page
302 * -1, errno = EIO - page is already unretired
303 * -1, errno = EAGAIN - page couldn't be locked, still retired
304 * -1, errno = EINVAL - page fmri is invalid
305 * -1, errno = any else - error
306 */
307 if (rc == 0 || err == EIO || err == EINVAL) {
308 if (rc == 0)
309 (void) fmd_agent_seterrno(hdl, 0);
310 return (FMD_AGENT_RETIRE_DONE);
311 }
312
313 return (FMD_AGENT_RETIRE_FAIL);
314 }
315
316 int
fmd_agent_page_isretired(fmd_agent_hdl_t * hdl,nvlist_t * fmri)317 fmd_agent_page_isretired(fmd_agent_hdl_t *hdl, nvlist_t *fmri)
318 {
319 int rc = fmd_agent_pageop(hdl, FM_IOC_PAGE_STATUS, fmri);
320 int err = fmd_agent_errno(hdl);
321
322 /*
323 * FM_IOC_PAGE_STATUS returns:
324 * 0 - page is retired
325 * -1, errno = EAGAIN - page is scheduled for retirement
326 * -1, errno = EIO - page not scheduled for retirement
327 * -1, errno = EINVAL - page fmri is invalid
328 * -1, errno = any else - error
329 */
330 if (rc == 0 || err == EINVAL) {
331 if (rc == 0)
332 (void) fmd_agent_seterrno(hdl, 0);
333 return (FMD_AGENT_RETIRE_DONE);
334 }
335 if (err == EAGAIN)
336 return (FMD_AGENT_RETIRE_ASYNC);
337
338 return (FMD_AGENT_RETIRE_FAIL);
339 }
340