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, Version 1.0 only 6 * (the "License"). You may not use this file except in compliance 7 * with the License. 8 * 9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10 * or http://www.opensolaris.org/os/licensing. 11 * See the License for the specific language governing permissions 12 * and limitations under the License. 13 * 14 * When distributing Covered Code, include this CDDL HEADER in each 15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16 * If applicable, add the following below this CDDL HEADER, with the 17 * fields enclosed by brackets "[]" replaced with your own identifying 18 * information: Portions Copyright [yyyy] [name of copyright owner] 19 * 20 * CDDL HEADER END 21 */ 22 /* 23 * Copyright 2004 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 #pragma ident "%Z%%M% %I% %E% SMI" 28 29 /* 30 * Page retirement can be an extended process due to the fact that a retirement 31 * may not be possible when the original request is made. The kernel will 32 * repeatedly attempt to retire a given page, but will not let us know when the 33 * page has been retired. We therefore have to poll to see if the retirement 34 * has been completed. This poll is implemented with a bounded exponential 35 * backoff to reduce the burden which we impose upon the system. 36 * 37 * To reduce the burden on fmd in the face of retirement storms, we schedule 38 * all retries as a group. In the simplest case, we attempt to retire a single 39 * page. When forced to retry, we initially schedule a retry at a configurable 40 * interval t. If the retry fails, we schedule another at 2 * t, and so on, 41 * until t reaches the maximum interval (also configurable). Future retries 42 * for that page will occur with t equal to the maximum interval value. We 43 * will never give up on a retirement. 44 * 45 * With multiple retirements, the situation gets slightly more complicated. As 46 * indicated above, we schedule retries as a group. We don't want to deny new 47 * pages their short retry intervals, so we'll (re)set the retry interval to the 48 * value appropriate for the newest page. 49 */ 50 51 #include <cma.h> 52 53 #include <time.h> 54 #include <fcntl.h> 55 #include <errno.h> 56 #include <unistd.h> 57 #include <strings.h> 58 #include <fm/fmd_api.h> 59 #include <sys/fm/protocol.h> 60 #include <sys/mem.h> 61 62 static int 63 cma_page_cmd(int cmd, uint64_t addr) 64 { 65 int fd, rc, err; 66 67 if ((fd = open("/dev/mem", O_RDONLY)) < 0) 68 return (-1); /* errno is set for us */ 69 70 if ((rc = ioctl(fd, cmd, &addr)) < 0) 71 err = errno; 72 73 (void) close(fd); 74 75 if (rc < 0) { 76 errno = err; 77 return (-1); 78 } 79 80 return (0); 81 } 82 83 static void 84 cma_page_free(fmd_hdl_t *hdl, cma_page_t *page) 85 { 86 if (page->pg_fmri != NULL) 87 nvlist_free(page->pg_fmri); 88 fmd_hdl_free(hdl, page, sizeof (cma_page_t)); 89 } 90 91 /*ARGSUSED*/ 92 void 93 cma_page_retire(fmd_hdl_t *hdl, nvlist_t *nvl, nvlist_t *asru, const char *uuid) 94 { 95 cma_page_t *page; 96 uint64_t pageaddr; 97 98 /* It should already be expanded, but we'll do it again anyway */ 99 if (fmd_nvl_fmri_expand(hdl, asru) < 0) { 100 fmd_hdl_debug(hdl, "failed to expand page asru\n"); 101 cma_stats.bad_flts.fmds_value.ui64++; 102 return; 103 } 104 105 if (nvlist_lookup_uint64(asru, FM_FMRI_MEM_PHYSADDR, &pageaddr) != 0) { 106 fmd_hdl_debug(hdl, "mem fault missing '%s'\n", 107 FM_FMRI_MEM_PHYSADDR); 108 cma_stats.bad_flts.fmds_value.ui64++; 109 return; 110 } 111 112 if (!cma.cma_page_doretire) { 113 fmd_hdl_debug(hdl, "suppressed retire of page %llx\n", 114 (u_longlong_t)pageaddr); 115 cma_stats.page_supp.fmds_value.ui64++; 116 return; 117 } 118 119 if (!fmd_nvl_fmri_present(hdl, asru)) { 120 fmd_hdl_debug(hdl, "page retire overtaken by events\n"); 121 cma_stats.page_nonent.fmds_value.ui64++; 122 if (uuid != NULL) 123 fmd_case_uuclose(hdl, uuid); 124 return; 125 } 126 127 if (cma_page_cmd(MEM_PAGE_RETIRE, pageaddr) == 0) { 128 fmd_hdl_debug(hdl, "retired page 0x%llx\n", 129 (u_longlong_t)pageaddr); 130 cma_stats.page_flts.fmds_value.ui64++; 131 if (uuid != NULL) 132 fmd_case_uuclose(hdl, uuid); 133 return; 134 } 135 136 /* 137 * The page didn't immediately retire. We'll need to periodically 138 * check to see if it has been retired. 139 */ 140 fmd_hdl_debug(hdl, "page didn't retire - sleeping\n"); 141 142 page = fmd_hdl_zalloc(hdl, sizeof (cma_page_t), FMD_SLEEP); 143 page->pg_addr = pageaddr; 144 (void) nvlist_dup(asru, &page->pg_fmri, 0); 145 if (uuid != NULL) 146 page->pg_uuid = fmd_hdl_strdup(hdl, uuid, FMD_SLEEP); 147 148 page->pg_next = cma.cma_pages; 149 cma.cma_pages = page; 150 151 if (cma.cma_page_timerid != 0) 152 fmd_timer_remove(hdl, cma.cma_page_timerid); 153 154 cma.cma_page_curdelay = cma.cma_page_mindelay; 155 156 cma.cma_page_timerid = 157 fmd_timer_install(hdl, NULL, NULL, cma.cma_page_curdelay); 158 } 159 160 static int 161 page_retry(fmd_hdl_t *hdl, cma_page_t *page) 162 { 163 if (page->pg_fmri != NULL && !fmd_nvl_fmri_present(hdl, 164 page->pg_fmri)) { 165 fmd_hdl_debug(hdl, "page retire overtaken by events"); 166 cma_stats.page_nonent.fmds_value.ui64++; 167 168 if (page->pg_uuid != NULL) 169 fmd_case_uuclose(hdl, page->pg_uuid); 170 return (1); /* no longer a page to retire */ 171 } 172 173 if (cma_page_cmd(MEM_PAGE_ISRETIRED, page->pg_addr) == 0) { 174 fmd_hdl_debug(hdl, "retired page 0x%llx on retry %u\n", 175 page->pg_addr, page->pg_nretries); 176 cma_stats.page_flts.fmds_value.ui64++; 177 178 if (page->pg_uuid != NULL) 179 fmd_case_uuclose(hdl, page->pg_uuid); 180 return (1); /* page retired */ 181 } 182 183 if (errno == EAGAIN) { 184 fmd_hdl_debug(hdl, "scheduling another retry for 0x%llx\n", 185 page->pg_addr); 186 return (0); /* schedule another retry */ 187 } else { 188 if (errno == EIO) { 189 fmd_hdl_debug(hdl, "failed to retry page 0x%llx " 190 "retirement: page isn't scheduled for retirement\n", 191 page->pg_addr); 192 } else { 193 fmd_hdl_debug(hdl, "failed to retry page 0x%llx " 194 "retirement: %s\n", page->pg_addr, 195 strerror(errno)); 196 } 197 198 cma_stats.page_fails.fmds_value.ui64++; 199 return (1); /* give up */ 200 } 201 } 202 203 void 204 cma_page_retry(fmd_hdl_t *hdl) 205 { 206 cma_page_t **pagep; 207 208 cma.cma_page_timerid = 0; 209 210 fmd_hdl_debug(hdl, "page_retry: timer fired\n"); 211 212 pagep = &cma.cma_pages; 213 while (*pagep != NULL) { 214 cma_page_t *page = *pagep; 215 216 if (page_retry(hdl, page)) { 217 /* 218 * Successful retry or we're giving up - remove from 219 * the list 220 */ 221 *pagep = page->pg_next; 222 223 if (page->pg_uuid != NULL) 224 fmd_hdl_strfree(hdl, page->pg_uuid); 225 226 cma_page_free(hdl, page); 227 } else { 228 page->pg_nretries++; 229 pagep = &page->pg_next; 230 } 231 } 232 233 if (cma.cma_pages == NULL) 234 return; /* no more retirements */ 235 236 /* 237 * We still have retirements that haven't completed. Back the delay 238 * off, and schedule a retry. 239 */ 240 cma.cma_page_curdelay = MIN(cma.cma_page_curdelay * 2, 241 cma.cma_page_maxdelay); 242 243 fmd_hdl_debug(hdl, "scheduled page retirement retry for %llu secs\n", 244 (u_longlong_t)(cma.cma_page_curdelay / NANOSEC)); 245 246 cma.cma_page_timerid = 247 fmd_timer_install(hdl, NULL, NULL, cma.cma_page_curdelay); 248 } 249 250 void 251 cma_page_fini(fmd_hdl_t *hdl) 252 { 253 cma_page_t *page; 254 255 while ((page = cma.cma_pages) != NULL) { 256 cma.cma_pages = page->pg_next; 257 cma_page_free(hdl, page); 258 } 259 } 260