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 (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
  23  */
  24 
  25 
  26 /*
  27  * Support routines for managing per-page state.
  28  */
  29 
  30 #include <gmem_page.h>
  31 #include <gmem_mem.h>
  32 #include <gmem_dimm.h>
  33 #include <gmem.h>
  34 
  35 #include <errno.h>
  36 #include <strings.h>
  37 #include <fm/fmd_api.h>
  38 #include <sys/fm/protocol.h>
  39 
  40 static void
  41 page_write(fmd_hdl_t *hdl, gmem_page_t *page)
  42 {
  43         fmd_buf_write(hdl, NULL, page->page_bufname, page,
  44             sizeof (gmem_page_pers_t));
  45 }
  46 
  47 static void
  48 gmem_page_free(fmd_hdl_t *hdl, gmem_page_t *page, int destroy)
  49 {
  50         gmem_case_t *cc = &page->page_case;
  51 
  52         if (cc->cc_cp != NULL)
  53                 gmem_case_fini(hdl, cc->cc_cp, destroy);
  54 
  55         if (cc->cc_serdnm != NULL) {
  56                 if (fmd_serd_exists(hdl, cc->cc_serdnm) && destroy)
  57                         fmd_serd_destroy(hdl, cc->cc_serdnm);
  58                 fmd_hdl_strfree(hdl, cc->cc_serdnm);
  59         }
  60 
  61         if (destroy)
  62                 fmd_buf_destroy(hdl, NULL, page->page_bufname);
  63 
  64         gmem_fmri_fini(hdl, &page->page_asru, destroy);
  65 
  66         gmem_list_delete(&gmem.gm_pages, page);
  67         fmd_hdl_free(hdl, page, sizeof (gmem_page_t));
  68 }
  69 
  70 void
  71 gmem_page_destroy(fmd_hdl_t *hdl, gmem_page_t *page)
  72 {
  73         fmd_hdl_debug(hdl, "destroying the page\n");
  74         gmem_page_free(hdl, page, FMD_B_TRUE);
  75 }
  76 
  77 static gmem_page_t *
  78 page_lookup_by_physaddr(uint64_t pa)
  79 {
  80         gmem_page_t *page;
  81 
  82         for (page = gmem_list_next(&gmem.gm_pages); page != NULL;
  83             page = gmem_list_next(page)) {
  84                 if (page->page_physbase == pa)
  85                         return (page);
  86         }
  87 
  88         return (NULL);
  89 }
  90 
  91 gmem_page_t *
  92 gmem_page_create(fmd_hdl_t *hdl, nvlist_t *modasru, uint64_t pa,
  93     uint64_t offset)
  94 {
  95         gmem_page_t *page;
  96         nvlist_t *asru, *hsp;
  97 
  98         pa = pa & gmem.gm_pagemask;
  99 
 100         fmd_hdl_debug(hdl, "page_lookup: creating new page for %llx\n",
 101             (u_longlong_t)pa);
 102         GMEM_STAT_BUMP(page_creat);
 103 
 104         page = fmd_hdl_zalloc(hdl, sizeof (gmem_page_t), FMD_SLEEP);
 105         page->page_nodetype = GMEM_NT_PAGE;
 106         page->page_version = CMD_PAGE_VERSION;
 107         page->page_physbase = pa;
 108         page->page_offset = offset;
 109 
 110         gmem_bufname(page->page_bufname, sizeof (page->page_bufname),
 111             "page_%llx", (u_longlong_t)pa);
 112 
 113         if (nvlist_dup(modasru, &asru, 0) != 0) {
 114                 fmd_hdl_debug(hdl, "Page create nvlist dup failed");
 115                 return (NULL);
 116         }
 117 
 118         if (nvlist_alloc(&hsp, NV_UNIQUE_NAME, 0) != 0) {
 119                 fmd_hdl_debug(hdl, "Page create nvlist alloc failed");
 120                 nvlist_free(asru);
 121                 return (NULL);
 122         }
 123 
 124         if (nvlist_add_uint64(hsp, FM_FMRI_MEM_PHYSADDR,
 125             page->page_physbase) != 0 ||
 126             nvlist_add_uint64(hsp, FM_FMRI_HC_SPECIFIC_OFFSET,
 127             page->page_offset) != 0 ||
 128             nvlist_add_nvlist(asru, FM_FMRI_HC_SPECIFIC, hsp) != 0) {
 129                 fmd_hdl_debug(hdl, "Page create failed to build page fmri");
 130                 nvlist_free(asru);
 131                 nvlist_free(hsp);
 132                 return (NULL);
 133         }
 134 
 135         gmem_fmri_init(hdl, &page->page_asru, asru, "page_asru_%llx",
 136             (u_longlong_t)pa);
 137 
 138         nvlist_free(asru);
 139         nvlist_free(hsp);
 140 
 141         gmem_list_append(&gmem.gm_pages, page);
 142         page_write(hdl, page);
 143 
 144         return (page);
 145 }
 146 
 147 gmem_page_t *
 148 gmem_page_lookup(uint64_t pa)
 149 {
 150         pa = pa & gmem.gm_pagemask;
 151 
 152         return (page_lookup_by_physaddr(pa));
 153 }
 154 
 155 static gmem_page_t *
 156 page_wrapv0(fmd_hdl_t *hdl, gmem_page_pers_t *pers, size_t psz)
 157 {
 158         gmem_page_t *page;
 159 
 160         if (psz != sizeof (gmem_page_pers_t)) {
 161                 fmd_hdl_abort(hdl, "size of state doesn't match size of "
 162                     "version 0 state (%u bytes).\n", sizeof (gmem_page_pers_t));
 163         }
 164 
 165         page = fmd_hdl_zalloc(hdl, sizeof (gmem_page_t), FMD_SLEEP);
 166         bcopy(pers, page, sizeof (gmem_page_pers_t));
 167         fmd_hdl_free(hdl, pers, psz);
 168         return (page);
 169 }
 170 
 171 void *
 172 gmem_page_restore(fmd_hdl_t *hdl, fmd_case_t *cp, gmem_case_ptr_t *ptr)
 173 {
 174         gmem_page_t *page;
 175 
 176         for (page = gmem_list_next(&gmem.gm_pages); page != NULL;
 177             page = gmem_list_next(page)) {
 178                 if (strcmp(page->page_bufname, ptr->ptr_name) == 0)
 179                         break;
 180         }
 181 
 182         if (page == NULL) {
 183                 size_t pagesz;
 184 
 185                 fmd_hdl_debug(hdl, "restoring page from %s\n", ptr->ptr_name);
 186 
 187                 if ((pagesz = fmd_buf_size(hdl, NULL, ptr->ptr_name)) == 0) {
 188                         if (fmd_case_solved(hdl, cp) ||
 189                             fmd_case_closed(hdl, cp)) {
 190                                 fmd_hdl_debug(hdl, "page %s from case %s not "
 191                                     "found. Case is already solved or closed\n",
 192                                     ptr->ptr_name, fmd_case_uuid(hdl, cp));
 193                                 return (NULL);
 194                         } else {
 195                                 fmd_hdl_abort(hdl, "page referenced by case %s "
 196                                     "does not exist in saved state\n",
 197                                     fmd_case_uuid(hdl, cp));
 198                         }
 199                 } else if (pagesz > CMD_PAGE_MAXSIZE ||
 200                     pagesz < CMD_PAGE_MINSIZE) {
 201                         fmd_hdl_abort(hdl, "page buffer referenced by case %s "
 202                             "is out of bounds (is %u bytes, max %u, min %u)\n",
 203                             fmd_case_uuid(hdl, cp), pagesz,
 204                             CMD_PAGE_MAXSIZE, CMD_PAGE_MINSIZE);
 205                 }
 206 
 207                 if ((page = gmem_buf_read(hdl, NULL, ptr->ptr_name,
 208                     pagesz)) == NULL) {
 209                         fmd_hdl_abort(hdl, "failed to read page buf %s",
 210                             ptr->ptr_name);
 211                 }
 212 
 213                 fmd_hdl_debug(hdl, "found %d in version field\n",
 214                     page->page_version);
 215 
 216                 switch (page->page_version) {
 217                 case CMD_PAGE_VERSION_0:
 218                         page = page_wrapv0(hdl, (gmem_page_pers_t *)page,
 219                             pagesz);
 220                         break;
 221                 default:
 222                         fmd_hdl_abort(hdl, "unknown version (found %d) "
 223                             "for page state referenced by case %s.\n",
 224                             page->page_version, fmd_case_uuid(hdl, cp));
 225                         break;
 226                 }
 227 
 228                 gmem_fmri_restore(hdl, &page->page_asru);
 229 
 230                 gmem_list_append(&gmem.gm_pages, page);
 231         }
 232 
 233         switch (ptr->ptr_subtype) {
 234         case GMEM_PTR_PAGE_CASE:
 235                 gmem_case_restore(hdl, &page->page_case, cp,
 236                     gmem_page_serdnm_create(hdl, "page", page->page_physbase));
 237                 break;
 238         default:
 239                 fmd_hdl_abort(hdl, "invalid %s subtype %d\n",
 240                     ptr->ptr_name, ptr->ptr_subtype);
 241         }
 242 
 243         return (page);
 244 }
 245 
 246 /*ARGSUSED*/
 247 int
 248 gmem_page_unusable(fmd_hdl_t *hdl, gmem_page_t *page)
 249 {
 250         nvlist_t *asru = NULL;
 251         char *sn;
 252 
 253         if (nvlist_lookup_string(page->page_asru_nvl,
 254             FM_FMRI_HC_SERIAL_ID, &sn) != 0)
 255                 return (1);
 256 
 257         /*
 258          * get asru in mem scheme from topology
 259          */
 260         asru = gmem_find_dimm_asru(hdl, sn);
 261         if (asru == NULL)
 262                 return (1);
 263 
 264         (void) nvlist_add_string_array(asru, FM_FMRI_MEM_SERIAL_ID, &sn, 1);
 265         (void) nvlist_add_uint64(asru, FM_FMRI_MEM_PHYSADDR,
 266             page->page_physbase);
 267         (void) nvlist_add_uint64(asru, FM_FMRI_MEM_OFFSET, page->page_offset);
 268 
 269         if (fmd_nvl_fmri_unusable(hdl, asru)) {
 270                 nvlist_free(asru);
 271                 return (1);
 272         }
 273 
 274         nvlist_free(asru);
 275 
 276         return (0);
 277 }
 278 
 279 
 280 /*ARGSUSED*/
 281 void
 282 gmem_page_validate(fmd_hdl_t *hdl)
 283 {
 284         gmem_page_t *page, *next;
 285 
 286         for (page = gmem_list_next(&gmem.gm_pages); page != NULL; page = next) {
 287                 next = gmem_list_next(page);
 288 
 289                 if (gmem_page_unusable(hdl, page))
 290                         gmem_page_destroy(hdl, page);
 291         }
 292 }
 293 
 294 void
 295 gmem_page_dirty(fmd_hdl_t *hdl, gmem_page_t *page)
 296 {
 297         if (fmd_buf_size(hdl, NULL, page->page_bufname) !=
 298             sizeof (gmem_page_pers_t))
 299                 fmd_buf_destroy(hdl, NULL, page->page_bufname);
 300 
 301         /* No need to rewrite the FMRIs in the page - they don't change */
 302         fmd_buf_write(hdl, NULL, page->page_bufname, &page->page_pers,
 303             sizeof (gmem_page_pers_t));
 304 }
 305 
 306 void
 307 gmem_page_fini(fmd_hdl_t *hdl)
 308 {
 309         gmem_page_t *page;
 310 
 311         while ((page = gmem_list_next(&gmem.gm_pages)) != NULL)
 312                 gmem_page_free(hdl, page, FMD_B_FALSE);
 313 }
 314 
 315 
 316 int
 317 gmem_page_fault(fmd_hdl_t *hdl, nvlist_t *fru, nvlist_t *rsc,
 318     fmd_event_t *ep, uint64_t afar, uint64_t offset)
 319 {
 320         gmem_page_t *page = NULL;
 321         const char *uuid;
 322         nvlist_t *flt, *hsp;
 323 
 324         page = gmem_page_lookup(afar);
 325         if (page != NULL) {
 326                 if (page->page_flags & GMEM_F_FAULTING ||
 327                     gmem_page_unusable(hdl, page)) {
 328                         nvlist_free(rsc);
 329                         page->page_flags |= GMEM_F_FAULTING;
 330                         return (0);
 331                 }
 332         } else {
 333                 page = gmem_page_create(hdl, fru, afar, offset);
 334         }
 335 
 336         page->page_flags |= GMEM_F_FAULTING;
 337         if (page->page_case.cc_cp == NULL)
 338                 page->page_case.cc_cp = gmem_case_create(hdl,
 339                     &page->page_header, GMEM_PTR_PAGE_CASE, &uuid);
 340 
 341         if (nvlist_lookup_nvlist(page->page_asru_nvl, FM_FMRI_HC_SPECIFIC,
 342             &hsp) == 0)
 343                 (void) nvlist_add_nvlist(rsc, FM_FMRI_HC_SPECIFIC, hsp);
 344 
 345         flt = fmd_nvl_create_fault(hdl, GMEM_FAULT_PAGE, 100, NULL, fru, rsc);
 346         nvlist_free(rsc);
 347 
 348         if (nvlist_add_boolean_value(flt, FM_SUSPECT_MESSAGE, B_FALSE) != 0)
 349                 fmd_hdl_abort(hdl, "failed to add no-message member to fault");
 350 
 351         fmd_case_add_ereport(hdl, page->page_case.cc_cp, ep);
 352         fmd_case_add_suspect(hdl, page->page_case.cc_cp, flt);
 353         fmd_case_solve(hdl, page->page_case.cc_cp);
 354         return (1);
 355 }
 356 
 357 void
 358 gmem_page_close(fmd_hdl_t *hdl, void *arg)
 359 {
 360         gmem_page_destroy(hdl, arg);
 361 }