#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;
#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
#define HSVC_CHK_REFCNT(hsvcp)
#define HSVC_DEBUG(flag, args)
#define HSVC_DUMP()
#endif
typedef struct hsvc {
struct hsvc *next;
uint64_t group;
uint64_t major;
uint64_t minor;
uint64_t refcnt;
hsvc_info_t *clients;
} hsvc_t;
hsvc_t *hsvc_groups;
hsvc_t *hsvc_avail;
kmutex_t hsvc_lock;
#define HSVC_RESV_BUFS_MAX 16
hsvc_t hsvc_resv_bufs[HSVC_RESV_BUFS_MAX];
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
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);
}
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
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));
}
}
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++;
}
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);
}
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);
if (hsvc_lookup(hsvcinfop) != NULL) {
mutex_exit(&hsvc_lock);
return (EINVAL);
}
for (hsvcp = hsvc_groups; hsvcp != NULL; hsvcp = hsvcp->next)
if (hsvcp->group == api_group)
break;
if (hsvcp) {
if (hsvcp->major != major) {
status = ENOTSUP;
} else if (hsvcp->minor > minor) {
status = ENOTSUP;
*supported_minor = hsvcp->minor;
} else {
*supported_minor = hsvcp->minor;
hsvc_link_client(hsvcp, hsvcinfop);
}
} else {
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:
if (!pre_versioning_group(api_group)) {
status = EINVAL;
break;
} else if (major != 1) {
status = ENOTSUP;
break;
}
*supported_minor = 0;
case H_EOK:
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;
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;
}
}
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);
}
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));
mutex_enter(&hsvc_lock);
for (hsvcpp = &hsvc_groups; (hsvcp = *hsvcpp) != NULL;
hsvcpp = &hsvcp->next) {
if (hsvcp->group != api_group || hsvcp->major != major)
continue;
if (hsvc_unlink_client(hsvcp, hsvcinfop) < 0) {
status = EINVAL;
break;
}
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));
*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);
}
int
hsvc_version(uint64_t api_group, uint64_t *majorp, uint64_t *minorp)
{
int status = 0;
uint64_t hvstat;
hsvc_t *hsvcp;
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:
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);
}
void
hsvc_init(void)
{
int i;
hsvc_t *hsvcp;
mutex_init(&hsvc_lock, NULL, MUTEX_DEFAULT, NULL);
hsvc_groups = NULL;
hsvc_avail = NULL;
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);
}
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";
void
hsvc_setup()
{
int i, status;
uint64_t sup_minor;
hsvc_info_unix_t *hsvcinfop;
hsvc_init();
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();
}