1275c9da8Seschrock /*
2275c9da8Seschrock  * CDDL HEADER START
3275c9da8Seschrock  *
4275c9da8Seschrock  * The contents of this file are subject to the terms of the
5275c9da8Seschrock  * Common Development and Distribution License (the "License").
6275c9da8Seschrock  * You may not use this file except in compliance with the License.
7275c9da8Seschrock  *
8275c9da8Seschrock  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9275c9da8Seschrock  * or http://www.opensolaris.org/os/licensing.
10275c9da8Seschrock  * See the License for the specific language governing permissions
11275c9da8Seschrock  * and limitations under the License.
12275c9da8Seschrock  *
13275c9da8Seschrock  * When distributing Covered Code, include this CDDL HEADER in each
14275c9da8Seschrock  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15275c9da8Seschrock  * If applicable, add the following below this CDDL HEADER, with the
16275c9da8Seschrock  * fields enclosed by brackets "[]" replaced with your own identifying
17275c9da8Seschrock  * information: Portions Copyright [yyyy] [name of copyright owner]
18275c9da8Seschrock  *
19275c9da8Seschrock  * CDDL HEADER END
20275c9da8Seschrock  */
21275c9da8Seschrock 
22275c9da8Seschrock /*
23*ac88567aSHyon Kim  * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
24275c9da8Seschrock  */
25275c9da8Seschrock 
26275c9da8Seschrock #include <scsi/libses.h>
27275c9da8Seschrock #include "ses_impl.h"
28275c9da8Seschrock 
29275c9da8Seschrock ses_snap_page_t *
ses_snap_find_page(ses_snap_t * sp,ses2_diag_page_t page,boolean_t ctl)30275c9da8Seschrock ses_snap_find_page(ses_snap_t *sp, ses2_diag_page_t page, boolean_t ctl)
31275c9da8Seschrock {
32275c9da8Seschrock 	ses_snap_page_t *pp;
33275c9da8Seschrock 
34275c9da8Seschrock 	for (pp = sp->ss_pages; pp != NULL; pp = pp->ssp_next)
35275c9da8Seschrock 		if (pp->ssp_num == page && pp->ssp_control == ctl &&
36275c9da8Seschrock 		    (pp->ssp_len > 0 || pp->ssp_control))
37275c9da8Seschrock 			return (pp);
38275c9da8Seschrock 
39275c9da8Seschrock 	return (NULL);
40275c9da8Seschrock }
41275c9da8Seschrock 
42275c9da8Seschrock static int
grow_snap_page(ses_snap_page_t * pp,size_t min)43275c9da8Seschrock grow_snap_page(ses_snap_page_t *pp, size_t min)
44275c9da8Seschrock {
45275c9da8Seschrock 	uint8_t *newbuf;
46275c9da8Seschrock 
47275c9da8Seschrock 	if (min == 0 || min < pp->ssp_alloc)
48275c9da8Seschrock 		min = pp->ssp_alloc * 2;
49275c9da8Seschrock 
50275c9da8Seschrock 	if ((newbuf = ses_realloc(pp->ssp_page, min)) == NULL)
51275c9da8Seschrock 		return (-1);
52275c9da8Seschrock 
53275c9da8Seschrock 	pp->ssp_page = newbuf;
54275c9da8Seschrock 	pp->ssp_alloc = min;
55275c9da8Seschrock 
56275c9da8Seschrock 	bzero(newbuf + pp->ssp_len, pp->ssp_alloc - pp->ssp_len);
57275c9da8Seschrock 
58275c9da8Seschrock 	return (0);
59275c9da8Seschrock }
60275c9da8Seschrock 
61275c9da8Seschrock static ses_snap_page_t *
alloc_snap_page(void)62275c9da8Seschrock alloc_snap_page(void)
63275c9da8Seschrock {
64275c9da8Seschrock 	ses_snap_page_t *pp;
65275c9da8Seschrock 
66275c9da8Seschrock 	if ((pp = ses_zalloc(sizeof (ses_snap_page_t))) == NULL)
67275c9da8Seschrock 		return (NULL);
68275c9da8Seschrock 
69275c9da8Seschrock 	if ((pp->ssp_page = ses_zalloc(SES2_MIN_DIAGPAGE_ALLOC)) == NULL) {
70275c9da8Seschrock 		ses_free(pp);
71275c9da8Seschrock 		return (NULL);
72275c9da8Seschrock 	}
73275c9da8Seschrock 
74275c9da8Seschrock 	pp->ssp_num = -1;
75275c9da8Seschrock 	pp->ssp_alloc = SES2_MIN_DIAGPAGE_ALLOC;
76275c9da8Seschrock 
77275c9da8Seschrock 	return (pp);
78275c9da8Seschrock }
79275c9da8Seschrock 
80275c9da8Seschrock static void
free_snap_page(ses_snap_page_t * pp)81275c9da8Seschrock free_snap_page(ses_snap_page_t *pp)
82275c9da8Seschrock {
83275c9da8Seschrock 	if (pp == NULL)
84275c9da8Seschrock 		return;
85275c9da8Seschrock 
86275c9da8Seschrock 	if (pp->ssp_mmap_base)
87275c9da8Seschrock 		(void) munmap(pp->ssp_mmap_base, pp->ssp_mmap_len);
88275c9da8Seschrock 	else
89275c9da8Seschrock 		ses_free(pp->ssp_page);
90275c9da8Seschrock 	ses_free(pp);
91275c9da8Seschrock }
92275c9da8Seschrock 
93275c9da8Seschrock static void
free_all_snap_pages(ses_snap_t * sp)94275c9da8Seschrock free_all_snap_pages(ses_snap_t *sp)
95275c9da8Seschrock {
96275c9da8Seschrock 	ses_snap_page_t *pp, *np;
97275c9da8Seschrock 
98275c9da8Seschrock 	for (pp = sp->ss_pages; pp != NULL; pp = np) {
99275c9da8Seschrock 		np = pp->ssp_next;
100275c9da8Seschrock 		free_snap_page(pp);
101275c9da8Seschrock 	}
102275c9da8Seschrock 
103275c9da8Seschrock 	sp->ss_pages = NULL;
104275c9da8Seschrock }
105275c9da8Seschrock 
106275c9da8Seschrock /*
107275c9da8Seschrock  * Grow (if needed) the control page buffer, fill in the page code, page
108275c9da8Seschrock  * length, and generation count, and return a pointer to the page.  The
109275c9da8Seschrock  * caller is responsible for filling in the rest of the page data.  If 'unique'
110275c9da8Seschrock  * is specified, then a new page instance is created instead of sharing the
111275c9da8Seschrock  * current one.
112275c9da8Seschrock  */
113275c9da8Seschrock ses_snap_page_t *
ses_snap_ctl_page(ses_snap_t * sp,ses2_diag_page_t page,size_t dlen,boolean_t unique)114275c9da8Seschrock ses_snap_ctl_page(ses_snap_t *sp, ses2_diag_page_t page, size_t dlen,
115275c9da8Seschrock     boolean_t unique)
116275c9da8Seschrock {
117275c9da8Seschrock 	ses_target_t *tp = sp->ss_target;
118275c9da8Seschrock 	spc3_diag_page_impl_t *pip;
119275c9da8Seschrock 	ses_snap_page_t *pp, *up, **loc;
120275c9da8Seschrock 	ses_pagedesc_t *dp;
121275c9da8Seschrock 	size_t len;
122275c9da8Seschrock 
123275c9da8Seschrock 	pp = ses_snap_find_page(sp, page, B_TRUE);
124275c9da8Seschrock 	if (pp == NULL) {
125275c9da8Seschrock 		(void) ses_set_errno(ESES_NOTSUP);
126275c9da8Seschrock 		return (NULL);
127275c9da8Seschrock 	}
128275c9da8Seschrock 
129275c9da8Seschrock 	if (pp->ssp_initialized && !unique)
130275c9da8Seschrock 		return (pp);
131275c9da8Seschrock 
132275c9da8Seschrock 	if (unique) {
133275c9da8Seschrock 		/*
134275c9da8Seschrock 		 * The user has requested a unique instance of the page.  Create
135275c9da8Seschrock 		 * a new ses_snap_page_t instance and chain it off the
136275c9da8Seschrock 		 * 'ssp_instances' list of the master page.  These must be
137275c9da8Seschrock 		 * appended to the end of the chain, as the order of operations
138275c9da8Seschrock 		 * may be important (i.e. microcode download).
139275c9da8Seschrock 		 */
140275c9da8Seschrock 		if ((up = alloc_snap_page()) == NULL)
141275c9da8Seschrock 			return (NULL);
142275c9da8Seschrock 
143275c9da8Seschrock 		up->ssp_num = pp->ssp_num;
144275c9da8Seschrock 		up->ssp_control = B_TRUE;
145275c9da8Seschrock 
146275c9da8Seschrock 		for (loc = &pp->ssp_unique; *loc != NULL;
147275c9da8Seschrock 		    loc = &(*loc)->ssp_next)
148275c9da8Seschrock 			;
149275c9da8Seschrock 
150275c9da8Seschrock 		*loc = up;
151275c9da8Seschrock 		pp = up;
152275c9da8Seschrock 	}
153275c9da8Seschrock 
154275c9da8Seschrock 	dp = ses_get_pagedesc(tp, page, SES_PAGE_CTL);
155275c9da8Seschrock 	ASSERT(dp != NULL);
156275c9da8Seschrock 
157275c9da8Seschrock 	len = dp->spd_ctl_len(sp->ss_n_elem, page, dlen);
158*ac88567aSHyon Kim 	if (pp->ssp_alloc < len && grow_snap_page(pp, len) != 0)
159275c9da8Seschrock 		return (NULL);
160275c9da8Seschrock 	pp->ssp_len = len;
161275c9da8Seschrock 	bzero(pp->ssp_page, len);
162275c9da8Seschrock 	pp->ssp_initialized = B_TRUE;
163275c9da8Seschrock 
164275c9da8Seschrock 	pip = (spc3_diag_page_impl_t *)pp->ssp_page;
165275c9da8Seschrock 	pip->sdpi_page_code = (uint8_t)page;
166275c9da8Seschrock 	SCSI_WRITE16(&pip->sdpi_page_length,
167275c9da8Seschrock 	    len - offsetof(spc3_diag_page_impl_t, sdpi_data[0]));
168275c9da8Seschrock 	if (dp->spd_gcoff != -1)
169275c9da8Seschrock 		SCSI_WRITE32((uint8_t *)pip + dp->spd_gcoff, sp->ss_generation);
170275c9da8Seschrock 
171275c9da8Seschrock 	return (pp);
172275c9da8Seschrock }
173275c9da8Seschrock 
174275c9da8Seschrock static int
read_status_page(ses_snap_t * sp,ses2_diag_page_t page)175275c9da8Seschrock read_status_page(ses_snap_t *sp, ses2_diag_page_t page)
176275c9da8Seschrock {
177275c9da8Seschrock 	libscsi_action_t *ap;
178275c9da8Seschrock 	ses_snap_page_t *pp;
179275c9da8Seschrock 	ses_target_t *tp;
180275c9da8Seschrock 	spc3_diag_page_impl_t *pip;
181275c9da8Seschrock 	spc3_receive_diagnostic_results_cdb_t *cp;
182275c9da8Seschrock 	uint_t flags;
183275c9da8Seschrock 	uint8_t *buf;
184275c9da8Seschrock 	size_t alloc;
185275c9da8Seschrock 	uint_t retries = 0;
186275c9da8Seschrock 	ses2_diag_page_t retpage;
187275c9da8Seschrock 
188275c9da8Seschrock 	for (pp = sp->ss_pages; pp != NULL; pp = pp->ssp_next)
189275c9da8Seschrock 		if (pp->ssp_num == page && !pp->ssp_control)
190275c9da8Seschrock 			break;
191275c9da8Seschrock 
192275c9da8Seschrock 	/*
193275c9da8Seschrock 	 * No matching page.  Since the page number is not under consumer or
194275c9da8Seschrock 	 * device control, this must be a bug.
195275c9da8Seschrock 	 */
196275c9da8Seschrock 	ASSERT(pp != NULL);
197275c9da8Seschrock 
198275c9da8Seschrock 	tp = sp->ss_target;
199275c9da8Seschrock 
200275c9da8Seschrock 	flags = LIBSCSI_AF_READ | LIBSCSI_AF_SILENT | LIBSCSI_AF_DIAGNOSE |
201275c9da8Seschrock 	    LIBSCSI_AF_RQSENSE;
202275c9da8Seschrock 
203275c9da8Seschrock again:
204275c9da8Seschrock 	ap = libscsi_action_alloc(tp->st_scsi_hdl,
205275c9da8Seschrock 	    SPC3_CMD_RECEIVE_DIAGNOSTIC_RESULTS, flags, pp->ssp_page,
206275c9da8Seschrock 	    pp->ssp_alloc);
207275c9da8Seschrock 
208275c9da8Seschrock 	if (ap == NULL)
209275c9da8Seschrock 		return (ses_libscsi_error(tp->st_scsi_hdl, "failed to "
210275c9da8Seschrock 		    "allocate SCSI action"));
211275c9da8Seschrock 
212275c9da8Seschrock 	cp = (spc3_receive_diagnostic_results_cdb_t *)
213275c9da8Seschrock 	    libscsi_action_get_cdb(ap);
214275c9da8Seschrock 
215275c9da8Seschrock 	cp->rdrc_page_code = pp->ssp_num;
216275c9da8Seschrock 	cp->rdrc_pcv = 1;
217275c9da8Seschrock 	SCSI_WRITE16(&cp->rdrc_allocation_length,
218275c9da8Seschrock 	    MIN(pp->ssp_alloc, UINT16_MAX));
219275c9da8Seschrock 
220275c9da8Seschrock 	if (libscsi_exec(ap, tp->st_target) != 0) {
221275c9da8Seschrock 		libscsi_action_free(ap);
222275c9da8Seschrock 		return (ses_libscsi_error(tp->st_scsi_hdl,
223275c9da8Seschrock 		    "receive diagnostic results failed"));
224275c9da8Seschrock 	}
225275c9da8Seschrock 
226275c9da8Seschrock 	if (libscsi_action_get_status(ap) != 0) {
227275c9da8Seschrock 		(void) ses_scsi_error(ap,
228275c9da8Seschrock 		    "receive diagnostic results failed");
229275c9da8Seschrock 		libscsi_action_free(ap);
230275c9da8Seschrock 		return (-1);
231275c9da8Seschrock 	}
232275c9da8Seschrock 
233275c9da8Seschrock 	(void) libscsi_action_get_buffer(ap, &buf, &alloc, &pp->ssp_len);
234275c9da8Seschrock 	libscsi_action_free(ap);
235275c9da8Seschrock 
236275c9da8Seschrock 	ASSERT(buf == pp->ssp_page);
237275c9da8Seschrock 	ASSERT(alloc == pp->ssp_alloc);
238275c9da8Seschrock 
239*ac88567aSHyon Kim 	if (pp->ssp_alloc - pp->ssp_len < 0x80 && pp->ssp_alloc < UINT16_MAX) {
240275c9da8Seschrock 		bzero(pp->ssp_page, pp->ssp_len);
241275c9da8Seschrock 		pp->ssp_len = 0;
242275c9da8Seschrock 		if (grow_snap_page(pp, 0) != 0)
243275c9da8Seschrock 			return (-1);
244275c9da8Seschrock 		goto again;
245275c9da8Seschrock 	}
246275c9da8Seschrock 
247*ac88567aSHyon Kim 	if (pp->ssp_len < offsetof(spc3_diag_page_impl_t, sdpi_data)) {
248*ac88567aSHyon Kim 		bzero(pp->ssp_page, pp->ssp_len);
249*ac88567aSHyon Kim 		pp->ssp_len = 0;
250*ac88567aSHyon Kim 		return (ses_error(ESES_BAD_RESPONSE, "target returned "
251*ac88567aSHyon Kim 		    "truncated page 0x%x (length %d)", page, pp->ssp_len));
252*ac88567aSHyon Kim 	}
253*ac88567aSHyon Kim 
254275c9da8Seschrock 	pip = (spc3_diag_page_impl_t *)buf;
255275c9da8Seschrock 
256275c9da8Seschrock 	if (pip->sdpi_page_code == page)
257275c9da8Seschrock 		return (0);
258275c9da8Seschrock 
259275c9da8Seschrock 	retpage = pip->sdpi_page_code;
260275c9da8Seschrock 
261275c9da8Seschrock 	bzero(pp->ssp_page, pp->ssp_len);
262275c9da8Seschrock 	pp->ssp_len = 0;
263275c9da8Seschrock 
264275c9da8Seschrock 	if (retpage == SES2_DIAGPAGE_ENCLOSURE_BUSY) {
265275c9da8Seschrock 		if (++retries > LIBSES_MAX_BUSY_RETRIES)
266275c9da8Seschrock 			return (ses_error(ESES_BUSY, "too many "
267275c9da8Seschrock 			    "enclosure busy responses for page 0x%x", page));
268275c9da8Seschrock 		goto again;
269275c9da8Seschrock 	}
270275c9da8Seschrock 
271275c9da8Seschrock 	return (ses_error(ESES_BAD_RESPONSE, "target returned page 0x%x "
272275c9da8Seschrock 	    "instead of the requested page 0x%x", retpage, page));
273275c9da8Seschrock }
274275c9da8Seschrock 
275275c9da8Seschrock static int
send_control_page(ses_snap_t * sp,ses_snap_page_t * pp)276275c9da8Seschrock send_control_page(ses_snap_t *sp, ses_snap_page_t *pp)
277275c9da8Seschrock {
278275c9da8Seschrock 	ses_target_t *tp;
279275c9da8Seschrock 	libscsi_action_t *ap;
280275c9da8Seschrock 	spc3_send_diagnostic_cdb_t *cp;
281275c9da8Seschrock 	uint_t flags;
282275c9da8Seschrock 
283275c9da8Seschrock 	tp = sp->ss_target;
284275c9da8Seschrock 
285275c9da8Seschrock 	flags = LIBSCSI_AF_WRITE | LIBSCSI_AF_SILENT | LIBSCSI_AF_DIAGNOSE |
286275c9da8Seschrock 	    LIBSCSI_AF_RQSENSE;
287275c9da8Seschrock 
288275c9da8Seschrock 	ap = libscsi_action_alloc(tp->st_scsi_hdl, SPC3_CMD_SEND_DIAGNOSTIC,
289275c9da8Seschrock 	    flags, pp->ssp_page, pp->ssp_len);
290275c9da8Seschrock 
291275c9da8Seschrock 	if (ap == NULL)
292275c9da8Seschrock 		return (ses_libscsi_error(tp->st_scsi_hdl, "failed to "
293275c9da8Seschrock 		    "allocate SCSI action"));
294275c9da8Seschrock 
295275c9da8Seschrock 	cp = (spc3_send_diagnostic_cdb_t *)libscsi_action_get_cdb(ap);
296275c9da8Seschrock 
297275c9da8Seschrock 	cp->sdc_pf = 1;
298275c9da8Seschrock 	SCSI_WRITE16(&cp->sdc_parameter_list_length, pp->ssp_len);
299275c9da8Seschrock 
300275c9da8Seschrock 	if (libscsi_exec(ap, tp->st_target) != 0) {
301275c9da8Seschrock 		libscsi_action_free(ap);
302275c9da8Seschrock 		return (ses_libscsi_error(tp->st_scsi_hdl,
303275c9da8Seschrock 		    "SEND DIAGNOSTIC command failed for page 0x%x",
304275c9da8Seschrock 		    pp->ssp_num));
305275c9da8Seschrock 	}
306275c9da8Seschrock 
307275c9da8Seschrock 	if (libscsi_action_get_status(ap) != 0) {
308275c9da8Seschrock 		(void) ses_scsi_error(ap, "SEND DIAGNOSTIC command "
309275c9da8Seschrock 		    "failed for page 0x%x", pp->ssp_num);
310275c9da8Seschrock 		libscsi_action_free(ap);
311275c9da8Seschrock 		return (-1);
312275c9da8Seschrock 	}
313275c9da8Seschrock 
314275c9da8Seschrock 	libscsi_action_free(ap);
315275c9da8Seschrock 
316275c9da8Seschrock 	return (0);
317275c9da8Seschrock }
318275c9da8Seschrock 
319275c9da8Seschrock static int
pages_skel_create(ses_snap_t * sp)320275c9da8Seschrock pages_skel_create(ses_snap_t *sp)
321275c9da8Seschrock {
322275c9da8Seschrock 	ses_snap_page_t *pp, *np;
323275c9da8Seschrock 	ses_target_t *tp = sp->ss_target;
324275c9da8Seschrock 	ses2_supported_ses_diag_page_impl_t *pip;
325275c9da8Seschrock 	ses2_diag_page_t page;
326275c9da8Seschrock 	size_t npages;
327275c9da8Seschrock 	size_t pagelen;
328275c9da8Seschrock 	off_t i;
329275c9da8Seschrock 
330275c9da8Seschrock 	ASSERT(sp->ss_pages == NULL);
331275c9da8Seschrock 
332275c9da8Seschrock 	if ((pp = alloc_snap_page()) == NULL)
333275c9da8Seschrock 		return (-1);
334275c9da8Seschrock 
335275c9da8Seschrock 	pp->ssp_num = SES2_DIAGPAGE_SUPPORTED_PAGES;
336275c9da8Seschrock 	pp->ssp_control = B_FALSE;
337275c9da8Seschrock 	sp->ss_pages = pp;
338275c9da8Seschrock 
339275c9da8Seschrock 	if (read_status_page(sp, SES2_DIAGPAGE_SUPPORTED_PAGES) != 0) {
340275c9da8Seschrock 		free_snap_page(pp);
341275c9da8Seschrock 		sp->ss_pages = NULL;
342275c9da8Seschrock 		return (-1);
343275c9da8Seschrock 	}
344275c9da8Seschrock 
345275c9da8Seschrock 	pip = pp->ssp_page;
346275c9da8Seschrock 	pagelen = pp->ssp_len;
347275c9da8Seschrock 
348275c9da8Seschrock 	npages = SCSI_READ16(&pip->sssdpi_page_length);
349275c9da8Seschrock 
350275c9da8Seschrock 	for (i = 0; i < npages; i++) {
351275c9da8Seschrock 		if (!SES_WITHIN_PAGE(pip->sssdpi_pages + i, 1, pip,
352275c9da8Seschrock 		    pagelen))
353275c9da8Seschrock 			break;
354275c9da8Seschrock 
355275c9da8Seschrock 		page = (ses2_diag_page_t)pip->sssdpi_pages[i];
356275c9da8Seschrock 		/*
357275c9da8Seschrock 		 * Skip the page we already added during the bootstrap.
358275c9da8Seschrock 		 */
359275c9da8Seschrock 		if (page == SES2_DIAGPAGE_SUPPORTED_PAGES)
360275c9da8Seschrock 			continue;
361275c9da8Seschrock 		/*
362275c9da8Seschrock 		 * The end of the page list may be padded with zeros; ignore
363275c9da8Seschrock 		 * them all.
364275c9da8Seschrock 		 */
365275c9da8Seschrock 		if (page == 0 && i > 0)
366275c9da8Seschrock 			break;
367275c9da8Seschrock 		if ((np = alloc_snap_page()) == NULL) {
368275c9da8Seschrock 			free_all_snap_pages(sp);
369275c9da8Seschrock 			return (-1);
370275c9da8Seschrock 		}
371275c9da8Seschrock 		np->ssp_num = page;
372275c9da8Seschrock 		pp->ssp_next = np;
373275c9da8Seschrock 		pp = np;
374275c9da8Seschrock 
375275c9da8Seschrock 		/*
376275c9da8Seschrock 		 * Allocate a control page as well, if we can use it.
377275c9da8Seschrock 		 */
378275c9da8Seschrock 		if (ses_get_pagedesc(tp, page, SES_PAGE_CTL) != NULL) {
379275c9da8Seschrock 			if ((np = alloc_snap_page()) == NULL) {
380275c9da8Seschrock 				free_all_snap_pages(sp);
381275c9da8Seschrock 				return (-1);
382275c9da8Seschrock 			}
383275c9da8Seschrock 			np->ssp_num = page;
384275c9da8Seschrock 			np->ssp_control = B_TRUE;
385275c9da8Seschrock 			pp->ssp_next = np;
386275c9da8Seschrock 			pp = np;
387275c9da8Seschrock 		}
388275c9da8Seschrock 	}
389275c9da8Seschrock 
390275c9da8Seschrock 	return (0);
391275c9da8Seschrock }
392275c9da8Seschrock 
393275c9da8Seschrock static void
ses_snap_free(ses_snap_t * sp)394275c9da8Seschrock ses_snap_free(ses_snap_t *sp)
395275c9da8Seschrock {
396275c9da8Seschrock 	free_all_snap_pages(sp);
397275c9da8Seschrock 	ses_node_teardown(sp->ss_root);
398275c9da8Seschrock 	ses_free(sp->ss_nodes);
399275c9da8Seschrock 	ses_free(sp);
400275c9da8Seschrock }
401275c9da8Seschrock 
402275c9da8Seschrock static void
ses_snap_rele_unlocked(ses_snap_t * sp)403275c9da8Seschrock ses_snap_rele_unlocked(ses_snap_t *sp)
404275c9da8Seschrock {
405275c9da8Seschrock 	ses_target_t *tp = sp->ss_target;
406275c9da8Seschrock 
407275c9da8Seschrock 	if (--sp->ss_refcnt != 0)
408275c9da8Seschrock 		return;
409275c9da8Seschrock 
410275c9da8Seschrock 	if (sp->ss_next != NULL)
411275c9da8Seschrock 		sp->ss_next->ss_prev = sp->ss_prev;
412275c9da8Seschrock 
413275c9da8Seschrock 	if (sp->ss_prev != NULL)
414275c9da8Seschrock 		sp->ss_prev->ss_next = sp->ss_next;
415275c9da8Seschrock 	else
416275c9da8Seschrock 		tp->st_snapshots = sp->ss_next;
417275c9da8Seschrock 
418275c9da8Seschrock 	ses_snap_free(sp);
419275c9da8Seschrock }
420275c9da8Seschrock 
421275c9da8Seschrock ses_snap_t *
ses_snap_hold(ses_target_t * tp)422275c9da8Seschrock ses_snap_hold(ses_target_t *tp)
423275c9da8Seschrock {
424275c9da8Seschrock 	ses_snap_t *sp;
425275c9da8Seschrock 
426275c9da8Seschrock 	(void) pthread_mutex_lock(&tp->st_lock);
427275c9da8Seschrock 	sp = tp->st_snapshots;
428275c9da8Seschrock 	sp->ss_refcnt++;
429275c9da8Seschrock 	(void) pthread_mutex_unlock(&tp->st_lock);
430275c9da8Seschrock 
431275c9da8Seschrock 	return (sp);
432275c9da8Seschrock }
433275c9da8Seschrock 
434275c9da8Seschrock void
ses_snap_rele(ses_snap_t * sp)435275c9da8Seschrock ses_snap_rele(ses_snap_t *sp)
436275c9da8Seschrock {
437275c9da8Seschrock 	ses_target_t *tp = sp->ss_target;
438275c9da8Seschrock 
439275c9da8Seschrock 	(void) pthread_mutex_lock(&tp->st_lock);
440275c9da8Seschrock 	ses_snap_rele_unlocked(sp);
441275c9da8Seschrock 	(void) pthread_mutex_unlock(&tp->st_lock);
442275c9da8Seschrock }
443275c9da8Seschrock 
444275c9da8Seschrock ses_snap_t *
ses_snap_new(ses_target_t * tp)445275c9da8Seschrock ses_snap_new(ses_target_t *tp)
446275c9da8Seschrock {
447275c9da8Seschrock 	ses_snap_t *sp;
448275c9da8Seschrock 	ses_snap_page_t *pp;
449275c9da8Seschrock 	uint32_t gc;
450275c9da8Seschrock 	uint_t retries = 0;
451275c9da8Seschrock 	ses_pagedesc_t *dp;
452275c9da8Seschrock 	size_t pages, pagesize, pagelen;
453275c9da8Seschrock 	char *scratch;
454*ac88567aSHyon Kim 	boolean_t simple;
455275c9da8Seschrock 
456275c9da8Seschrock 	if ((sp = ses_zalloc(sizeof (ses_snap_t))) == NULL)
457275c9da8Seschrock 		return (NULL);
458275c9da8Seschrock 
459275c9da8Seschrock 	sp->ss_target = tp;
460275c9da8Seschrock 
461275c9da8Seschrock again:
462275c9da8Seschrock 	free_all_snap_pages(sp);
463275c9da8Seschrock 
464275c9da8Seschrock 	if (pages_skel_create(sp) != 0) {
465275c9da8Seschrock 		free(sp);
466275c9da8Seschrock 		return (NULL);
467275c9da8Seschrock 	}
468275c9da8Seschrock 
469275c9da8Seschrock 	sp->ss_generation = (uint32_t)-1;
470275c9da8Seschrock 	sp->ss_time = gethrtime();
471275c9da8Seschrock 
472*ac88567aSHyon Kim 	/*
473*ac88567aSHyon Kim 	 * First check for the short enclosure status diagnostic page and
474*ac88567aSHyon Kim 	 * determine if this is a simple subenclosure or not.
475*ac88567aSHyon Kim 	 */
476*ac88567aSHyon Kim 	simple = B_FALSE;
477*ac88567aSHyon Kim 	for (pp = sp->ss_pages; pp != NULL; pp = pp->ssp_next) {
478*ac88567aSHyon Kim 		if (pp->ssp_num == SES2_DIAGPAGE_SHORT_STATUS)
479*ac88567aSHyon Kim 			simple = B_TRUE;
480*ac88567aSHyon Kim 	}
481*ac88567aSHyon Kim 
482275c9da8Seschrock 	for (pp = sp->ss_pages; pp != NULL; pp = pp->ssp_next) {
483275c9da8Seschrock 		/*
484275c9da8Seschrock 		 * We skip all of:
485275c9da8Seschrock 		 *
486275c9da8Seschrock 		 * - Control pages
487275c9da8Seschrock 		 * - Pages we've already filled in
488275c9da8Seschrock 		 * - Pages we don't understand (those with no descriptor)
489275c9da8Seschrock 		 */
490275c9da8Seschrock 		if (pp->ssp_len > 0 || pp->ssp_control)
491275c9da8Seschrock 			continue;
492275c9da8Seschrock 		if ((dp = ses_get_pagedesc(tp, pp->ssp_num,
493275c9da8Seschrock 		    SES_PAGE_DIAG)) == NULL)
494275c9da8Seschrock 			continue;
495275c9da8Seschrock 
496*ac88567aSHyon Kim 		if (read_status_page(sp, pp->ssp_num) != 0)  {
497*ac88567aSHyon Kim 			/*
498*ac88567aSHyon Kim 			 * If this page is required, and this is not a simple
499*ac88567aSHyon Kim 			 * subenclosure, then fail the entire snapshot.
500*ac88567aSHyon Kim 			 */
501*ac88567aSHyon Kim 			if (dp->spd_req == SES_REQ_MANDATORY_ALL ||
502*ac88567aSHyon Kim 			    (dp->spd_req == SES_REQ_MANDATORY_STANDARD &&
503*ac88567aSHyon Kim 			    !simple)) {
504*ac88567aSHyon Kim 				ses_snap_free(sp);
505*ac88567aSHyon Kim 				return (NULL);
506*ac88567aSHyon Kim 			}
507*ac88567aSHyon Kim 
508275c9da8Seschrock 			continue;
509*ac88567aSHyon Kim 		}
510275c9da8Seschrock 
511275c9da8Seschrock 		/*
512275c9da8Seschrock 		 * If the generation code has changed, we don't have a valid
513275c9da8Seschrock 		 * snapshot.  Start over.
514275c9da8Seschrock 		 */
515275c9da8Seschrock 		if (dp->spd_gcoff != -1 &&
516275c9da8Seschrock 		    dp->spd_gcoff + 4 <= pp->ssp_len) {
517275c9da8Seschrock 			gc = SCSI_READ32((uint8_t *)pp->ssp_page +
518275c9da8Seschrock 			    dp->spd_gcoff);
519275c9da8Seschrock 			if (sp->ss_generation == (uint32_t)-1) {
520275c9da8Seschrock 				sp->ss_generation = gc;
521275c9da8Seschrock 			} else if (sp->ss_generation != gc) {
522275c9da8Seschrock 				if (++retries > LIBSES_MAX_GC_RETRIES) {
523275c9da8Seschrock 					(void) ses_error(ESES_TOOMUCHCHANGE,
524275c9da8Seschrock 					    "too many generation count "
525275c9da8Seschrock 					    "mismatches: page 0x%x gc %u "
526275c9da8Seschrock 					    "previous page %u", dp->spd_gcoff,
527275c9da8Seschrock 					    gc, sp->ss_generation);
528275c9da8Seschrock 					ses_snap_free((ses_snap_t *)sp);
529275c9da8Seschrock 					return (NULL);
530275c9da8Seschrock 				}
531275c9da8Seschrock 				goto again;
532275c9da8Seschrock 			}
533275c9da8Seschrock 		}
534275c9da8Seschrock 	}
535275c9da8Seschrock 
536275c9da8Seschrock 	/*
537275c9da8Seschrock 	 * The LIBSES_TRUNCATE environment variable is a debugging tool which,
538275c9da8Seschrock 	 * if set, randomly truncates all pages (except
539275c9da8Seschrock 	 * SES2_DIAGPAGE_SUPPORTED_PAGES).  In order to be truly evil, we
540275c9da8Seschrock 	 * mmap() each page with enough space after it so we can move the data
541275c9da8Seschrock 	 * up to the end of a page and unmap the following page so that any
542275c9da8Seschrock 	 * attempt to read past the end of the page results in a segfault.
543275c9da8Seschrock 	 */
544275c9da8Seschrock 	if (sp->ss_target->st_truncate) {
545275c9da8Seschrock 		pagesize = PAGESIZE;
546275c9da8Seschrock 
547275c9da8Seschrock 		/*
548275c9da8Seschrock 		 * Count the maximum number of pages we will need and allocate
549275c9da8Seschrock 		 * the necessary space.
550275c9da8Seschrock 		 */
551275c9da8Seschrock 		pages = 0;
552275c9da8Seschrock 		for (pp = sp->ss_pages; pp != NULL; pp = pp->ssp_next) {
553275c9da8Seschrock 			if (pp->ssp_control || pp->ssp_len == 0)
554275c9da8Seschrock 				continue;
555275c9da8Seschrock 
556275c9da8Seschrock 			pages += (P2ROUNDUP(pp->ssp_len, pagesize) /
557275c9da8Seschrock 			    pagesize) + 1;
558275c9da8Seschrock 		}
559275c9da8Seschrock 
560275c9da8Seschrock 		if ((scratch = mmap(NULL, pages * pagesize,
561275c9da8Seschrock 		    PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE,
562275c9da8Seschrock 		    -1, 0)) == MAP_FAILED) {
563275c9da8Seschrock 			(void) ses_error(ESES_NOMEM,
564275c9da8Seschrock 			    "failed to mmap() pages for truncation");
565275c9da8Seschrock 			ses_snap_free(sp);
566275c9da8Seschrock 			return (NULL);
567275c9da8Seschrock 		}
568275c9da8Seschrock 
569275c9da8Seschrock 		for (pp = sp->ss_pages; pp != NULL; pp = pp->ssp_next) {
570275c9da8Seschrock 			if (pp->ssp_control || pp->ssp_len == 0)
571275c9da8Seschrock 				continue;
572275c9da8Seschrock 
573275c9da8Seschrock 			pages = P2ROUNDUP(pp->ssp_len, pagesize) / pagesize;
574275c9da8Seschrock 			pp->ssp_mmap_base = scratch;
575275c9da8Seschrock 			pp->ssp_mmap_len = pages * pagesize;
576275c9da8Seschrock 
577275c9da8Seschrock 			pagelen = lrand48() % pp->ssp_len;
578275c9da8Seschrock 			(void) memcpy(pp->ssp_mmap_base + pp->ssp_mmap_len -
579275c9da8Seschrock 			    pagelen, pp->ssp_page, pagelen);
580275c9da8Seschrock 			ses_free(pp->ssp_page);
581275c9da8Seschrock 			pp->ssp_page = pp->ssp_mmap_base + pp->ssp_mmap_len -
582275c9da8Seschrock 			    pagelen;
583275c9da8Seschrock 			pp->ssp_len = pagelen;
584275c9da8Seschrock 
585275c9da8Seschrock 			(void) munmap(pp->ssp_mmap_base + pages * pagesize,
586275c9da8Seschrock 			    pagesize);
587275c9da8Seschrock 			scratch += (pages + 1) * pagesize;
588275c9da8Seschrock 		}
589275c9da8Seschrock 	}
590275c9da8Seschrock 
591275c9da8Seschrock 
592275c9da8Seschrock 	if (ses_fill_snap(sp) != 0) {
593275c9da8Seschrock 		ses_snap_free(sp);
594275c9da8Seschrock 		return (NULL);
595275c9da8Seschrock 	}
596275c9da8Seschrock 
597275c9da8Seschrock 	(void) pthread_mutex_lock(&tp->st_lock);
598275c9da8Seschrock 	if (tp->st_snapshots != NULL)
599275c9da8Seschrock 		ses_snap_rele_unlocked(tp->st_snapshots);
600275c9da8Seschrock 	sp->ss_next = tp->st_snapshots;
601275c9da8Seschrock 	if (tp->st_snapshots != NULL)
602275c9da8Seschrock 		tp->st_snapshots->ss_prev = sp;
603275c9da8Seschrock 	tp->st_snapshots = sp;
604275c9da8Seschrock 	sp->ss_refcnt = 2;
605275c9da8Seschrock 	(void) pthread_mutex_unlock(&tp->st_lock);
606275c9da8Seschrock 
607275c9da8Seschrock 	return (sp);
608275c9da8Seschrock }
609275c9da8Seschrock 
610275c9da8Seschrock int
ses_snap_do_ctl(ses_snap_t * sp)611275c9da8Seschrock ses_snap_do_ctl(ses_snap_t *sp)
612275c9da8Seschrock {
613275c9da8Seschrock 	ses_snap_page_t *pp, *up;
614275c9da8Seschrock 	int ret = -1;
615275c9da8Seschrock 
616275c9da8Seschrock 	for (pp = sp->ss_pages; pp != NULL; pp = pp->ssp_next) {
617275c9da8Seschrock 		if (!pp->ssp_control)
618275c9da8Seschrock 			continue;
619275c9da8Seschrock 
620275c9da8Seschrock 		if (pp->ssp_initialized && send_control_page(sp, pp) != 0)
621275c9da8Seschrock 			goto error;
622275c9da8Seschrock 
623275c9da8Seschrock 		for (up = pp->ssp_unique; up != NULL; up = up->ssp_next) {
624275c9da8Seschrock 			if (send_control_page(sp, up) != 0)
625275c9da8Seschrock 				goto error;
626275c9da8Seschrock 		}
627275c9da8Seschrock 	}
628275c9da8Seschrock 
629275c9da8Seschrock 	ret = 0;
630275c9da8Seschrock error:
631275c9da8Seschrock 	for (pp = sp->ss_pages; pp != NULL; pp = pp->ssp_next) {
632275c9da8Seschrock 		if (!pp->ssp_control)
633275c9da8Seschrock 			continue;
634275c9da8Seschrock 
635275c9da8Seschrock 		pp->ssp_initialized = B_FALSE;
636275c9da8Seschrock 		while ((up = pp->ssp_unique) != NULL) {
637275c9da8Seschrock 			pp->ssp_unique = up->ssp_next;
638275c9da8Seschrock 			free_snap_page(up);
639275c9da8Seschrock 		}
640275c9da8Seschrock 	}
641275c9da8Seschrock 
642275c9da8Seschrock 
643275c9da8Seschrock 	return (ret);
644275c9da8Seschrock }
645275c9da8Seschrock 
646275c9da8Seschrock uint32_t
ses_snap_generation(ses_snap_t * sp)647275c9da8Seschrock ses_snap_generation(ses_snap_t *sp)
648275c9da8Seschrock {
649275c9da8Seschrock 	return (sp->ss_generation);
650275c9da8Seschrock }
651275c9da8Seschrock 
652275c9da8Seschrock static ses_walk_action_t
ses_walk_node(ses_node_t * np,ses_walk_f func,void * arg)653275c9da8Seschrock ses_walk_node(ses_node_t *np, ses_walk_f func, void *arg)
654275c9da8Seschrock {
655275c9da8Seschrock 	ses_walk_action_t action;
656275c9da8Seschrock 
657275c9da8Seschrock 	for (; np != NULL; np = ses_node_sibling(np)) {
658275c9da8Seschrock 		action = func(np, arg);
659275c9da8Seschrock 		if (action == SES_WALK_ACTION_TERMINATE)
660275c9da8Seschrock 			return (SES_WALK_ACTION_TERMINATE);
661275c9da8Seschrock 		if (action == SES_WALK_ACTION_PRUNE ||
662275c9da8Seschrock 		    ses_node_child(np) == NULL)
663275c9da8Seschrock 			continue;
664275c9da8Seschrock 		if (ses_walk_node(ses_node_child(np), func, arg) ==
665275c9da8Seschrock 		    SES_WALK_ACTION_TERMINATE)
666275c9da8Seschrock 			return (SES_WALK_ACTION_TERMINATE);
667275c9da8Seschrock 	}
668275c9da8Seschrock 
669275c9da8Seschrock 	return (SES_WALK_ACTION_CONTINUE);
670275c9da8Seschrock }
671275c9da8Seschrock 
672275c9da8Seschrock int
ses_walk(ses_snap_t * sp,ses_walk_f func,void * arg)673275c9da8Seschrock ses_walk(ses_snap_t *sp, ses_walk_f func, void *arg)
674275c9da8Seschrock {
675275c9da8Seschrock 	(void) ses_walk_node(ses_root_node(sp), func, arg);
676275c9da8Seschrock 
677275c9da8Seschrock 	return (0);
678275c9da8Seschrock }
679275c9da8Seschrock 
680275c9da8Seschrock /*ARGSUSED*/
681275c9da8Seschrock static ses_walk_action_t
ses_fill_nodes(ses_node_t * np,void * unused)682275c9da8Seschrock ses_fill_nodes(ses_node_t *np, void *unused)
683275c9da8Seschrock {
684275c9da8Seschrock 	np->sn_snapshot->ss_nodes[np->sn_id] = np;
685275c9da8Seschrock 
686275c9da8Seschrock 	return (SES_WALK_ACTION_CONTINUE);
687275c9da8Seschrock }
688275c9da8Seschrock 
689275c9da8Seschrock /*
690275c9da8Seschrock  * Given an ID returned by ses_node_id(), lookup and return the corresponding
691275c9da8Seschrock  * node in the snapshot.  If the snapshot generation count has changed, then
692275c9da8Seschrock  * return failure.
693275c9da8Seschrock  */
694275c9da8Seschrock ses_node_t *
ses_node_lookup(ses_snap_t * sp,uint64_t id)695275c9da8Seschrock ses_node_lookup(ses_snap_t *sp, uint64_t id)
696275c9da8Seschrock {
697275c9da8Seschrock 	uint32_t gen = (id >> 32);
698275c9da8Seschrock 	uint32_t idx = (id & 0xFFFFFFFF);
699275c9da8Seschrock 
700275c9da8Seschrock 	if (sp->ss_generation != gen) {
701275c9da8Seschrock 		(void) ses_set_errno(ESES_CHANGED);
702275c9da8Seschrock 		return (NULL);
703275c9da8Seschrock 	}
704275c9da8Seschrock 
705275c9da8Seschrock 	if (idx >= sp->ss_n_nodes) {
706275c9da8Seschrock 		(void) ses_error(ESES_BAD_NODE,
707275c9da8Seschrock 		    "no such node in snapshot");
708275c9da8Seschrock 		return (NULL);
709275c9da8Seschrock 	}
710275c9da8Seschrock 
711275c9da8Seschrock 	/*
712275c9da8Seschrock 	 * If this is our first lookup attempt, construct the array for fast
713275c9da8Seschrock 	 * lookups.
714275c9da8Seschrock 	 */
715275c9da8Seschrock 	if (sp->ss_nodes == NULL) {
716275c9da8Seschrock 		if ((sp->ss_nodes = ses_zalloc(
717275c9da8Seschrock 		    sp->ss_n_nodes * sizeof (void *))) == NULL)
718275c9da8Seschrock 			return (NULL);
719275c9da8Seschrock 
720275c9da8Seschrock 		(void) ses_walk(sp, ses_fill_nodes, NULL);
721275c9da8Seschrock 	}
722275c9da8Seschrock 
723275c9da8Seschrock 	if (sp->ss_nodes[idx] == NULL)
724275c9da8Seschrock 		(void) ses_error(ESES_BAD_NODE,
725275c9da8Seschrock 		    "no such node in snapshot");
726275c9da8Seschrock 	return (sp->ss_nodes[idx]);
727275c9da8Seschrock }
728