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 2007 Sun Microsystems, Inc.  All rights reserved.
  23  * Use is subject to license terms.
  24  */
  25 
  26 #include <sys/ddi.h>
  27 #include <sys/sunddi.h>
  28 #include <sys/sunndi.h>
  29 #include <sys/ddi_impldefs.h>
  30 #include <sys/ddi_implfuncs.h>
  31 #include <sys/list.h>
  32 #include <sys/reboot.h>
  33 #include <sys/sysmacros.h>
  34 #include <sys/console.h>
  35 #include <sys/devcache.h>
  36 
  37 /*
  38  * The nvpair name in the I/O retire specific sub-nvlist
  39  */
  40 #define RIO_STORE_VERSION_STR   "rio-store-version"
  41 #define RIO_STORE_MAGIC_STR     "rio-store-magic"
  42 #define RIO_STORE_FLAGS_STR     "rio-store-flags"
  43 
  44 #define RIO_STORE_VERSION_1     1
  45 #define RIO_STORE_VERSION       RIO_STORE_VERSION_1
  46 
  47 /*
  48  * decoded retire list element
  49  */
  50 
  51 typedef enum rio_store_flags {
  52         RIO_STORE_F_INVAL = 0,
  53         RIO_STORE_F_RETIRED = 1,
  54         RIO_STORE_F_BYPASS = 2
  55 } rio_store_flags_t;
  56 
  57 typedef struct rio_store {
  58         char                    *rst_devpath;
  59         rio_store_flags_t       rst_flags;
  60         list_node_t             rst_next;
  61 } rio_store_t;
  62 
  63 #define RIO_STORE_MAGIC         0x601fcace      /* retire */
  64 
  65 static int rio_store_decode(nvf_handle_t nvfh, nvlist_t *line_nvl, char *name);
  66 static int rio_store_encode(nvf_handle_t nvfh, nvlist_t **ret_nvl);
  67 static void retire_list_free(nvf_handle_t  nvfh);
  68 
  69 
  70 /*
  71  * Retire I/O persistent store registration info
  72  */
  73 static nvf_ops_t rio_store_ops = {
  74         "/etc/devices/retire_store",    /* path to store */
  75         rio_store_decode,               /* decode nvlist into retire_list */
  76         rio_store_encode,               /* encode retire_list into nvlist */
  77         retire_list_free,               /* free retire_list */
  78         NULL                            /* write complete callback */
  79 };
  80 
  81 static nvf_handle_t     rio_store_handle;
  82 static char             store_path[MAXPATHLEN];
  83 static int              store_debug = 0;
  84 static int              bypass_msg = 0;
  85 static int              retire_msg = 0;
  86 
  87 #define STORE_DEBUG     0x0001
  88 #define STORE_TRACE     0x0002
  89 
  90 #define STORE_DBG(args)         if (store_debug & STORE_DEBUG)      cmn_err args
  91 #define STORE_TRC(args)         if (store_debug & STORE_TRACE)      cmn_err args
  92 
  93 /*
  94  * We don't use the simple read disable offered by the
  95  * caching framework (see devcache.c) as it will not
  96  * have the desired effect of bypassing the persistent
  97  * store. A simple read disable will
  98  *
  99  *      1. cause any additions to the cache to destroy the
 100  *         existing on-disk cache
 101  *
 102  *      2. prevent deletions from the existing on-disk
 103  *         cache which is needed for recovery from bad
 104  *         retire decisions.
 105  *
 106  * Use the following tunable instead
 107  *
 108  */
 109 int     ddi_retire_store_bypass = 0;
 110 
 111 
 112 
 113 /*
 114  * Initialize retire store data structures
 115  */
 116 void
 117 retire_store_init(void)
 118 {
 119         if (boothowto & RB_ASKNAME) {
 120 
 121                 printf("Retire store [%s] (/dev/null to bypass): ",
 122                     rio_store_ops.nvfr_cache_path);
 123                 console_gets(store_path, sizeof (store_path) - 1);
 124                 store_path[sizeof (store_path) - 1] = '\0';
 125 
 126                 if (strcmp(store_path, "/dev/null") == 0) {
 127                         ddi_retire_store_bypass = 1;
 128                 } else if (store_path[0] != '\0') {
 129                         if (store_path[0] != '/') {
 130                                 printf("Invalid store path: %s. Using default"
 131                                     "\n", store_path);
 132                         } else {
 133                                 rio_store_ops.nvfr_cache_path = store_path;
 134                         }
 135                 }
 136         }
 137 
 138         rio_store_handle = nvf_register_file(&rio_store_ops);
 139 
 140         list_create(nvf_list(rio_store_handle), sizeof (rio_store_t),
 141             offsetof(rio_store_t, rst_next));
 142 }
 143 
 144 /*
 145  * Read and populate the in-core retire store
 146  */
 147 void
 148 retire_store_read(void)
 149 {
 150         rw_enter(nvf_lock(rio_store_handle), RW_WRITER);
 151         ASSERT(list_head(nvf_list(rio_store_handle)) == NULL);
 152         (void) nvf_read_file(rio_store_handle);
 153         rw_exit(nvf_lock(rio_store_handle));
 154         STORE_DBG((CE_NOTE, "Read on-disk retire store"));
 155 }
 156 
 157 static void
 158 rio_store_free(rio_store_t *rsp)
 159 {
 160         int flag_mask = RIO_STORE_F_RETIRED|RIO_STORE_F_BYPASS;
 161 
 162         ASSERT(rsp);
 163         ASSERT(rsp->rst_devpath);
 164         ASSERT(rsp->rst_flags & RIO_STORE_F_RETIRED);
 165         ASSERT(!(rsp->rst_flags & ~flag_mask));
 166 
 167         STORE_TRC((CE_NOTE, "store: freed path: %s", rsp->rst_devpath));
 168 
 169         kmem_free(rsp->rst_devpath, strlen(rsp->rst_devpath) + 1);
 170         kmem_free(rsp, sizeof (*rsp));
 171 }
 172 
 173 static void
 174 retire_list_free(nvf_handle_t  nvfh)
 175 {
 176         list_t          *listp;
 177         rio_store_t     *rsp;
 178 
 179         ASSERT(nvfh == rio_store_handle);
 180         ASSERT(RW_WRITE_HELD(nvf_lock(nvfh)));
 181 
 182         listp = nvf_list(nvfh);
 183         while (rsp = list_head(listp)) {
 184                 list_remove(listp, rsp);
 185                 rio_store_free(rsp);
 186         }
 187 
 188         STORE_DBG((CE_NOTE, "store: freed retire list"));
 189 }
 190 
 191 static int
 192 rio_store_decode(nvf_handle_t nvfh, nvlist_t *line_nvl, char *name)
 193 {
 194         rio_store_t     *rsp;
 195         int32_t         version;
 196         int32_t         magic;
 197         int32_t         flags;
 198         int             rval;
 199 
 200         ASSERT(nvfh == rio_store_handle);
 201         ASSERT(RW_WRITE_HELD(nvf_lock(nvfh)));
 202         ASSERT(name);
 203 
 204         version = 0;
 205         rval = nvlist_lookup_int32(line_nvl, RIO_STORE_VERSION_STR, &version);
 206         if (rval != 0 || version != RIO_STORE_VERSION) {
 207                 return (EINVAL);
 208         }
 209 
 210         magic = 0;
 211         rval = nvlist_lookup_int32(line_nvl, RIO_STORE_MAGIC_STR, &magic);
 212         if (rval != 0 || magic != RIO_STORE_MAGIC) {
 213                 return (EINVAL);
 214         }
 215 
 216         flags = 0;
 217         rval = nvlist_lookup_int32(line_nvl, RIO_STORE_FLAGS_STR, &flags);
 218         if (rval != 0 || flags != RIO_STORE_F_RETIRED) {
 219                 return (EINVAL);
 220         }
 221 
 222         if (ddi_retire_store_bypass) {
 223                 flags |= RIO_STORE_F_BYPASS;
 224                 if (!bypass_msg) {
 225                         bypass_msg = 1;
 226                         cmn_err(CE_WARN,
 227                             "Bypassing retire store /etc/devices/retire_store");
 228                 }
 229         }
 230 
 231         rsp = kmem_zalloc(sizeof (rio_store_t), KM_SLEEP);
 232         rsp->rst_devpath = i_ddi_strdup(name, KM_SLEEP);
 233         rsp->rst_flags = flags;
 234         list_insert_tail(nvf_list(nvfh), rsp);
 235 
 236         STORE_TRC((CE_NOTE, "store: added to retire list: %s", name));
 237         if (!retire_msg) {
 238                 retire_msg = 1;
 239                 cmn_err(CE_NOTE, "One or more I/O devices have been retired");
 240         }
 241 
 242         return (0);
 243 }
 244 
 245 static int
 246 rio_store_encode(nvf_handle_t nvfh, nvlist_t **ret_nvl)
 247 {
 248         nvlist_t        *nvl;
 249         nvlist_t        *line_nvl;
 250         list_t          *listp;
 251         rio_store_t     *rsp;
 252         int             rval;
 253 
 254         ASSERT(nvfh == rio_store_handle);
 255         ASSERT(RW_WRITE_HELD(nvf_lock(nvfh)));
 256 
 257         *ret_nvl = NULL;
 258 
 259         nvl = NULL;
 260         rval = nvlist_alloc(&nvl, NV_UNIQUE_NAME, KM_SLEEP);
 261         if (rval != 0) {
 262                 return (DDI_FAILURE);
 263         }
 264 
 265         listp = nvf_list(nvfh);
 266         for (rsp = list_head(listp); rsp; rsp = list_next(listp, rsp)) {
 267                 int flag_mask = RIO_STORE_F_RETIRED|RIO_STORE_F_BYPASS;
 268                 int flags;
 269                 ASSERT(rsp->rst_devpath);
 270                 ASSERT(!(rsp->rst_flags & ~flag_mask));
 271 
 272                 line_nvl = NULL;
 273                 rval = nvlist_alloc(&line_nvl, NV_UNIQUE_NAME, KM_SLEEP);
 274                 if (rval != 0) {
 275                         line_nvl = NULL;
 276                         goto error;
 277                 }
 278 
 279                 rval = nvlist_add_int32(line_nvl, RIO_STORE_VERSION_STR,
 280                         RIO_STORE_VERSION);
 281                 if (rval != 0) {
 282                         goto error;
 283                 }
 284                 rval = nvlist_add_int32(line_nvl, RIO_STORE_MAGIC_STR,
 285                         RIO_STORE_MAGIC);
 286                 if (rval != 0) {
 287                         goto error;
 288                 }
 289 
 290                 /* don't save the bypass flag */
 291                 flags = RIO_STORE_F_RETIRED;
 292                 rval = nvlist_add_int32(line_nvl, RIO_STORE_FLAGS_STR,
 293                         flags);
 294                 if (rval != 0) {
 295                         goto error;
 296                 }
 297 
 298                 rval = nvlist_add_nvlist(nvl, rsp->rst_devpath, line_nvl);
 299                 if (rval != 0) {
 300                         goto error;
 301                 }
 302                 nvlist_free(line_nvl);
 303                 line_nvl = NULL;
 304         }
 305 
 306         *ret_nvl = nvl;
 307         STORE_DBG((CE_NOTE, "packed retire list into nvlist"));
 308         return (DDI_SUCCESS);
 309 
 310 error:
 311         nvlist_free(line_nvl);
 312         ASSERT(nvl);
 313         nvlist_free(nvl);
 314         return (DDI_FAILURE);
 315 }
 316 
 317 int
 318 e_ddi_retire_persist(char *devpath)
 319 {
 320         rio_store_t     *rsp;
 321         rio_store_t     *new_rsp;
 322         list_t          *listp;
 323         char            *new_path;
 324 
 325         STORE_DBG((CE_NOTE, "e_ddi_retire_persist: entered: %s", devpath));
 326 
 327         new_rsp = kmem_zalloc(sizeof (*new_rsp), KM_SLEEP);
 328         new_rsp->rst_devpath = new_path = i_ddi_strdup(devpath, KM_SLEEP);
 329         new_rsp->rst_flags = RIO_STORE_F_RETIRED;
 330 
 331         rw_enter(nvf_lock(rio_store_handle), RW_WRITER);
 332 
 333         listp = nvf_list(rio_store_handle);
 334         for (rsp = list_head(listp); rsp; rsp = list_next(listp, rsp)) {
 335                 int flag_mask = RIO_STORE_F_RETIRED|RIO_STORE_F_BYPASS;
 336                 ASSERT(!(rsp->rst_flags & ~flag_mask));
 337 
 338                 /* already there */
 339                 if (strcmp(devpath, rsp->rst_devpath) == 0) {
 340                         /* explicit retire, clear bypass flag (if any) */
 341                         rsp->rst_flags &= ~RIO_STORE_F_BYPASS;
 342                         ASSERT(rsp->rst_flags == RIO_STORE_F_RETIRED);
 343                         rw_exit(nvf_lock(rio_store_handle));
 344                         kmem_free(new_path, strlen(new_path) + 1);
 345                         kmem_free(new_rsp, sizeof (*new_rsp));
 346                         STORE_DBG((CE_NOTE, "store: already in. Clear bypass "
 347                             ": %s", devpath));
 348                         return (0);
 349                 }
 350 
 351         }
 352 
 353         ASSERT(rsp == NULL);
 354         list_insert_tail(listp, new_rsp);
 355 
 356         nvf_mark_dirty(rio_store_handle);
 357 
 358         rw_exit(nvf_lock(rio_store_handle));
 359 
 360         nvf_wake_daemon();
 361 
 362         STORE_DBG((CE_NOTE, "store: New, added to list, dirty: %s", devpath));
 363 
 364         return (0);
 365 }
 366 
 367 int
 368 e_ddi_retire_unpersist(char *devpath)
 369 {
 370         rio_store_t     *rsp;
 371         rio_store_t     *next;
 372         list_t          *listp;
 373         int             is_dirty = 0;
 374 
 375         STORE_DBG((CE_NOTE, "e_ddi_retire_unpersist: entered: %s", devpath));
 376 
 377         rw_enter(nvf_lock(rio_store_handle), RW_WRITER);
 378 
 379         listp = nvf_list(rio_store_handle);
 380         for (rsp = list_head(listp); rsp; rsp = next) {
 381                 next = list_next(listp, rsp);
 382                 if (strcmp(devpath, rsp->rst_devpath) != 0)
 383                         continue;
 384 
 385                 list_remove(listp, rsp);
 386                 rio_store_free(rsp);
 387 
 388                 STORE_DBG((CE_NOTE, "store: found in list. Freed: %s",
 389                     devpath));
 390 
 391                 nvf_mark_dirty(rio_store_handle);
 392                 is_dirty = 1;
 393         }
 394 
 395         rw_exit(nvf_lock(rio_store_handle));
 396 
 397         if (is_dirty)
 398                 nvf_wake_daemon();
 399 
 400         return (is_dirty);
 401 }
 402 
 403 int
 404 e_ddi_device_retired(char *devpath)
 405 {
 406         list_t          *listp;
 407         rio_store_t     *rsp;
 408         size_t          len;
 409         int             retired;
 410 
 411         retired = 0;
 412 
 413         rw_enter(nvf_lock(rio_store_handle), RW_READER);
 414 
 415         listp = nvf_list(rio_store_handle);
 416         for (rsp = list_head(listp); rsp; rsp = list_next(listp, rsp)) {
 417                 int flag_mask = RIO_STORE_F_RETIRED|RIO_STORE_F_BYPASS;
 418                 ASSERT(!(rsp->rst_flags & ~flag_mask));
 419 
 420                 /*
 421                  * If the "bypass" flag is set, then the device
 422                  * is *not* retired for the current boot of the
 423                  * system. It indicates that the retire store
 424                  * was read but the devices in the retire store
 425                  * were not retired i.e. effectively the store
 426                  * was bypassed. For why we bother to even read
 427                  * the store when we bypass it, see the comments
 428                  * for the tunable ddi_retire_store_bypass.
 429                  */
 430                 if (rsp->rst_flags & RIO_STORE_F_BYPASS) {
 431                         STORE_TRC((CE_NOTE, "store: found & bypassed: %s",
 432                             rsp->rst_devpath));
 433                         continue;
 434                 }
 435 
 436                 /*
 437                  * device is retired, if it or a parent exists
 438                  * in the in-core list
 439                  */
 440                 len = strlen(rsp->rst_devpath);
 441                 if (strncmp(devpath, rsp->rst_devpath, len) != 0)
 442                         continue;
 443                 if (devpath[len] == '\0' || devpath[len] == '/') {
 444                         /* exact match or a child */
 445                         retired = 1;
 446                         STORE_TRC((CE_NOTE, "store: found & !bypassed: %s",
 447                             devpath));
 448                         break;
 449                 }
 450         }
 451         rw_exit(nvf_lock(rio_store_handle));
 452 
 453         return (retired);
 454 }