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  * Copyright 2023 Oxide Computer Company
29  */
30 
31 /*
32  * libfmd_agent contains the low-level operations that needed by the fmd
33  * agents, such as page operations (status/retire/unretire), cpu operations
34  * (status/online/offline), etc.
35  *
36  * Some operations are implemented by /dev/fm ioctls.  Those ioctls are
37  * heavily versioned to allow userland patching without requiring a reboot
38  * to get a matched /dev/fm.   All the ioctls use packed nvlist to interact
39  * between userland and kernel.  (see fmd_agent_nvl_ioctl()).
40  */
41 
42 #include <fcntl.h>
43 #include <errno.h>
44 #include <unistd.h>
45 #include <strings.h>
46 #include <libnvpair.h>
47 #include <string.h>
48 #include <sys/types.h>
49 #include <sys/devfm.h>
50 #include <fmd_agent_impl.h>
51 
52 int
fmd_agent_errno(fmd_agent_hdl_t * hdl)53 fmd_agent_errno(fmd_agent_hdl_t *hdl)
54 {
55 	return (hdl->agent_errno);
56 }
57 
58 int
fmd_agent_seterrno(fmd_agent_hdl_t * hdl,int err)59 fmd_agent_seterrno(fmd_agent_hdl_t *hdl, int err)
60 {
61 	hdl->agent_errno = err;
62 	return (-1);
63 }
64 
65 const char *
fmd_agent_strerr(int err)66 fmd_agent_strerr(int err)
67 {
68 	return (strerror(err));
69 }
70 
71 const char *
fmd_agent_errmsg(fmd_agent_hdl_t * hdl)72 fmd_agent_errmsg(fmd_agent_hdl_t *hdl)
73 {
74 	return (fmd_agent_strerr(hdl->agent_errno));
75 }
76 
77 static int
cleanup_set_errno(fmd_agent_hdl_t * hdl,nvlist_t * innvl,nvlist_t * outnvl,int err)78 cleanup_set_errno(fmd_agent_hdl_t *hdl, nvlist_t *innvl, nvlist_t *outnvl,
79     int err)
80 {
81 	nvlist_free(innvl);
82 	nvlist_free(outnvl);
83 	return (fmd_agent_seterrno(hdl, err));
84 }
85 
86 /*
87  * Perform /dev/fm ioctl.  The input and output data are represented by
88  * name-value lists (nvlists).
89  */
90 int
fmd_agent_nvl_ioctl(fmd_agent_hdl_t * hdl,int cmd,uint32_t ver,nvlist_t * innvl,nvlist_t ** outnvlp)91 fmd_agent_nvl_ioctl(fmd_agent_hdl_t *hdl, int cmd, uint32_t ver,
92     nvlist_t *innvl, nvlist_t **outnvlp)
93 {
94 	fm_ioc_data_t fid;
95 	int err = 0;
96 	char *inbuf = NULL, *outbuf = NULL;
97 	size_t insz = 0, outsz = 0;
98 
99 	if (innvl != NULL) {
100 		if ((err = nvlist_size(innvl, &insz, NV_ENCODE_NATIVE)) != 0)
101 			return (err);
102 		if (insz > FM_IOC_MAXBUFSZ)
103 			return (ENAMETOOLONG);
104 		if ((inbuf = umem_alloc(insz, UMEM_DEFAULT)) == NULL)
105 			return (errno);
106 
107 		if ((err = nvlist_pack(innvl, &inbuf, &insz,
108 		    NV_ENCODE_NATIVE, 0)) != 0) {
109 			umem_free(inbuf, insz);
110 			return (err);
111 		}
112 	}
113 
114 	if (outnvlp != NULL) {
115 		outsz = FM_IOC_OUT_BUFSZ;
116 	}
117 	for (;;) {
118 		if (outnvlp != NULL) {
119 			outbuf = umem_alloc(outsz, UMEM_DEFAULT);
120 			if (outbuf == NULL) {
121 				err = errno;
122 				break;
123 			}
124 		}
125 
126 		fid.fid_version = ver;
127 		fid.fid_insz = insz;
128 		fid.fid_inbuf = inbuf;
129 		fid.fid_outsz = outsz;
130 		fid.fid_outbuf = outbuf;
131 
132 		if (ioctl(hdl->agent_devfd, cmd, &fid) < 0) {
133 			if (errno == ENAMETOOLONG && outsz != 0 &&
134 			    outsz <= (FM_IOC_OUT_MAXBUFSZ / 2)) {
135 				umem_free(outbuf, outsz);
136 				outbuf = NULL;
137 				outsz *= 2;
138 			} else {
139 				err = errno;
140 				break;
141 			}
142 		} else if (outnvlp != NULL) {
143 			err = nvlist_unpack(fid.fid_outbuf, fid.fid_outsz,
144 			    outnvlp, 0);
145 			break;
146 		} else {
147 			break;
148 		}
149 	}
150 
151 	if (inbuf != NULL)
152 		umem_free(inbuf, insz);
153 	if (outbuf != NULL)
154 		umem_free(outbuf, outsz);
155 
156 	return (err);
157 }
158 
159 /*
160  * Open /dev/fm and return a handle.  ver is the overall interface version.
161  */
162 static fmd_agent_hdl_t *
fmd_agent_open_dev(int ver,int mode)163 fmd_agent_open_dev(int ver, int mode)
164 {
165 	fmd_agent_hdl_t *hdl;
166 	int fd, err;
167 	nvlist_t *nvl;
168 
169 	if ((fd = open("/dev/fm", mode)) < 0)
170 		return (NULL); /* errno is set for us */
171 
172 	if ((hdl = umem_alloc(sizeof (fmd_agent_hdl_t),
173 	    UMEM_DEFAULT)) == NULL) {
174 		err = errno;
175 		(void) close(fd);
176 		errno = err;
177 		return (NULL);
178 	}
179 
180 	hdl->agent_devfd = fd;
181 	hdl->agent_version = ver;
182 
183 	/*
184 	 * Get the individual interface versions.
185 	 */
186 	if ((err = fmd_agent_nvl_ioctl(hdl, FM_IOC_VERSIONS, ver, NULL, &nvl))
187 	    < 0) {
188 		(void) close(fd);
189 		umem_free(hdl, sizeof (fmd_agent_hdl_t));
190 		errno = err;
191 		return (NULL);
192 	}
193 
194 	hdl->agent_ioc_versions = nvl;
195 	return (hdl);
196 }
197 
198 fmd_agent_hdl_t *
fmd_agent_open(int ver)199 fmd_agent_open(int ver)
200 {
201 	if (ver > FMD_AGENT_VERSION) {
202 		errno = ENOTSUP;
203 		return (NULL);
204 	}
205 	return (fmd_agent_open_dev(ver, O_RDONLY));
206 }
207 
208 void
fmd_agent_close(fmd_agent_hdl_t * hdl)209 fmd_agent_close(fmd_agent_hdl_t *hdl)
210 {
211 	(void) close(hdl->agent_devfd);
212 	nvlist_free(hdl->agent_ioc_versions);
213 	umem_free(hdl, sizeof (fmd_agent_hdl_t));
214 }
215 
216 /*
217  * Given a interface name, return the kernel interface version.
218  */
219 int
fmd_agent_version(fmd_agent_hdl_t * hdl,const char * op,uint32_t * verp)220 fmd_agent_version(fmd_agent_hdl_t *hdl, const char *op, uint32_t *verp)
221 {
222 	int err;
223 
224 	err = nvlist_lookup_uint32(hdl->agent_ioc_versions,
225 	    op, verp);
226 
227 	if (err != 0) {
228 		errno = err;
229 		return (-1);
230 	}
231 	return (0);
232 }
233 
234 static int
fmd_agent_pageop_v1(fmd_agent_hdl_t * hdl,int cmd,nvlist_t * fmri)235 fmd_agent_pageop_v1(fmd_agent_hdl_t *hdl, int cmd, nvlist_t *fmri)
236 {
237 	int err;
238 	nvlist_t *nvl = NULL;
239 
240 	if ((err = nvlist_alloc(&nvl, NV_UNIQUE_NAME_TYPE, 0)) != 0 ||
241 	    (err = nvlist_add_nvlist(nvl, FM_PAGE_RETIRE_FMRI, fmri)) != 0 ||
242 	    (err = fmd_agent_nvl_ioctl(hdl, cmd, 1, nvl, NULL)) != 0)
243 		return (cleanup_set_errno(hdl, nvl, NULL, err));
244 
245 	nvlist_free(nvl);
246 	return (0);
247 }
248 
249 static int
fmd_agent_pageop(fmd_agent_hdl_t * hdl,int cmd,nvlist_t * fmri)250 fmd_agent_pageop(fmd_agent_hdl_t *hdl, int cmd, nvlist_t *fmri)
251 {
252 	uint32_t ver;
253 
254 	if (fmd_agent_version(hdl, FM_PAGE_OP_VERSION, &ver) == -1)
255 		return (fmd_agent_seterrno(hdl, errno));
256 
257 	switch (ver) {
258 	case 1:
259 		return (fmd_agent_pageop_v1(hdl, cmd, fmri));
260 
261 	default:
262 		return (fmd_agent_seterrno(hdl, ENOTSUP));
263 	}
264 }
265 
266 int
fmd_agent_page_retire(fmd_agent_hdl_t * hdl,nvlist_t * fmri)267 fmd_agent_page_retire(fmd_agent_hdl_t *hdl, nvlist_t *fmri)
268 {
269 	int rc = fmd_agent_pageop(hdl, FM_IOC_PAGE_RETIRE, fmri);
270 	int err = fmd_agent_errno(hdl);
271 
272 	/*
273 	 * FM_IOC_PAGE_RETIRE ioctl returns:
274 	 *   0 - success in retiring page
275 	 *   -1, errno = EIO - page is already retired
276 	 *   -1, errno = EAGAIN - page is scheduled for retirement
277 	 *   -1, errno = EINVAL - page fmri is invalid
278 	 *   -1, errno = any else - error
279 	 */
280 	if (rc == 0 || err == EIO || err == EINVAL) {
281 		if (rc == 0)
282 			(void) fmd_agent_seterrno(hdl, 0);
283 		return (FMD_AGENT_RETIRE_DONE);
284 	}
285 	if (err == EAGAIN)
286 		return (FMD_AGENT_RETIRE_ASYNC);
287 
288 	return (FMD_AGENT_RETIRE_FAIL);
289 }
290 
291 int
fmd_agent_page_unretire(fmd_agent_hdl_t * hdl,nvlist_t * fmri)292 fmd_agent_page_unretire(fmd_agent_hdl_t *hdl, nvlist_t *fmri)
293 {
294 	int rc = fmd_agent_pageop(hdl, FM_IOC_PAGE_UNRETIRE, fmri);
295 	int err = fmd_agent_errno(hdl);
296 
297 	/*
298 	 * FM_IOC_PAGE_UNRETIRE ioctl returns:
299 	 *   0 - success in unretiring page
300 	 *   -1, errno = EIO - page is already unretired
301 	 *   -1, errno = EAGAIN - page couldn't be locked, still retired
302 	 *   -1, errno = EINVAL - page fmri is invalid
303 	 *   -1, errno = any else - error
304 	 */
305 	if (rc == 0 || err == EIO || err == EINVAL) {
306 		if (rc == 0)
307 			(void) fmd_agent_seterrno(hdl, 0);
308 		return (FMD_AGENT_RETIRE_DONE);
309 	}
310 
311 	return (FMD_AGENT_RETIRE_FAIL);
312 }
313 
314 int
fmd_agent_page_isretired(fmd_agent_hdl_t * hdl,nvlist_t * fmri)315 fmd_agent_page_isretired(fmd_agent_hdl_t *hdl, nvlist_t *fmri)
316 {
317 	int rc = fmd_agent_pageop(hdl, FM_IOC_PAGE_STATUS, fmri);
318 	int err = fmd_agent_errno(hdl);
319 
320 	/*
321 	 * FM_IOC_PAGE_STATUS returns:
322 	 *   0 - page is retired
323 	 *   -1, errno = EAGAIN - page is scheduled for retirement
324 	 *   -1, errno = EIO - page not scheduled for retirement
325 	 *   -1, errno = EINVAL - page fmri is invalid
326 	 *   -1, errno = any else - error
327 	 */
328 	if (rc == 0 || err == EINVAL) {
329 		if (rc == 0)
330 			(void) fmd_agent_seterrno(hdl, 0);
331 		return (FMD_AGENT_RETIRE_DONE);
332 	}
333 	if (err == EAGAIN)
334 		return (FMD_AGENT_RETIRE_ASYNC);
335 
336 	return (FMD_AGENT_RETIRE_FAIL);
337 }
338 
339 void
fmd_agent_cache_info_free(fmd_agent_hdl_t * hdl __unused,fmd_agent_cpu_cache_list_t * cache)340 fmd_agent_cache_info_free(fmd_agent_hdl_t *hdl __unused,
341     fmd_agent_cpu_cache_list_t *cache)
342 {
343 	for (uint_t cpuno = 0; cpuno < cache->fmc_ncpus; cpuno++) {
344 		fmd_agent_cpu_cache_t *cpu_cache = &cache->fmc_cpus[cpuno];
345 
346 		if (cpu_cache->fmcc_caches == NULL)
347 			continue;
348 
349 		for (uint_t cacheno = 0; cacheno < cpu_cache->fmcc_ncaches;
350 		    cacheno++) {
351 			nvlist_free(cpu_cache->fmcc_caches[cacheno]);
352 		}
353 
354 		umem_free(cpu_cache->fmcc_caches, sizeof (nvlist_t *) *
355 		    cpu_cache->fmcc_ncaches);
356 		cpu_cache->fmcc_caches = NULL;
357 	}
358 
359 	if (cache->fmc_cpus != NULL) {
360 		umem_free(cache->fmc_cpus, sizeof (fmd_agent_cpu_cache_t) *
361 		    cache->fmc_ncpus);
362 		cache->fmc_cpus = NULL;
363 		cache->fmc_ncpus = 0;
364 	}
365 }
366 
367 static int
fmd_agent_cache_info_pop_cpu(fmd_agent_cpu_cache_list_t * cache,nvlist_t * cpu_nvl,uint_t cpuno)368 fmd_agent_cache_info_pop_cpu(fmd_agent_cpu_cache_list_t *cache,
369     nvlist_t *cpu_nvl, uint_t cpuno)
370 {
371 	int ret;
372 	char cpustr[32];
373 	nvlist_t **cache_nvls;
374 	uint_t ncache_nvls;
375 	fmd_agent_cpu_cache_t *cpu = &cache->fmc_cpus[cpuno];
376 
377 	(void) snprintf(cpustr, sizeof (cpustr), "%u", cpuno);
378 	if ((ret = nvlist_lookup_nvlist_array(cpu_nvl, cpustr, &cache_nvls,
379 	    &ncache_nvls)) != 0) {
380 		return (ret);
381 	}
382 
383 	if (ncache_nvls == 0) {
384 		cpu->fmcc_ncaches = 0;
385 		cpu->fmcc_caches = NULL;
386 		return (0);
387 	}
388 
389 	cpu->fmcc_caches = umem_zalloc(sizeof (nvlist_t *) * ncache_nvls,
390 	    UMEM_DEFAULT);
391 	if (cpu->fmcc_caches == NULL) {
392 		return (errno);
393 	}
394 	cpu->fmcc_ncaches = ncache_nvls;
395 	for (uint_t i = 0; i < cpu->fmcc_ncaches; i++) {
396 		ret = nvlist_dup(cache_nvls[i], &cpu->fmcc_caches[i], 0);
397 		if (ret != 0) {
398 			return (ret);
399 		}
400 	}
401 
402 	return (0);
403 }
404 
405 int
fmd_agent_cache_info(fmd_agent_hdl_t * hdl,fmd_agent_cpu_cache_list_t * cache)406 fmd_agent_cache_info(fmd_agent_hdl_t *hdl, fmd_agent_cpu_cache_list_t *cache)
407 {
408 	int err, ret = 0;
409 	uint32_t ncpus;
410 	nvlist_t *nvl = NULL;
411 
412 	bzero(cache, sizeof (fmd_agent_cpu_cache_list_t));
413 	if ((err = fmd_agent_nvl_ioctl(hdl, FM_IOC_CACHE_INFO, 1, NULL,
414 	    &nvl)) != 0) {
415 		ret = fmd_agent_seterrno(hdl, err);
416 		goto out;
417 	}
418 
419 	if ((err = nvlist_lookup_uint32(nvl, FM_CACHE_INFO_NCPUS, &ncpus)) !=
420 	    0) {
421 		ret = fmd_agent_seterrno(hdl, err);
422 		goto out;
423 	}
424 
425 	cache->fmc_cpus = umem_zalloc(sizeof (fmd_agent_cpu_cache_t) * ncpus,
426 	    UMEM_DEFAULT);
427 	if (cache->fmc_cpus == NULL) {
428 		ret = fmd_agent_seterrno(hdl, errno);
429 		goto out;
430 	}
431 	cache->fmc_ncpus = ncpus;
432 	for (uint_t i = 0; i < cache->fmc_ncpus; i++) {
433 		if ((err = fmd_agent_cache_info_pop_cpu(cache, nvl, i)) != 0) {
434 			ret = fmd_agent_seterrno(hdl, errno);
435 			goto out;
436 		}
437 
438 	}
439 out:
440 	if (ret != 0) {
441 		fmd_agent_cache_info_free(hdl, cache);
442 	}
443 	nvlist_free(nvl);
444 	return (ret);
445 }
446