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         nvlist_free(innvl);
  78         nvlist_free(outnvl);
  79         return (fmd_agent_seterrno(hdl, err));
  80 }
  81 
  82 /*
  83  * Perform /dev/fm ioctl.  The input and output data are represented by
  84  * name-value lists (nvlists).
  85  */
  86 int
  87 fmd_agent_nvl_ioctl(fmd_agent_hdl_t *hdl, int cmd, uint32_t ver,
  88     nvlist_t *innvl, nvlist_t **outnvlp)
  89 {
  90         fm_ioc_data_t fid;
  91         int err = 0;
  92         char *inbuf = NULL, *outbuf = NULL;
  93         size_t insz = 0, outsz = 0;
  94 
  95         if (innvl != NULL) {
  96                 if ((err = nvlist_size(innvl, &insz, NV_ENCODE_NATIVE)) != 0)
  97                         return (err);
  98                 if (insz > FM_IOC_MAXBUFSZ)
  99                         return (ENAMETOOLONG);
 100                 if ((inbuf = umem_alloc(insz, UMEM_DEFAULT)) == NULL)
 101                         return (errno);
 102 
 103                 if ((err = nvlist_pack(innvl, &inbuf, &insz,
 104                     NV_ENCODE_NATIVE, 0)) != 0) {
 105                         umem_free(inbuf, insz);
 106                         return (err);
 107                 }
 108         }
 109 
 110         if (outnvlp != NULL) {
 111                 outsz = FM_IOC_OUT_BUFSZ;
 112         }
 113         for (;;) {
 114                 if (outnvlp != NULL) {
 115                         outbuf = umem_alloc(outsz, UMEM_DEFAULT);
 116                         if (outbuf == NULL) {
 117                                 err = errno;
 118                                 break;
 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                         if (errno == ENAMETOOLONG && outsz != 0 &&
 130                             outsz < (FM_IOC_OUT_MAXBUFSZ / 2)) {
 131                                 umem_free(outbuf, outsz);
 132                                 outsz *= 2;
 133                                 outbuf = umem_alloc(outsz, UMEM_DEFAULT);
 134                                 if (outbuf == NULL) {
 135                                         err = errno;
 136                                         break;
 137                                 }
 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 *
 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 *
 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
 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
 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
 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
 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
 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
 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
 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 }