xref: /illumos-gate/usr/src/uts/common/os/retire_store.c (revision 69a119caa6570c7077699161b7c28b6ee9f8b0f4)
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 }
458