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
retire_store_init(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
retire_store_read(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
rio_store_free(rio_store_t * rsp)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
retire_list_free(nvf_handle_t nvfh)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
rio_store_decode(nvf_handle_t nvfh,nvlist_t * line_nvl,char * name)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
rio_store_encode(nvf_handle_t nvfh,nvlist_t ** ret_nvl)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
e_ddi_retire_persist(char * devpath)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
e_ddi_retire_unpersist(char * devpath)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
e_ddi_device_retired(char * devpath)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 }
458