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