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         if (invl != NULL)
 302                 nvlist_free(invl);
 303 
 304         if (err != 0) {
 305                 if (onvl != NULL)
 306                         nvlist_free(onvl);
 307                 return (err);
 308         }
 309 
 310         /*
 311          * If the output nvlist contains any data, pack it and copyout.
 312          */
 313         if (onvl != NULL) {
 314                 size_t sz;
 315 
 316                 if ((err = nvlist_size(onvl, &sz, NV_ENCODE_NATIVE)) != 0) {
 317                         nvlist_free(onvl);
 318                         return (err);
 319                 }
 320                 if (sz > fid.fid_outsz) {
 321                         nvlist_free(onvl);
 322                         return (ENAMETOOLONG);
 323                 }
 324 
 325                 buf = kmem_alloc(sz, KM_SLEEP);
 326                 if ((err = nvlist_pack(onvl, &buf, &sz, NV_ENCODE_NATIVE,
 327                     KM_SLEEP)) != 0) {
 328                         kmem_free(buf, sz);
 329                         nvlist_free(onvl);
 330                         return (err);
 331                 }
 332                 nvlist_free(onvl);
 333                 if (ddi_copyout(buf, fid.fid_outbuf, sz, flag) != 0) {
 334                         kmem_free(buf, sz);
 335                         return (EFAULT);
 336                 }
 337                 kmem_free(buf, sz);
 338                 fid.fid_outsz = sz;
 339 
 340                 switch (model) {
 341 #ifdef _MULTI_DATAMODEL
 342                 case DDI_MODEL_ILP32:
 343                         fid32.fid_outsz = (size32_t)fid.fid_outsz;
 344                         if (ddi_copyout(&fid32, (void *)data,
 345                             sizeof (fm_ioc_data32_t), flag) != 0)
 346                                 return (EFAULT);
 347                         break;
 348 #endif /* _MULTI_DATAMODEL */
 349                 case DDI_MODEL_NONE:
 350                 default:
 351                         if (ddi_copyout(&fid, (void *)data,
 352                             sizeof (fm_ioc_data_t), flag) != 0)
 353                                 return (EFAULT);
 354                 }
 355         }
 356 
 357         return (err);
 358 }
 359 
 360 static struct cb_ops fm_cb_ops = {
 361         fm_open,                /* open */
 362         nulldev,                /* close */
 363         nodev,                  /* strategy */
 364         nodev,                  /* print */
 365         nodev,                  /* dump */
 366         nodev,                  /* read */
 367         nodev,                  /* write */
 368         fm_ioctl,               /* ioctl */
 369         nodev,                  /* devmap */
 370         nodev,                  /* mmap */
 371         nodev,                  /* segmap */
 372         nochpoll,               /* poll */
 373         ddi_prop_op,            /* prop_op */
 374         NULL,                   /* streamtab  */
 375         D_NEW | D_MP | D_64BIT | D_U64BIT
 376 };
 377 
 378 static struct dev_ops fm_ops = {
 379         DEVO_REV,               /* devo_rev, */
 380         0,                      /* refcnt  */
 381         fm_info,                /* get_dev_info */
 382         nulldev,                /* identify */
 383         nulldev,                /* probe */
 384         fm_attach,              /* attach */
 385         fm_detach,              /* detach */
 386         nodev,                  /* reset */
 387         &fm_cb_ops,         /* driver operations */
 388         (struct bus_ops *)0     /* bus operations */
 389 };
 390 
 391 static struct modldrv modldrv = {
 392         &mod_driverops, "fault management driver", &fm_ops,
 393 };
 394 
 395 static struct modlinkage modlinkage = {
 396         MODREV_1, &modldrv, NULL
 397 };
 398 
 399 int
 400 _init(void)
 401 {
 402         const fm_vers_t *p;
 403         int ret;
 404 
 405 
 406         if ((ret = mod_install(&modlinkage)) == 0) {
 407                 (void) nvlist_alloc(&fm_vers_nvl, NV_UNIQUE_NAME, KM_SLEEP);
 408                 for (p = fm_versions; p->interface != NULL; p++)
 409                         (void) nvlist_add_uint32(fm_vers_nvl, p->interface,
 410                             p->version);
 411         }
 412 
 413         return (ret);
 414 }
 415 
 416 int
 417 _info(struct modinfo *modinfop)
 418 {
 419         return (mod_info(&modlinkage, modinfop));
 420 }
 421 
 422 int
 423 _fini(void)
 424 {
 425         int ret;
 426 
 427         if ((ret = mod_remove(&modlinkage)) == 0) {
 428                 if (fm_vers_nvl != NULL)
 429                         nvlist_free(fm_vers_nvl);
 430         }
 431 
 432         return (ret);
 433 }