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
  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_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 *
 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 *
 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
 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
 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
 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
 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
 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
 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
 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 }