diff options
Diffstat (limited to 'usr/src/lib/scsi/libses/common/ses_snap.c')
-rw-r--r-- | usr/src/lib/scsi/libses/common/ses_snap.c | 700 |
1 files changed, 700 insertions, 0 deletions
diff --git a/usr/src/lib/scsi/libses/common/ses_snap.c b/usr/src/lib/scsi/libses/common/ses_snap.c new file mode 100644 index 0000000000..78e7927061 --- /dev/null +++ b/usr/src/lib/scsi/libses/common/ses_snap.c @@ -0,0 +1,700 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <scsi/libses.h> +#include "ses_impl.h" + +ses_snap_page_t * +ses_snap_find_page(ses_snap_t *sp, ses2_diag_page_t page, boolean_t ctl) +{ + ses_snap_page_t *pp; + + for (pp = sp->ss_pages; pp != NULL; pp = pp->ssp_next) + if (pp->ssp_num == page && pp->ssp_control == ctl && + (pp->ssp_len > 0 || pp->ssp_control)) + return (pp); + + return (NULL); +} + +static int +grow_snap_page(ses_snap_page_t *pp, size_t min) +{ + uint8_t *newbuf; + + if (min == 0 || min < pp->ssp_alloc) + min = pp->ssp_alloc * 2; + + if ((newbuf = ses_realloc(pp->ssp_page, min)) == NULL) + return (-1); + + pp->ssp_page = newbuf; + pp->ssp_alloc = min; + + bzero(newbuf + pp->ssp_len, pp->ssp_alloc - pp->ssp_len); + + return (0); +} + +static ses_snap_page_t * +alloc_snap_page(void) +{ + ses_snap_page_t *pp; + + if ((pp = ses_zalloc(sizeof (ses_snap_page_t))) == NULL) + return (NULL); + + if ((pp->ssp_page = ses_zalloc(SES2_MIN_DIAGPAGE_ALLOC)) == NULL) { + ses_free(pp); + return (NULL); + } + + pp->ssp_num = -1; + pp->ssp_alloc = SES2_MIN_DIAGPAGE_ALLOC; + + return (pp); +} + +static void +free_snap_page(ses_snap_page_t *pp) +{ + if (pp == NULL) + return; + + if (pp->ssp_mmap_base) + (void) munmap(pp->ssp_mmap_base, pp->ssp_mmap_len); + else + ses_free(pp->ssp_page); + ses_free(pp); +} + +static void +free_all_snap_pages(ses_snap_t *sp) +{ + ses_snap_page_t *pp, *np; + + for (pp = sp->ss_pages; pp != NULL; pp = np) { + np = pp->ssp_next; + free_snap_page(pp); + } + + sp->ss_pages = NULL; +} + +/* + * Grow (if needed) the control page buffer, fill in the page code, page + * length, and generation count, and return a pointer to the page. The + * caller is responsible for filling in the rest of the page data. If 'unique' + * is specified, then a new page instance is created instead of sharing the + * current one. + */ +ses_snap_page_t * +ses_snap_ctl_page(ses_snap_t *sp, ses2_diag_page_t page, size_t dlen, + boolean_t unique) +{ + ses_target_t *tp = sp->ss_target; + spc3_diag_page_impl_t *pip; + ses_snap_page_t *pp, *up, **loc; + ses_pagedesc_t *dp; + size_t len; + + pp = ses_snap_find_page(sp, page, B_TRUE); + if (pp == NULL) { + (void) ses_set_errno(ESES_NOTSUP); + return (NULL); + } + + if (pp->ssp_initialized && !unique) + return (pp); + + if (unique) { + /* + * The user has requested a unique instance of the page. Create + * a new ses_snap_page_t instance and chain it off the + * 'ssp_instances' list of the master page. These must be + * appended to the end of the chain, as the order of operations + * may be important (i.e. microcode download). + */ + if ((up = alloc_snap_page()) == NULL) + return (NULL); + + up->ssp_num = pp->ssp_num; + up->ssp_control = B_TRUE; + + for (loc = &pp->ssp_unique; *loc != NULL; + loc = &(*loc)->ssp_next) + ; + + *loc = up; + pp = up; + } + + dp = ses_get_pagedesc(tp, page, SES_PAGE_CTL); + ASSERT(dp != NULL); + + len = dp->spd_ctl_len(sp->ss_n_elem, page, dlen); + if (pp->ssp_alloc < dlen && grow_snap_page(pp, len) != 0) + return (NULL); + pp->ssp_len = len; + bzero(pp->ssp_page, len); + pp->ssp_initialized = B_TRUE; + + pip = (spc3_diag_page_impl_t *)pp->ssp_page; + pip->sdpi_page_code = (uint8_t)page; + SCSI_WRITE16(&pip->sdpi_page_length, + len - offsetof(spc3_diag_page_impl_t, sdpi_data[0])); + if (dp->spd_gcoff != -1) + SCSI_WRITE32((uint8_t *)pip + dp->spd_gcoff, sp->ss_generation); + + return (pp); +} + +static int +read_status_page(ses_snap_t *sp, ses2_diag_page_t page) +{ + libscsi_action_t *ap; + ses_snap_page_t *pp; + ses_target_t *tp; + spc3_diag_page_impl_t *pip; + spc3_receive_diagnostic_results_cdb_t *cp; + uint_t flags; + uint8_t *buf; + size_t alloc; + uint_t retries = 0; + ses2_diag_page_t retpage; + + for (pp = sp->ss_pages; pp != NULL; pp = pp->ssp_next) + if (pp->ssp_num == page && !pp->ssp_control) + break; + + /* + * No matching page. Since the page number is not under consumer or + * device control, this must be a bug. + */ + ASSERT(pp != NULL); + + tp = sp->ss_target; + + flags = LIBSCSI_AF_READ | LIBSCSI_AF_SILENT | LIBSCSI_AF_DIAGNOSE | + LIBSCSI_AF_RQSENSE; + +again: + ap = libscsi_action_alloc(tp->st_scsi_hdl, + SPC3_CMD_RECEIVE_DIAGNOSTIC_RESULTS, flags, pp->ssp_page, + pp->ssp_alloc); + + if (ap == NULL) + return (ses_libscsi_error(tp->st_scsi_hdl, "failed to " + "allocate SCSI action")); + + cp = (spc3_receive_diagnostic_results_cdb_t *) + libscsi_action_get_cdb(ap); + + cp->rdrc_page_code = pp->ssp_num; + cp->rdrc_pcv = 1; + SCSI_WRITE16(&cp->rdrc_allocation_length, + MIN(pp->ssp_alloc, UINT16_MAX)); + + if (libscsi_exec(ap, tp->st_target) != 0) { + libscsi_action_free(ap); + return (ses_libscsi_error(tp->st_scsi_hdl, + "receive diagnostic results failed")); + } + + if (libscsi_action_get_status(ap) != 0) { + (void) ses_scsi_error(ap, + "receive diagnostic results failed"); + libscsi_action_free(ap); + return (-1); + } + + (void) libscsi_action_get_buffer(ap, &buf, &alloc, &pp->ssp_len); + libscsi_action_free(ap); + + ASSERT(buf == pp->ssp_page); + ASSERT(alloc == pp->ssp_alloc); + + if (pp->ssp_len == pp->ssp_alloc && pp->ssp_alloc < UINT16_MAX) { + bzero(pp->ssp_page, pp->ssp_len); + pp->ssp_len = 0; + if (grow_snap_page(pp, 0) != 0) + return (-1); + goto again; + } + + pip = (spc3_diag_page_impl_t *)buf; + + if (pip->sdpi_page_code == page) + return (0); + + retpage = pip->sdpi_page_code; + + bzero(pp->ssp_page, pp->ssp_len); + pp->ssp_len = 0; + + if (retpage == SES2_DIAGPAGE_ENCLOSURE_BUSY) { + if (++retries > LIBSES_MAX_BUSY_RETRIES) + return (ses_error(ESES_BUSY, "too many " + "enclosure busy responses for page 0x%x", page)); + goto again; + } + + return (ses_error(ESES_BAD_RESPONSE, "target returned page 0x%x " + "instead of the requested page 0x%x", retpage, page)); +} + +static int +send_control_page(ses_snap_t *sp, ses_snap_page_t *pp) +{ + ses_target_t *tp; + libscsi_action_t *ap; + spc3_send_diagnostic_cdb_t *cp; + uint_t flags; + + tp = sp->ss_target; + + flags = LIBSCSI_AF_WRITE | LIBSCSI_AF_SILENT | LIBSCSI_AF_DIAGNOSE | + LIBSCSI_AF_RQSENSE; + + ap = libscsi_action_alloc(tp->st_scsi_hdl, SPC3_CMD_SEND_DIAGNOSTIC, + flags, pp->ssp_page, pp->ssp_len); + + if (ap == NULL) + return (ses_libscsi_error(tp->st_scsi_hdl, "failed to " + "allocate SCSI action")); + + cp = (spc3_send_diagnostic_cdb_t *)libscsi_action_get_cdb(ap); + + cp->sdc_pf = 1; + SCSI_WRITE16(&cp->sdc_parameter_list_length, pp->ssp_len); + + if (libscsi_exec(ap, tp->st_target) != 0) { + libscsi_action_free(ap); + return (ses_libscsi_error(tp->st_scsi_hdl, + "SEND DIAGNOSTIC command failed for page 0x%x", + pp->ssp_num)); + } + + if (libscsi_action_get_status(ap) != 0) { + (void) ses_scsi_error(ap, "SEND DIAGNOSTIC command " + "failed for page 0x%x", pp->ssp_num); + libscsi_action_free(ap); + return (-1); + } + + libscsi_action_free(ap); + + return (0); +} + +static int +pages_skel_create(ses_snap_t *sp) +{ + ses_snap_page_t *pp, *np; + ses_target_t *tp = sp->ss_target; + ses2_supported_ses_diag_page_impl_t *pip; + ses2_diag_page_t page; + size_t npages; + size_t pagelen; + off_t i; + + ASSERT(sp->ss_pages == NULL); + + if ((pp = alloc_snap_page()) == NULL) + return (-1); + + pp->ssp_num = SES2_DIAGPAGE_SUPPORTED_PAGES; + pp->ssp_control = B_FALSE; + sp->ss_pages = pp; + + if (read_status_page(sp, SES2_DIAGPAGE_SUPPORTED_PAGES) != 0) { + free_snap_page(pp); + sp->ss_pages = NULL; + return (-1); + } + + pip = pp->ssp_page; + pagelen = pp->ssp_len; + + npages = SCSI_READ16(&pip->sssdpi_page_length); + + for (i = 0; i < npages; i++) { + if (!SES_WITHIN_PAGE(pip->sssdpi_pages + i, 1, pip, + pagelen)) + break; + + page = (ses2_diag_page_t)pip->sssdpi_pages[i]; + /* + * Skip the page we already added during the bootstrap. + */ + if (page == SES2_DIAGPAGE_SUPPORTED_PAGES) + continue; + /* + * The end of the page list may be padded with zeros; ignore + * them all. + */ + if (page == 0 && i > 0) + break; + if ((np = alloc_snap_page()) == NULL) { + free_all_snap_pages(sp); + return (-1); + } + np->ssp_num = page; + pp->ssp_next = np; + pp = np; + + /* + * Allocate a control page as well, if we can use it. + */ + if (ses_get_pagedesc(tp, page, SES_PAGE_CTL) != NULL) { + if ((np = alloc_snap_page()) == NULL) { + free_all_snap_pages(sp); + return (-1); + } + np->ssp_num = page; + np->ssp_control = B_TRUE; + pp->ssp_next = np; + pp = np; + } + } + + return (0); +} + +static void +ses_snap_free(ses_snap_t *sp) +{ + free_all_snap_pages(sp); + ses_node_teardown(sp->ss_root); + ses_free(sp->ss_nodes); + ses_free(sp); +} + +static void +ses_snap_rele_unlocked(ses_snap_t *sp) +{ + ses_target_t *tp = sp->ss_target; + + if (--sp->ss_refcnt != 0) + return; + + if (sp->ss_next != NULL) + sp->ss_next->ss_prev = sp->ss_prev; + + if (sp->ss_prev != NULL) + sp->ss_prev->ss_next = sp->ss_next; + else + tp->st_snapshots = sp->ss_next; + + ses_snap_free(sp); +} + +ses_snap_t * +ses_snap_hold(ses_target_t *tp) +{ + ses_snap_t *sp; + + (void) pthread_mutex_lock(&tp->st_lock); + sp = tp->st_snapshots; + sp->ss_refcnt++; + (void) pthread_mutex_unlock(&tp->st_lock); + + return (sp); +} + +void +ses_snap_rele(ses_snap_t *sp) +{ + ses_target_t *tp = sp->ss_target; + + (void) pthread_mutex_lock(&tp->st_lock); + ses_snap_rele_unlocked(sp); + (void) pthread_mutex_unlock(&tp->st_lock); +} + +ses_snap_t * +ses_snap_new(ses_target_t *tp) +{ + ses_snap_t *sp; + ses_snap_page_t *pp; + uint32_t gc; + uint_t retries = 0; + ses_pagedesc_t *dp; + size_t pages, pagesize, pagelen; + char *scratch; + + if ((sp = ses_zalloc(sizeof (ses_snap_t))) == NULL) + return (NULL); + + sp->ss_target = tp; + +again: + free_all_snap_pages(sp); + + if (pages_skel_create(sp) != 0) { + free(sp); + return (NULL); + } + + sp->ss_generation = (uint32_t)-1; + sp->ss_time = gethrtime(); + + for (pp = sp->ss_pages; pp != NULL; pp = pp->ssp_next) { + /* + * We skip all of: + * + * - Control pages + * - Pages we've already filled in + * - Pages we don't understand (those with no descriptor) + */ + if (pp->ssp_len > 0 || pp->ssp_control) + continue; + if ((dp = ses_get_pagedesc(tp, pp->ssp_num, + SES_PAGE_DIAG)) == NULL) + continue; + + if (read_status_page(sp, pp->ssp_num) != 0) + continue; + + /* + * If the generation code has changed, we don't have a valid + * snapshot. Start over. + */ + if (dp->spd_gcoff != -1 && + dp->spd_gcoff + 4 <= pp->ssp_len) { + gc = SCSI_READ32((uint8_t *)pp->ssp_page + + dp->spd_gcoff); + if (sp->ss_generation == (uint32_t)-1) { + sp->ss_generation = gc; + } else if (sp->ss_generation != gc) { + if (++retries > LIBSES_MAX_GC_RETRIES) { + (void) ses_error(ESES_TOOMUCHCHANGE, + "too many generation count " + "mismatches: page 0x%x gc %u " + "previous page %u", dp->spd_gcoff, + gc, sp->ss_generation); + ses_snap_free((ses_snap_t *)sp); + return (NULL); + } + goto again; + } + } + } + + /* + * The LIBSES_TRUNCATE environment variable is a debugging tool which, + * if set, randomly truncates all pages (except + * SES2_DIAGPAGE_SUPPORTED_PAGES). In order to be truly evil, we + * mmap() each page with enough space after it so we can move the data + * up to the end of a page and unmap the following page so that any + * attempt to read past the end of the page results in a segfault. + */ + if (sp->ss_target->st_truncate) { + pagesize = PAGESIZE; + + /* + * Count the maximum number of pages we will need and allocate + * the necessary space. + */ + pages = 0; + for (pp = sp->ss_pages; pp != NULL; pp = pp->ssp_next) { + if (pp->ssp_control || pp->ssp_len == 0) + continue; + + pages += (P2ROUNDUP(pp->ssp_len, pagesize) / + pagesize) + 1; + } + + if ((scratch = mmap(NULL, pages * pagesize, + PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, + -1, 0)) == MAP_FAILED) { + (void) ses_error(ESES_NOMEM, + "failed to mmap() pages for truncation"); + ses_snap_free(sp); + return (NULL); + } + + for (pp = sp->ss_pages; pp != NULL; pp = pp->ssp_next) { + if (pp->ssp_control || pp->ssp_len == 0) + continue; + + pages = P2ROUNDUP(pp->ssp_len, pagesize) / pagesize; + pp->ssp_mmap_base = scratch; + pp->ssp_mmap_len = pages * pagesize; + + pagelen = lrand48() % pp->ssp_len; + (void) memcpy(pp->ssp_mmap_base + pp->ssp_mmap_len - + pagelen, pp->ssp_page, pagelen); + ses_free(pp->ssp_page); + pp->ssp_page = pp->ssp_mmap_base + pp->ssp_mmap_len - + pagelen; + pp->ssp_len = pagelen; + + (void) munmap(pp->ssp_mmap_base + pages * pagesize, + pagesize); + scratch += (pages + 1) * pagesize; + } + } + + + if (ses_fill_snap(sp) != 0) { + ses_snap_free(sp); + return (NULL); + } + + (void) pthread_mutex_lock(&tp->st_lock); + if (tp->st_snapshots != NULL) + ses_snap_rele_unlocked(tp->st_snapshots); + sp->ss_next = tp->st_snapshots; + if (tp->st_snapshots != NULL) + tp->st_snapshots->ss_prev = sp; + tp->st_snapshots = sp; + sp->ss_refcnt = 2; + (void) pthread_mutex_unlock(&tp->st_lock); + + return (sp); +} + +int +ses_snap_do_ctl(ses_snap_t *sp) +{ + ses_snap_page_t *pp, *up; + int ret = -1; + + for (pp = sp->ss_pages; pp != NULL; pp = pp->ssp_next) { + if (!pp->ssp_control) + continue; + + if (pp->ssp_initialized && send_control_page(sp, pp) != 0) + goto error; + + for (up = pp->ssp_unique; up != NULL; up = up->ssp_next) { + if (send_control_page(sp, up) != 0) + goto error; + } + } + + ret = 0; +error: + for (pp = sp->ss_pages; pp != NULL; pp = pp->ssp_next) { + if (!pp->ssp_control) + continue; + + pp->ssp_initialized = B_FALSE; + while ((up = pp->ssp_unique) != NULL) { + pp->ssp_unique = up->ssp_next; + free_snap_page(up); + } + } + + + return (ret); +} + +uint32_t +ses_snap_generation(ses_snap_t *sp) +{ + return (sp->ss_generation); +} + +static ses_walk_action_t +ses_walk_node(ses_node_t *np, ses_walk_f func, void *arg) +{ + ses_walk_action_t action; + + for (; np != NULL; np = ses_node_sibling(np)) { + action = func(np, arg); + if (action == SES_WALK_ACTION_TERMINATE) + return (SES_WALK_ACTION_TERMINATE); + if (action == SES_WALK_ACTION_PRUNE || + ses_node_child(np) == NULL) + continue; + if (ses_walk_node(ses_node_child(np), func, arg) == + SES_WALK_ACTION_TERMINATE) + return (SES_WALK_ACTION_TERMINATE); + } + + return (SES_WALK_ACTION_CONTINUE); +} + +int +ses_walk(ses_snap_t *sp, ses_walk_f func, void *arg) +{ + (void) ses_walk_node(ses_root_node(sp), func, arg); + + return (0); +} + +/*ARGSUSED*/ +static ses_walk_action_t +ses_fill_nodes(ses_node_t *np, void *unused) +{ + np->sn_snapshot->ss_nodes[np->sn_id] = np; + + return (SES_WALK_ACTION_CONTINUE); +} + +/* + * Given an ID returned by ses_node_id(), lookup and return the corresponding + * node in the snapshot. If the snapshot generation count has changed, then + * return failure. + */ +ses_node_t * +ses_node_lookup(ses_snap_t *sp, uint64_t id) +{ + uint32_t gen = (id >> 32); + uint32_t idx = (id & 0xFFFFFFFF); + + if (sp->ss_generation != gen) { + (void) ses_set_errno(ESES_CHANGED); + return (NULL); + } + + if (idx >= sp->ss_n_nodes) { + (void) ses_error(ESES_BAD_NODE, + "no such node in snapshot"); + return (NULL); + } + + /* + * If this is our first lookup attempt, construct the array for fast + * lookups. + */ + if (sp->ss_nodes == NULL) { + if ((sp->ss_nodes = ses_zalloc( + sp->ss_n_nodes * sizeof (void *))) == NULL) + return (NULL); + + (void) ses_walk(sp, ses_fill_nodes, NULL); + } + + if (sp->ss_nodes[idx] == NULL) + (void) ses_error(ESES_BAD_NODE, + "no such node in snapshot"); + return (sp->ss_nodes[idx]); +} |