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  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
  23  * Use is subject to license terms.
  24  */
  25 
  26 #include <sys/stat.h>
  27 #include <sys/types.h>
  28 #include <sys/param.h>
  29 #include <sys/cred.h>
  30 #include <sys/policy.h>
  31 #include <sys/file.h>
  32 #include <sys/errno.h>
  33 #include <sys/modctl.h>
  34 #include <sys/ddi.h>
  35 #include <sys/sunddi.h>
  36 #include <sys/conf.h>
  37 #include <sys/debug.h>
  38 #include <sys/systeminfo.h>
  39 
  40 #include <sys/fm/protocol.h>
  41 #include <sys/devfm.h>
  42 
  43 extern int fm_get_paddr(nvlist_t *, uint64_t *);
  44 #if defined(__x86)
  45 extern int fm_ioctl_physcpu_info(int, nvlist_t *, nvlist_t **);
  46 extern int fm_ioctl_cpu_retire(int, nvlist_t *, nvlist_t **);
  47 extern int fm_ioctl_gentopo_legacy(int, nvlist_t *, nvlist_t **);
  48 #endif /* __x86 */
  49 
  50 static int fm_ioctl_versions(int, nvlist_t *, nvlist_t **);
  51 static int fm_ioctl_page_retire(int, nvlist_t *, nvlist_t **);
  52 
  53 /*
  54  * The driver's capabilities are strictly versioned, allowing userland patching
  55  * without a reboot.  The userland should start with a FM_VERSIONS ioctl to
  56  * query the versions of the kernel interfaces, then it's all userland's
  57  * responsibility to prepare arguments etc to match the current kenrel.
  58  * The version of FM_VERSIONS itself is FM_DRV_VERSION.
  59  */
  60 typedef struct fm_version {
  61         char            *interface;     /* interface name */
  62         uint32_t        version;        /* interface version */
  63 } fm_vers_t;
  64 
  65 typedef struct fm_subroutine {
  66         int             cmd;            /* ioctl cmd */
  67         boolean_t       priv;           /* require privilege */
  68         char            *version;       /* version name */
  69         int             (*func)(int, nvlist_t *, nvlist_t **);  /* handler */
  70 } fm_subr_t;
  71 
  72 static const fm_vers_t fm_versions[] = {
  73         { FM_VERSIONS_VERSION, FM_DRV_VERSION },
  74         { FM_PAGE_OP_VERSION, 1 },
  75         { FM_CPU_OP_VERSION, 1 },
  76         { FM_CPU_INFO_VERSION, 1 },
  77         { FM_TOPO_LEGACY_VERSION, 1 },
  78         { NULL, 0 }
  79 };
  80 
  81 static const fm_subr_t fm_subrs[] = {
  82         { FM_IOC_VERSIONS, B_FALSE, FM_VERSIONS_VERSION, fm_ioctl_versions },
  83         { FM_IOC_PAGE_RETIRE, B_TRUE, FM_PAGE_OP_VERSION,
  84             fm_ioctl_page_retire },
  85         { FM_IOC_PAGE_STATUS, B_FALSE, FM_PAGE_OP_VERSION,
  86             fm_ioctl_page_retire },
  87         { FM_IOC_PAGE_UNRETIRE, B_TRUE, FM_PAGE_OP_VERSION,
  88             fm_ioctl_page_retire },
  89 #if defined(__x86)
  90         { FM_IOC_PHYSCPU_INFO, B_FALSE, FM_CPU_INFO_VERSION,
  91             fm_ioctl_physcpu_info },
  92         { FM_IOC_CPU_RETIRE, B_TRUE, FM_CPU_OP_VERSION,
  93             fm_ioctl_cpu_retire },
  94         { FM_IOC_CPU_STATUS, B_FALSE, FM_CPU_OP_VERSION,
  95             fm_ioctl_cpu_retire },
  96         { FM_IOC_CPU_UNRETIRE, B_TRUE, FM_CPU_OP_VERSION,
  97             fm_ioctl_cpu_retire },
  98         { FM_IOC_GENTOPO_LEGACY, B_FALSE, FM_TOPO_LEGACY_VERSION,
  99             fm_ioctl_gentopo_legacy },
 100 #endif  /* __x86 */
 101         { -1, B_FALSE, NULL, NULL },
 102 };
 103 
 104 static dev_info_t *fm_dip;
 105 static boolean_t is_i86xpv;
 106 static nvlist_t *fm_vers_nvl;
 107 
 108 static int
 109 fm_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
 110 {
 111         switch (cmd) {
 112         case DDI_ATTACH:
 113                 if (ddi_create_minor_node(dip, ddi_get_name(dip), S_IFCHR,
 114                     ddi_get_instance(dip), DDI_PSEUDO, 0) != DDI_SUCCESS) {
 115                         ddi_remove_minor_node(dip, NULL);
 116                         return (DDI_FAILURE);
 117                 }
 118                 fm_dip = dip;
 119                 is_i86xpv = (strcmp(platform, "i86xpv") == 0);
 120                 break;
 121         case DDI_RESUME:
 122                 break;
 123         default:
 124                 return (DDI_FAILURE);
 125         }
 126         return (DDI_SUCCESS);
 127 }
 128 
 129 static int
 130 fm_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
 131 {
 132         int ret = DDI_SUCCESS;
 133 
 134         switch (cmd) {
 135         case DDI_DETACH:
 136                 ddi_remove_minor_node(dip, NULL);
 137                 fm_dip = NULL;
 138                 break;
 139         default:
 140                 ret = DDI_FAILURE;
 141         }
 142         return (ret);
 143 }
 144 
 145 /*ARGSUSED*/
 146 static int
 147 fm_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result)
 148 {
 149         int error;
 150 
 151         switch (infocmd) {
 152         case DDI_INFO_DEVT2DEVINFO:
 153                 *result = fm_dip;
 154                 error = DDI_SUCCESS;
 155                 break;
 156         case DDI_INFO_DEVT2INSTANCE:
 157                 *result = NULL;
 158                 error = DDI_SUCCESS;
 159                 break;
 160         default:
 161                 error = DDI_FAILURE;
 162         }
 163         return (error);
 164 }
 165 
 166 /*ARGSUSED1*/
 167 static int
 168 fm_open(dev_t *devp, int flag, int typ, struct cred *cred)
 169 {
 170         if (typ != OTYP_CHR)
 171                 return (EINVAL);
 172         if (getminor(*devp) != 0)
 173                 return (ENXIO);
 174 
 175         return (0);
 176 }
 177 
 178 /*ARGSUSED*/
 179 static int
 180 fm_ioctl_versions(int cmd, nvlist_t *invl, nvlist_t **onvlp)
 181 {
 182         nvlist_t *nvl;
 183         int err;
 184 
 185         if ((err = nvlist_dup(fm_vers_nvl, &nvl, KM_SLEEP)) == 0)
 186                 *onvlp = nvl;
 187 
 188         return (err);
 189 }
 190 
 191 /*
 192  * Given a mem-scheme FMRI for a page, execute the given page retire
 193  * command on it.
 194  */
 195 /*ARGSUSED*/
 196 static int
 197 fm_ioctl_page_retire(int cmd, nvlist_t *invl, nvlist_t **onvlp)
 198 {
 199         uint64_t pa;
 200         nvlist_t *fmri;
 201         int err;
 202 
 203         if (is_i86xpv)
 204                 return (ENOTSUP);
 205 
 206         if ((err = nvlist_lookup_nvlist(invl, FM_PAGE_RETIRE_FMRI, &fmri))
 207             != 0)
 208                 return (err);
 209 
 210         if ((err = fm_get_paddr(fmri, &pa)) != 0)
 211                 return (err);
 212 
 213         switch (cmd) {
 214         case FM_IOC_PAGE_STATUS:
 215                 return (page_retire_check(pa, NULL));
 216 
 217         case FM_IOC_PAGE_RETIRE:
 218                 return (page_retire(pa, PR_FMA));
 219 
 220         case FM_IOC_PAGE_UNRETIRE:
 221                 return (page_unretire(pa));
 222         }
 223 
 224         return (ENOTTY);
 225 }
 226 
 227 /*ARGSUSED*/
 228 static int
 229 fm_ioctl(dev_t dev, int cmd, intptr_t data, int flag, cred_t *cred, int *rvalp)
 230 {
 231         char *buf;
 232         int err;
 233         uint_t model;
 234         const fm_subr_t *subr;
 235         uint32_t vers;
 236         fm_ioc_data_t fid;
 237         nvlist_t *invl = NULL, *onvl = NULL;
 238 #ifdef _MULTI_DATAMODEL
 239         fm_ioc_data32_t fid32;
 240 #endif
 241 
 242         if (getminor(dev) != 0)
 243                 return (ENXIO);
 244 
 245         for (subr = fm_subrs; subr->cmd != cmd; subr++)
 246                 if (subr->cmd == -1)
 247                         return (ENOTTY);
 248 
 249         if (subr->priv && (flag & FWRITE) == 0 &&
 250             secpolicy_sys_config(CRED(), 0) != 0)
 251                 return (EPERM);
 252 
 253         model = ddi_model_convert_from(flag & FMODELS);
 254 
 255         switch (model) {
 256 #ifdef _MULTI_DATAMODEL
 257         case DDI_MODEL_ILP32:
 258                 if (ddi_copyin((void *)data, &fid32,
 259                     sizeof (fm_ioc_data32_t), flag) != 0)
 260                         return (EFAULT);
 261                 fid.fid_version = fid32.fid_version;
 262                 fid.fid_insz = fid32.fid_insz;
 263                 fid.fid_inbuf = (caddr_t)(uintptr_t)fid32.fid_inbuf;
 264                 fid.fid_outsz = fid32.fid_outsz;
 265                 fid.fid_outbuf = (caddr_t)(uintptr_t)fid32.fid_outbuf;
 266                 break;
 267 #endif /* _MULTI_DATAMODEL */
 268         case DDI_MODEL_NONE:
 269         default:
 270                 if (ddi_copyin((void *)data, &fid, sizeof (fm_ioc_data_t),
 271                     flag) != 0)
 272                         return (EFAULT);
 273         }
 274 
 275         if (nvlist_lookup_uint32(fm_vers_nvl, subr->version, &vers) != 0 ||
 276             fid.fid_version != vers)
 277                 return (ENOTSUP);
 278 
 279         if (fid.fid_insz > FM_IOC_MAXBUFSZ)
 280                 return (ENAMETOOLONG);
 281         if (fid.fid_outsz > FM_IOC_OUT_MAXBUFSZ)
 282                 return (EINVAL);
 283 
 284         /*
 285          * Copy in and unpack the input nvlist.
 286          */
 287         if (fid.fid_insz != 0 && fid.fid_inbuf != (caddr_t)0) {
 288                 buf = kmem_alloc(fid.fid_insz, KM_SLEEP);
 289                 if (ddi_copyin(fid.fid_inbuf, buf, fid.fid_insz, flag) != 0) {
 290                         kmem_free(buf, fid.fid_insz);
 291                         return (EFAULT);
 292                 }
 293                 err = nvlist_unpack(buf, fid.fid_insz, &invl, KM_SLEEP);
 294                 kmem_free(buf, fid.fid_insz);
 295                 if (err != 0)
 296                         return (err);
 297         }
 298 
 299         err = subr->func(cmd, invl, &onvl);
 300 
 301         nvlist_free(invl);
 302 
 303         if (err != 0) {
 304                 nvlist_free(onvl);
 305                 return (err);
 306         }
 307 
 308         /*
 309          * If the output nvlist contains any data, pack it and copyout.
 310          */
 311         if (onvl != NULL) {
 312                 size_t sz;
 313 
 314                 if ((err = nvlist_size(onvl, &sz, NV_ENCODE_NATIVE)) != 0) {
 315                         nvlist_free(onvl);
 316                         return (err);
 317                 }
 318                 if (sz > fid.fid_outsz) {
 319                         nvlist_free(onvl);
 320                         return (ENAMETOOLONG);
 321                 }
 322 
 323                 buf = kmem_alloc(sz, KM_SLEEP);
 324                 if ((err = nvlist_pack(onvl, &buf, &sz, NV_ENCODE_NATIVE,
 325                     KM_SLEEP)) != 0) {
 326                         kmem_free(buf, sz);
 327                         nvlist_free(onvl);
 328                         return (err);
 329                 }
 330                 nvlist_free(onvl);
 331                 if (ddi_copyout(buf, fid.fid_outbuf, sz, flag) != 0) {
 332                         kmem_free(buf, sz);
 333                         return (EFAULT);
 334                 }
 335                 kmem_free(buf, sz);
 336                 fid.fid_outsz = sz;
 337 
 338                 switch (model) {
 339 #ifdef _MULTI_DATAMODEL
 340                 case DDI_MODEL_ILP32:
 341                         fid32.fid_outsz = (size32_t)fid.fid_outsz;
 342                         if (ddi_copyout(&fid32, (void *)data,
 343                             sizeof (fm_ioc_data32_t), flag) != 0)
 344                                 return (EFAULT);
 345                         break;
 346 #endif /* _MULTI_DATAMODEL */
 347                 case DDI_MODEL_NONE:
 348                 default:
 349                         if (ddi_copyout(&fid, (void *)data,
 350                             sizeof (fm_ioc_data_t), flag) != 0)
 351                                 return (EFAULT);
 352                 }
 353         }
 354 
 355         return (err);
 356 }
 357 
 358 static struct cb_ops fm_cb_ops = {
 359         fm_open,                /* open */
 360         nulldev,                /* close */
 361         nodev,                  /* strategy */
 362         nodev,                  /* print */
 363         nodev,                  /* dump */
 364         nodev,                  /* read */
 365         nodev,                  /* write */
 366         fm_ioctl,               /* ioctl */
 367         nodev,                  /* devmap */
 368         nodev,                  /* mmap */
 369         nodev,                  /* segmap */
 370         nochpoll,               /* poll */
 371         ddi_prop_op,            /* prop_op */
 372         NULL,                   /* streamtab  */
 373         D_NEW | D_MP | D_64BIT | D_U64BIT
 374 };
 375 
 376 static struct dev_ops fm_ops = {
 377         DEVO_REV,               /* devo_rev, */
 378         0,                      /* refcnt  */
 379         fm_info,                /* get_dev_info */
 380         nulldev,                /* identify */
 381         nulldev,                /* probe */
 382         fm_attach,              /* attach */
 383         fm_detach,              /* detach */
 384         nodev,                  /* reset */
 385         &fm_cb_ops,         /* driver operations */
 386         (struct bus_ops *)0     /* bus operations */
 387 };
 388 
 389 static struct modldrv modldrv = {
 390         &mod_driverops, "fault management driver", &fm_ops,
 391 };
 392 
 393 static struct modlinkage modlinkage = {
 394         MODREV_1, &modldrv, NULL
 395 };
 396 
 397 int
 398 _init(void)
 399 {
 400         const fm_vers_t *p;
 401         int ret;
 402 
 403 
 404         if ((ret = mod_install(&modlinkage)) == 0) {
 405                 (void) nvlist_alloc(&fm_vers_nvl, NV_UNIQUE_NAME, KM_SLEEP);
 406                 for (p = fm_versions; p->interface != NULL; p++)
 407                         (void) nvlist_add_uint32(fm_vers_nvl, p->interface,
 408                             p->version);
 409         }
 410 
 411         return (ret);
 412 }
 413 
 414 int
 415 _info(struct modinfo *modinfop)
 416 {
 417         return (mod_info(&modlinkage, modinfop));
 418 }
 419 
 420 int
 421 _fini(void)
 422 {
 423         int ret;
 424 
 425         if ((ret = mod_remove(&modlinkage)) == 0) {
 426                 nvlist_free(fm_vers_nvl);
 427         }
 428 
 429         return (ret);
 430 }