/*
* 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 <sys/types.h>
#include <sys/dditypes.h>
#include <sys/machsystm.h>
#include <sys/archsystm.h>
#include <sys/prom_plat.h>
#include <sys/promif.h>
#include <sys/kmem.h>
#include <sys/hypervisor_api.h>
#include <sys/hsvc.h>
#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},
{{HSVC_REV_1, NULL, HSVC_GROUP_REBOOT_DATA, 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();
}