/* * 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 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include #include #include #include #ifdef DEBUG int hsvc_debug = 0; /* HSVC debug flags */ /* * Flags to control HSVC debugging */ #define DBG_HSVC_REGISTER 0x0001 #define DBG_HSVC_UNREGISTER 0x0002 #define DBG_HSVC_OBP_CIF 0x0004 #define DBG_HSVC_ALLOC 0x0008 #define DBG_HSVC_VERSION 0x0010 #define DBG_HSVC_REFCNT 0x0020 #define DBG_HSVC_SETUP 0x0040 #define HSVC_CHK_REFCNT(hsvcp) \ if (hsvc_debug & DBG_HSVC_REFCNT) hsvc_chk_refcnt(hsvcp) #define HSVC_DEBUG(flag, ARGS) \ if (hsvc_debug & flag) prom_printf ARGS #define HSVC_DUMP() \ if (hsvc_debug & DBG_HSVC_SETUP) hsvc_dump() #else /* DEBUG */ #define HSVC_CHK_REFCNT(hsvcp) #define HSVC_DEBUG(flag, args) #define HSVC_DUMP() #endif /* DEBUG */ /* * Each hypervisor API group negotiation is tracked via a * hsvc structure. This structure contains the API group, * currently negotiated major/minor number, a singly linked * list of clients currently registered and a reference count. * * Since the number of API groups is fairly small, negotiated * API groups are maintained via a singly linked list. Also, * sufficient free space is reserved to allow for API group * registration before kmem_xxx interface can be used to * allocate memory dynamically. * * Note that all access to the API group lookup and negotiation * is serialized to support strict HV API interface. */ typedef struct hsvc { struct hsvc *next; /* next group/free entry */ uint64_t group; /* hypervisor service group */ uint64_t major; /* major number */ uint64_t minor; /* minor number */ uint64_t refcnt; /* reference count */ hsvc_info_t *clients; /* linked list of clients */ } hsvc_t; /* * Global variables */ hsvc_t *hsvc_groups; /* linked list of API groups in use */ hsvc_t *hsvc_avail; /* free reserved buffers */ kmutex_t hsvc_lock; /* protects linked list and globals */ /* * Preallocate some space for boot requirements (before kmem_xxx can be * used) */ #define HSVC_RESV_BUFS_MAX 16 hsvc_t hsvc_resv_bufs[HSVC_RESV_BUFS_MAX]; /* * Pre-versioning groups (not negotiated by Ontario/Erie FCS release) */ static uint64_t hsvc_pre_versioning_groups[] = { HSVC_GROUP_SUN4V, HSVC_GROUP_CORE, HSVC_GROUP_VPCI, HSVC_GROUP_VSC, HSVC_GROUP_NIAGARA_CPU, HSVC_GROUP_NCS, HSVC_GROUP_DIAG }; #define HSVC_PRE_VERSIONING_GROUP_CNT \ (sizeof (hsvc_pre_versioning_groups) / sizeof (uint64_t)) static boolean_t pre_versioning_group(uint64_t api_group) { int i; for (i = 0; i < HSVC_PRE_VERSIONING_GROUP_CNT; i++) if (hsvc_pre_versioning_groups[i] == api_group) return (B_TRUE); return (B_FALSE); } static hsvc_t * hsvc_lookup(hsvc_info_t *hsvcinfop) { hsvc_t *hsvcp; hsvc_info_t *p; for (hsvcp = hsvc_groups; hsvcp != NULL; hsvcp = hsvcp->next) { for (p = hsvcp->clients; p != NULL; p = (hsvc_info_t *)p->hsvc_private) if (p == hsvcinfop) break; if (p) break; } return (hsvcp); } #ifdef DEBUG /* * Check client reference count */ static void hsvc_chk_refcnt(hsvc_t *hsvcp) { int refcnt; hsvc_info_t *p; for (refcnt = 0, p = hsvcp->clients; p != NULL; p = (hsvc_info_t *)p->hsvc_private) refcnt++; ASSERT(hsvcp->refcnt == refcnt); } /* * Dump registered clients information */ static void hsvc_dump(void) { hsvc_t *hsvcp; hsvc_info_t *p; mutex_enter(&hsvc_lock); prom_printf("hsvc_dump: hsvc_groups: %p hsvc_avail: %p\n", (void *)hsvc_groups, (void *)hsvc_avail); for (hsvcp = hsvc_groups; hsvcp != NULL; hsvcp = hsvcp->next) { prom_printf(" hsvcp: %p (0x%lx 0x%lx 0x%lx) ref: %ld clients: " "%p\n", (void *)hsvcp, hsvcp->group, hsvcp->major, hsvcp->minor, hsvcp->refcnt, (void *)hsvcp->clients); for (p = hsvcp->clients; p != NULL; p = (hsvc_info_t *)p->hsvc_private) { prom_printf(" client %p (0x%lx 0x%lx 0x%lx '%s') " "private: %p\n", (void *)p, p->hsvc_group, p->hsvc_major, p->hsvc_minor, p->hsvc_modname, p->hsvc_private); } } mutex_exit(&hsvc_lock); } #endif /* DEBUG */ /* * Allocate a buffer to cache API group information. Note that we * allocate a buffer from reserved pool early on, before kmem_xxx * interface becomes available. */ static hsvc_t * hsvc_alloc(void) { hsvc_t *hsvcp; ASSERT(MUTEX_HELD(&hsvc_lock)); if (hsvc_avail != NULL) { hsvcp = hsvc_avail; hsvc_avail = hsvcp->next; } else if (kmem_ready) { hsvcp = kmem_zalloc(sizeof (hsvc_t), KM_SLEEP); HSVC_DEBUG(DBG_HSVC_ALLOC, ("hsvc_alloc: hsvc_avail: %p kmem_zalloc hsvcp: %p\n", (void *)hsvc_avail, (void *)hsvcp)); } else hsvcp = NULL; return (hsvcp); } static void hsvc_free(hsvc_t *hsvcp) { ASSERT(hsvcp != NULL); ASSERT(MUTEX_HELD(&hsvc_lock)); if (hsvcp >= hsvc_resv_bufs && hsvcp < &hsvc_resv_bufs[HSVC_RESV_BUFS_MAX]) { hsvcp->next = hsvc_avail; hsvc_avail = hsvcp; } else { HSVC_DEBUG(DBG_HSVC_ALLOC, ("hsvc_free: hsvc_avail: %p kmem_free hsvcp: %p\n", (void *)hsvc_avail, (void *)hsvcp)); (void) kmem_free(hsvcp, sizeof (hsvc_t)); } } /* * Link client on the specified hsvc's client list and * bump the reference count. */ static void hsvc_link_client(hsvc_t *hsvcp, hsvc_info_t *hsvcinfop) { ASSERT(MUTEX_HELD(&hsvc_lock)); HSVC_CHK_REFCNT(hsvcp); hsvcinfop->hsvc_private = hsvcp->clients; hsvcp->clients = hsvcinfop; hsvcp->refcnt++; } /* * Unlink a client from the specified hsvc's client list and * decrement the reference count, if found. * * Return 0 if client unlinked. Otherwise return -1. */ static int hsvc_unlink_client(hsvc_t *hsvcp, hsvc_info_t *hsvcinfop) { hsvc_info_t *p, **pp; int status = 0; ASSERT(MUTEX_HELD(&hsvc_lock)); HSVC_CHK_REFCNT(hsvcp); for (pp = &hsvcp->clients; (p = *pp) != NULL; pp = (hsvc_info_t **)&p->hsvc_private) { if (p != hsvcinfop) continue; ASSERT(hsvcp->refcnt > 0); hsvcp->refcnt--; *pp = (hsvc_info_t *)p->hsvc_private; p->hsvc_private = NULL; break; } if (p == NULL) status = -1; return (status); } /* * Negotiate/register an API group usage */ int hsvc_register(hsvc_info_t *hsvcinfop, uint64_t *supported_minor) { hsvc_t *hsvcp; uint64_t api_group = hsvcinfop->hsvc_group; uint64_t major = hsvcinfop->hsvc_major; uint64_t minor = hsvcinfop->hsvc_minor; int status = 0; HSVC_DEBUG(DBG_HSVC_REGISTER, ("hsvc_register %p (0x%lx 0x%lx 0x%lx ID %s)\n", (void *)hsvcinfop, api_group, major, minor, hsvcinfop->hsvc_modname)); if (hsvcinfop->hsvc_rev != HSVC_REV_1) return (EINVAL); mutex_enter(&hsvc_lock); /* * Make sure that the hsvcinfop is new (i.e. not already registered). */ if (hsvc_lookup(hsvcinfop) != NULL) { mutex_exit(&hsvc_lock); return (EINVAL); } /* * Search for the specified api_group */ for (hsvcp = hsvc_groups; hsvcp != NULL; hsvcp = hsvcp->next) if (hsvcp->group == api_group) break; if (hsvcp) { /* * If major number mismatch, then return ENOTSUP. * Otherwise return currently negotiated minor * and the following status: * ENOTSUP requested minor < current minor * OK requested minor >= current minor */ if (hsvcp->major != major) { status = ENOTSUP; } else if (hsvcp->minor > minor) { /* * Client requested a lower minor number than * currently in use. */ status = ENOTSUP; *supported_minor = hsvcp->minor; } else { /* * Client requested a minor number same or higher * than the one in use. Set supported minor number * and link the client on hsvc client linked list. */ *supported_minor = hsvcp->minor; hsvc_link_client(hsvcp, hsvcinfop); } } else { /* * This service group has not been negotiated yet. * Call OBP CIF interface to negotiate a major/minor * number. * * If not enough memory to cache this information, then * return EAGAIN so that the caller can try again later. * Otherwise, process OBP CIF results as follows: * * H_BADTRAP OBP CIF interface is not supported. * If not a pre-versioning group, then * return EINVAL, indicating unsupported * API group. Otherwise, mimic default * behavior (i.e. support only major=1). * * H_EOK Negotiation was successful. Cache * and return supported major/minor, * limiting the minor number to the * requested value. * * H_EINVAL Invalid group. Return EINVAL * * H_ENOTSUPPORTED Unsupported major number. Return * ENOTSUP. * * H_EBUSY Return EAGAIN. * * H_EWOULDBLOCK Return EAGAIN. */ hsvcp = hsvc_alloc(); if (hsvcp == NULL) { status = EAGAIN; } else { uint64_t hvstat; hvstat = prom_set_sun4v_api_version(api_group, major, minor, supported_minor); HSVC_DEBUG(DBG_HSVC_OBP_CIF, ("prom_set_sun4v_api_ver: 0x%lx 0x%lx, 0x%lx " " hvstat: 0x%lx sup_minor: 0x%lx\n", api_group, major, minor, hvstat, *supported_minor)); switch (hvstat) { case H_EBADTRAP: /* * Older firmware does not support OBP CIF * interface. If it's a pre-versioning group, * then assume that the firmware supports * only major=1 and minor=0. */ if (!pre_versioning_group(api_group)) { status = EINVAL; break; } else if (major != 1) { status = ENOTSUP; break; } /* * It's a preversioning group. Default minor * value to 0. */ *supported_minor = 0; /*FALLTHROUGH*/ case H_EOK: /* * Limit supported minor number to the * requested value and cache the new * API group information. */ if (*supported_minor > minor) *supported_minor = minor; hsvcp->group = api_group; hsvcp->major = major; hsvcp->minor = *supported_minor; hsvcp->refcnt = 0; hsvcp->clients = NULL; hsvcp->next = hsvc_groups; hsvc_groups = hsvcp; /* * Link the caller on the client linked list. */ hsvc_link_client(hsvcp, hsvcinfop); break; case H_ENOTSUPPORTED: status = ENOTSUP; break; case H_EBUSY: case H_EWOULDBLOCK: status = EAGAIN; break; case H_EINVAL: default: status = EINVAL; break; } } /* * Deallocate entry if not used */ if (status != 0) hsvc_free(hsvcp); } mutex_exit(&hsvc_lock); HSVC_DEBUG(DBG_HSVC_REGISTER, ("hsvc_register(%p) status; %d sup_minor: 0x%lx\n", (void *)hsvcinfop, status, *supported_minor)); return (status); } /* * Unregister an API group usage */ int hsvc_unregister(hsvc_info_t *hsvcinfop) { hsvc_t **hsvcpp, *hsvcp; uint64_t api_group; uint64_t major, supported_minor; int status = 0; if (hsvcinfop->hsvc_rev != HSVC_REV_1) return (EINVAL); major = hsvcinfop->hsvc_major; api_group = hsvcinfop->hsvc_group; HSVC_DEBUG(DBG_HSVC_UNREGISTER, ("hsvc_unregister %p (0x%lx 0x%lx 0x%lx ID %s)\n", (void *)hsvcinfop, api_group, major, hsvcinfop->hsvc_minor, hsvcinfop->hsvc_modname)); /* * Search for the matching entry and return EINVAL if no match found. * Otherwise, remove it from our list and unregister the API * group if this was the last reference to that API group. */ mutex_enter(&hsvc_lock); for (hsvcpp = &hsvc_groups; (hsvcp = *hsvcpp) != NULL; hsvcpp = &hsvcp->next) { if (hsvcp->group != api_group || hsvcp->major != major) continue; /* * Search client list for a matching hsvcinfop entry * and unlink it and decrement refcnt, if found. */ if (hsvc_unlink_client(hsvcp, hsvcinfop) < 0) { /* client not registered */ status = EINVAL; break; } /* * Client has been unlinked. If this was the last * reference, unregister API group via OBP CIF * interface. */ if (hsvcp->refcnt == 0) { uint64_t hvstat; ASSERT(hsvcp->clients == NULL); hvstat = prom_set_sun4v_api_version(api_group, 0, 0, &supported_minor); HSVC_DEBUG(DBG_HSVC_OBP_CIF, (" prom unreg group: 0x%lx hvstat: 0x%lx\n", api_group, hvstat)); /* * Note that the call to unnegotiate an API group * may fail if anyone, including OBP, is using * those services. However, the caller is done * with this API group and should be allowed to * unregister regardless of the outcome. */ *hsvcpp = hsvcp->next; hsvc_free(hsvcp); } break; } if (hsvcp == NULL) status = EINVAL; mutex_exit(&hsvc_lock); HSVC_DEBUG(DBG_HSVC_UNREGISTER, ("hsvc_unregister %p status: %d\n", (void *)hsvcinfop, status)); return (status); } /* * Get negotiated major/minor version number for an API group */ int hsvc_version(uint64_t api_group, uint64_t *majorp, uint64_t *minorp) { int status = 0; uint64_t hvstat; hsvc_t *hsvcp; /* * Check if the specified api_group is already in use. * If so, return currently negotiated major/minor number. * Otherwise, call OBP CIF interface to get the currently * negotiated major/minor number. */ mutex_enter(&hsvc_lock); for (hsvcp = hsvc_groups; hsvcp != NULL; hsvcp = hsvcp->next) if (hsvcp->group == api_group) break; if (hsvcp) { *majorp = hsvcp->major; *minorp = hsvcp->minor; } else { hvstat = prom_get_sun4v_api_version(api_group, majorp, minorp); switch (hvstat) { case H_EBADTRAP: /* * Older firmware does not support OBP CIF * interface. If it's a pre-versioning group, * then return default major/minor (i.e. 1/0). * Otherwise, return EINVAL. */ if (pre_versioning_group(api_group)) { *majorp = 1; *minorp = 0; } else status = EINVAL; break; case H_EINVAL: default: status = EINVAL; break; } } mutex_exit(&hsvc_lock); HSVC_DEBUG(DBG_HSVC_VERSION, ("hsvc_version(0x%lx) status: %d major: 0x%lx minor: 0x%lx\n", api_group, status, *majorp, *minorp)); return (status); } /* * Initialize framework data structures */ void hsvc_init(void) { int i; hsvc_t *hsvcp; /* * Initialize global data structures */ mutex_init(&hsvc_lock, NULL, MUTEX_DEFAULT, NULL); hsvc_groups = NULL; hsvc_avail = NULL; /* * Setup initial free list */ mutex_enter(&hsvc_lock); for (i = 0, hsvcp = &hsvc_resv_bufs[0]; i < HSVC_RESV_BUFS_MAX; i++, hsvcp++) hsvc_free(hsvcp); mutex_exit(&hsvc_lock); } /* * Hypervisor services to be negotiated at boot time. * * Note that the kernel needs to negotiate the HSVC_GROUP_SUN4V * API group first, before doing any other negotiation. Also, it * uses hypervisor services belonging to the HSVC_GROUP_CORE API * group only for itself. * * Note that the HSVC_GROUP_DIAG is negotiated on behalf of * any driver/module using DIAG services. */ typedef struct hsvc_info_unix_s { hsvc_info_t hsvcinfo; int required; } hsvc_info_unix_t; static hsvc_info_unix_t hsvcinfo_unix[] = { {{HSVC_REV_1, NULL, HSVC_GROUP_SUN4V, 1, 0, NULL}, 1}, {{HSVC_REV_1, NULL, HSVC_GROUP_CORE, 1, 2, NULL}, 1}, {{HSVC_REV_1, NULL, HSVC_GROUP_DIAG, 1, 0, NULL}, 1}, {{HSVC_REV_1, NULL, HSVC_GROUP_INTR, 1, 0, NULL}, 0}, }; #define HSVCINFO_UNIX_CNT (sizeof (hsvcinfo_unix) / sizeof (hsvc_info_t)) static char *hsvcinfo_unix_modname = "unix"; /* * Initialize framework and register hypervisor services to be used * by the kernel. */ void hsvc_setup() { int i, status; uint64_t sup_minor; hsvc_info_unix_t *hsvcinfop; /* * Initialize framework */ hsvc_init(); /* * Negotiate versioning for required groups */ for (hsvcinfop = &hsvcinfo_unix[0], i = 0; i < HSVCINFO_UNIX_CNT; i++, hsvcinfop++) { hsvcinfop->hsvcinfo.hsvc_private = NULL; hsvcinfop->hsvcinfo.hsvc_modname = hsvcinfo_unix_modname; status = hsvc_register(&(hsvcinfop->hsvcinfo), &sup_minor); if ((status != 0) && hsvcinfop->required) { cmn_err(CE_PANIC, "%s: cannot negotiate hypervisor " "services - group: 0x%lx major: 0x%lx minor: 0x%lx" " errno: %d\n", hsvcinfop->hsvcinfo.hsvc_modname, hsvcinfop->hsvcinfo.hsvc_group, hsvcinfop->hsvcinfo.hsvc_major, hsvcinfop->hsvcinfo.hsvc_minor, status); } } HSVC_DUMP(); }