#include <sys/types.h>
#include <sys/file.h>
#include <sys/errno.h>
#include <sys/open.h>
#include <sys/cred.h>
#include <sys/uio.h>
#include <sys/stat.h>
#include <sys/ksynch.h>
#include <sys/modctl.h>
#include <sys/conf.h>
#include <sys/devops.h>
#include <sys/debug.h>
#include <sys/cmn_err.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/ds.h>
#include <sys/hypervisor_api.h>
#include <sys/machsystm.h>
#include <sys/sysmacros.h>
#include <sys/hsvc.h>
#include <sys/bitmap.h>
#include <sys/ds_pri.h>
static uint_t ds_pri_debug = 0;
#define DS_PRI_DBG if (ds_pri_debug) printf
#define DS_PRI_NAME "ds_pri"
#define TEST_HARNESS
#ifdef TEST_HARNESS
#define DS_PRI_MAX_PRI_SIZE (64 * 1024)
#define DSIOC_TEST_REG 97
#define DSIOC_TEST_UNREG 98
#define DSIOC_TEST_DATA 99
struct ds_pri_test_data {
size_t size;
void *data;
};
struct ds_pri_test_data32 {
size32_t size;
caddr32_t data;
};
#endif
typedef enum {
DS_PRI_REQUEST = 0,
DS_PRI_DATA = 1,
DS_PRI_UPDATE = 2
} ds_pri_msg_type_t;
typedef struct {
struct {
uint64_t seq_num;
uint64_t type;
} hdr;
uint8_t data[1];
} ds_pri_msg_t;
typedef enum {
DS_PRI_NO_SERVICE = 0x0,
DS_PRI_HAS_SERVICE = 0x1,
DS_PRI_REQUESTED = 0x2,
DS_PRI_HAS_PRI = 0x4
} ds_pri_flags_t;
struct ds_pri_state {
dev_info_t *dip;
int instance;
kmutex_t lock;
kcondvar_t cv;
ds_pri_flags_t state;
uint64_t gencount;
ds_svc_hdl_t ds_pri_handle;
void *ds_pri;
size_t ds_pri_len;
uint64_t req_id;
uint64_t last_req_id;
int num_opens;
};
typedef struct ds_pri_state ds_pri_state_t;
static void *ds_pri_statep;
static void request_pri(ds_pri_state_t *sp);
static uint64_t ds_get_hv_pri(ds_pri_state_t *sp);
static int ds_pri_getinfo(dev_info_t *, ddi_info_cmd_t, void *, void **);
static int ds_pri_attach(dev_info_t *, ddi_attach_cmd_t);
static int ds_pri_detach(dev_info_t *, ddi_detach_cmd_t);
static int ds_pri_open(dev_t *, int, int, cred_t *);
static int ds_pri_close(dev_t, int, int, cred_t *);
static int ds_pri_read(dev_t, struct uio *, cred_t *);
static int ds_pri_ioctl(dev_t, int, intptr_t, int, cred_t *, int *);
static void ds_pri_reg_handler(ds_cb_arg_t, ds_ver_t *, ds_svc_hdl_t);
static void ds_pri_unreg_handler(ds_cb_arg_t arg);
static void ds_pri_data_handler(ds_cb_arg_t arg, void *buf, size_t buflen);
static ds_ver_t ds_pri_ver_1_0 = { 1, 0 };
static ds_capability_t ds_pri_cap = {
"pri",
&ds_pri_ver_1_0,
1
};
static ds_clnt_ops_t ds_pri_ops = {
ds_pri_reg_handler,
ds_pri_unreg_handler,
ds_pri_data_handler,
NULL
};
static struct cb_ops ds_pri_cb_ops = {
ds_pri_open,
ds_pri_close,
nodev,
nodev,
nodev,
ds_pri_read,
nodev,
ds_pri_ioctl,
nodev,
nodev,
nodev,
nochpoll,
ddi_prop_op,
(struct streamtab *)NULL,
D_MP | D_64BIT,
CB_REV,
nodev,
nodev
};
static struct dev_ops ds_pri_dev_ops = {
DEVO_REV,
0,
ds_pri_getinfo,
nulldev,
nulldev,
ds_pri_attach,
ds_pri_detach,
nodev,
&ds_pri_cb_ops,
(struct bus_ops *)NULL,
nulldev,
ddi_quiesce_not_needed,
};
static struct modldrv modldrv = {
&mod_driverops,
"Domain Services PRI Driver",
&ds_pri_dev_ops
};
static struct modlinkage modlinkage = {
MODREV_1,
(void *)&modldrv,
NULL
};
static boolean_t hsvc_pboot_available = B_FALSE;
static hsvc_info_t pboot_hsvc = {
HSVC_REV_1, NULL, HSVC_GROUP_PBOOT, 1, 0, NULL
};
int
_init(void)
{
int retval;
uint64_t hsvc_pboot_minor;
uint64_t status;
status = hsvc_register(&pboot_hsvc, &hsvc_pboot_minor);
if (status == H_EOK) {
hsvc_pboot_available = B_TRUE;
} else {
DS_PRI_DBG("hypervisor services not negotiated "
"for group number: 0x%lx errorno: 0x%lx\n",
pboot_hsvc.hsvc_group, status);
}
retval = ddi_soft_state_init(&ds_pri_statep,
sizeof (ds_pri_state_t), 0);
if (retval != 0)
return (retval);
retval = mod_install(&modlinkage);
if (retval != 0) {
ddi_soft_state_fini(&ds_pri_statep);
return (retval);
}
return (retval);
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&modlinkage, modinfop));
}
int
_fini(void)
{
int retval;
if ((retval = mod_remove(&modlinkage)) != 0)
return (retval);
ddi_soft_state_fini(&ds_pri_statep);
if (hsvc_pboot_available)
(void) hsvc_unregister(&pboot_hsvc);
return (retval);
}
static int
ds_pri_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **resultp)
{
ds_pri_state_t *sp;
int retval = DDI_FAILURE;
ASSERT(resultp != NULL);
switch (cmd) {
case DDI_INFO_DEVT2DEVINFO:
sp = ddi_get_soft_state(ds_pri_statep, getminor((dev_t)arg));
if (sp != NULL) {
*resultp = sp->dip;
retval = DDI_SUCCESS;
} else
*resultp = NULL;
break;
case DDI_INFO_DEVT2INSTANCE:
*resultp = (void *)(uintptr_t)getminor((dev_t)arg);
retval = DDI_SUCCESS;
break;
default:
break;
}
return (retval);
}
static int
ds_pri_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
int instance;
ds_pri_state_t *sp;
int rv;
uint64_t status;
switch (cmd) {
case DDI_ATTACH:
break;
case DDI_RESUME:
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
instance = ddi_get_instance(dip);
if (ddi_soft_state_zalloc(ds_pri_statep, instance) !=
DDI_SUCCESS) {
cmn_err(CE_WARN, "%s@%d: Unable to allocate state",
DS_PRI_NAME, instance);
return (DDI_FAILURE);
}
sp = ddi_get_soft_state(ds_pri_statep, instance);
mutex_init(&sp->lock, NULL, MUTEX_DEFAULT, NULL);
cv_init(&sp->cv, NULL, CV_DEFAULT, NULL);
if (ddi_create_minor_node(dip, DS_PRI_NAME, S_IFCHR, instance,
DDI_PSEUDO, 0) != DDI_SUCCESS) {
cmn_err(CE_WARN, "%s@%d: Unable to create minor node",
DS_PRI_NAME, instance);
goto fail;
}
if (ds_pri_ops.cb_arg != NULL)
goto fail;
ds_pri_ops.cb_arg = dip;
sp->state = DS_PRI_NO_SERVICE;
sp->ds_pri_handle = DS_INVALID_HDL;
sp->ds_pri = NULL;
sp->ds_pri_len = 0;
sp->req_id = 0;
sp->num_opens = 0;
if (hsvc_pboot_available) {
if ((status = ds_get_hv_pri(sp)) != 0) {
cmn_err(CE_NOTE, "ds_get_hv_pri failed: 0x%lx", status);
}
}
if ((rv = ds_cap_init(&ds_pri_cap, &ds_pri_ops)) != 0) {
cmn_err(CE_NOTE, "ds_cap_init failed: %d", rv);
goto fail;
}
ddi_report_dev(dip);
return (DDI_SUCCESS);
fail:
if (sp->ds_pri)
kmem_free(sp->ds_pri, sp->ds_pri_len);
ddi_remove_minor_node(dip, NULL);
cv_destroy(&sp->cv);
mutex_destroy(&sp->lock);
ddi_soft_state_free(ds_pri_statep, instance);
return (DDI_FAILURE);
}
static int
ds_pri_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
ds_pri_state_t *sp;
int instance;
int rv;
instance = ddi_get_instance(dip);
sp = ddi_get_soft_state(ds_pri_statep, instance);
switch (cmd) {
case DDI_DETACH:
break;
case DDI_SUSPEND:
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
if ((rv = ds_cap_fini(&ds_pri_cap)) != 0) {
cmn_err(CE_WARN, "ds_cap_fini failed: %d", rv);
}
if (sp != NULL && sp->ds_pri_len != 0)
kmem_free(sp->ds_pri, sp->ds_pri_len);
ds_pri_ops.cb_arg = NULL;
ddi_remove_minor_node(dip, NULL);
cv_destroy(&sp->cv);
mutex_destroy(&sp->lock);
ddi_soft_state_free(ds_pri_statep, instance);
return (DDI_SUCCESS);
}
static int
ds_pri_open(dev_t *devp, int flag, int otyp, cred_t *credp)
{
ds_pri_state_t *sp;
int instance;
if (otyp != OTYP_CHR)
return (EINVAL);
instance = getminor(*devp);
sp = ddi_get_soft_state(ds_pri_statep, instance);
if (sp == NULL)
return (ENXIO);
mutex_enter(&sp->lock);
if (!(sp->state & DS_PRI_HAS_PRI)) {
while (!(sp->state & DS_PRI_HAS_SERVICE)) {
if (cv_wait_sig(&sp->cv, &sp->lock) == 0) {
mutex_exit(&sp->lock);
return (EINTR);
}
}
}
sp->num_opens++;
mutex_exit(&sp->lock);
DS_PRI_DBG("ds_pri_open: state = 0x%x\n", sp->state);
return (0);
}
static int
ds_pri_close(dev_t dev, int flag, int otyp, cred_t *credp)
{
int instance;
ds_pri_state_t *sp;
if (otyp != OTYP_CHR)
return (EINVAL);
DS_PRI_DBG("ds_pri_close\n");
instance = getminor(dev);
if ((sp = ddi_get_soft_state(ds_pri_statep, instance)) == NULL)
return (ENXIO);
mutex_enter(&sp->lock);
if (!(sp->state & DS_PRI_HAS_SERVICE)) {
mutex_exit(&sp->lock);
return (0);
}
if (--sp->num_opens > 0) {
mutex_exit(&sp->lock);
return (0);
}
sp->state &= ~DS_PRI_REQUESTED;
mutex_exit(&sp->lock);
return (0);
}
static int
ds_pri_read(dev_t dev, struct uio *uiop, cred_t *credp)
{
ds_pri_state_t *sp;
int instance;
size_t len;
int retval;
caddr_t tmpbufp;
offset_t off = uiop->uio_offset;
instance = getminor(dev);
if ((sp = ddi_get_soft_state(ds_pri_statep, instance)) == NULL)
return (ENXIO);
len = uiop->uio_resid;
if (len == 0)
return (0);
mutex_enter(&sp->lock);
DS_PRI_DBG("ds_pri_read: state = 0x%x\n", sp->state);
if (!(sp->state & DS_PRI_HAS_PRI)) {
DS_PRI_DBG("ds_pri_read: no PRI held\n");
if (uiop->uio_fmode & (FNDELAY | FNONBLOCK)) {
mutex_exit(&sp->lock);
return (EAGAIN);
}
while (!(sp->state & DS_PRI_HAS_PRI)) {
DS_PRI_DBG("ds_pri_read: state = 0x%x\n", sp->state);
request_pri(sp);
if (cv_wait_sig(&sp->cv, &sp->lock) == 0) {
mutex_exit(&sp->lock);
return (EINTR);
}
}
}
if (len > sp->ds_pri_len)
len = sp->ds_pri_len;
if (len == 0) {
mutex_exit(&sp->lock);
return (0);
}
tmpbufp = kmem_alloc(len, KM_SLEEP);
bcopy(((caddr_t)sp->ds_pri), tmpbufp, len);
mutex_exit(&sp->lock);
retval = uiomove(tmpbufp, len, UIO_READ, uiop);
kmem_free(tmpbufp, len);
uiop->uio_offset = off;
return (retval);
}
static int
ds_pri_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp,
int *rvalp)
{
ds_pri_state_t *sp;
int instance;
instance = getminor(dev);
if ((sp = ddi_get_soft_state(ds_pri_statep, instance)) == NULL)
return (ENXIO);
switch (cmd) {
case DSPRI_GETINFO: {
struct dspri_info info;
if (!(mode & FREAD))
return (EACCES);
mutex_enter(&sp->lock);
loop:;
if (sp->state & DS_PRI_HAS_PRI) {
info.size = sp->ds_pri_len;
info.token = sp->gencount;
} else
if (!(sp->state & DS_PRI_HAS_SERVICE)) {
info.size = 0;
info.token = 0;
} else {
request_pri(sp);
if (cv_wait_sig(&sp->cv, &sp->lock) == 0) {
mutex_exit(&sp->lock);
return (EINTR);
}
goto loop;
}
DS_PRI_DBG("ds_pri_ioctl: DSPRI_GETINFO sz=0x%lx tok=0x%lx\n",
info.size, info.token);
mutex_exit(&sp->lock);
if (ddi_copyout(&info, (void *)arg, sizeof (info), mode) != 0)
return (EFAULT);
break;
}
case DSPRI_WAIT: {
uint64_t gencount;
if (ddi_copyin((void *)arg, &gencount, sizeof (gencount),
mode) != 0)
return (EFAULT);
mutex_enter(&sp->lock);
DS_PRI_DBG("ds_pri_ioctl: DSPRI_WAIT gen=0x%lx sp->gen=0x%lx\n",
gencount, sp->gencount);
while ((sp->state & DS_PRI_HAS_PRI) == 0 ||
gencount == sp->gencount) {
if ((sp->state & DS_PRI_HAS_PRI) == 0)
request_pri(sp);
if (cv_wait_sig(&sp->cv, &sp->lock) == 0) {
mutex_exit(&sp->lock);
return (EINTR);
}
}
mutex_exit(&sp->lock);
break;
}
default:
return (ENOTTY);
}
return (0);
}
static void
request_pri(ds_pri_state_t *sp)
{
ds_pri_msg_t reqmsg;
ASSERT(MUTEX_HELD(&sp->lock));
if (!(sp->state & DS_PRI_HAS_SERVICE))
return;
if (sp->state & DS_PRI_REQUESTED)
return;
if (sp->state & DS_PRI_HAS_PRI) {
ASSERT(sp->ds_pri_len != 0);
ASSERT(sp->ds_pri != NULL);
kmem_free(sp->ds_pri, sp->ds_pri_len);
sp->ds_pri_len = 0;
sp->ds_pri = NULL;
sp->state &= ~DS_PRI_HAS_PRI;
} else {
ASSERT(sp->ds_pri == NULL);
ASSERT(sp->ds_pri_len == 0);
}
reqmsg.hdr.seq_num = ++(sp->req_id);
reqmsg.hdr.type = DS_PRI_REQUEST;
DS_PRI_DBG("request_pri: request id 0x%lx\n", sp->req_id);
(void) ds_cap_send(sp->ds_pri_handle, &reqmsg, sizeof (reqmsg.hdr));
sp->state |= DS_PRI_REQUESTED;
sp->last_req_id = sp->req_id;
}
static void
ds_pri_reg_handler(ds_cb_arg_t arg, ds_ver_t *ver, ds_svc_hdl_t hdl)
{
dev_info_t *dip = arg;
ds_pri_state_t *sp;
int instance;
instance = ddi_get_instance(dip);
if ((sp = ddi_get_soft_state(ds_pri_statep, instance)) == NULL)
return;
DS_PRI_DBG("ds_pri_reg_handler: registering handle 0x%lx for version "
"0x%x:0x%x\n", (uint64_t)hdl, ver->major, ver->minor);
mutex_enter(&sp->lock);
ASSERT(sp->ds_pri_handle == DS_INVALID_HDL);
sp->ds_pri_handle = hdl;
ASSERT(!(sp->state & DS_PRI_HAS_SERVICE));
sp->state |= DS_PRI_HAS_SERVICE;
cv_broadcast(&sp->cv);
mutex_exit(&sp->lock);
}
static void
ds_pri_unreg_handler(ds_cb_arg_t arg)
{
dev_info_t *dip = arg;
ds_pri_state_t *sp;
int instance;
instance = ddi_get_instance(dip);
if ((sp = ddi_get_soft_state(ds_pri_statep, instance)) == NULL)
return;
DS_PRI_DBG("ds_pri_unreg_handler: un-registering ds_pri service\n");
mutex_enter(&sp->lock);
sp->ds_pri_handle = DS_INVALID_HDL;
sp->state &= ~DS_PRI_HAS_SERVICE;
mutex_exit(&sp->lock);
}
static void
ds_pri_data_handler(ds_cb_arg_t arg, void *buf, size_t buflen)
{
dev_info_t *dip = arg;
ds_pri_state_t *sp;
int instance;
void *data;
ds_pri_msg_t *msgp;
size_t pri_size;
msgp = (ds_pri_msg_t *)buf;
if (buflen < sizeof (msgp->hdr))
return;
DS_PRI_DBG("ds_pri_data_handler: msg buf len 0x%lx : type 0x%lx, "
"seqn 0x%lx\n", buflen, msgp->hdr.type, msgp->hdr.seq_num);
instance = ddi_get_instance(dip);
if ((sp = ddi_get_soft_state(ds_pri_statep, instance)) == NULL)
return;
mutex_enter(&sp->lock);
ASSERT(sp->state & DS_PRI_HAS_SERVICE);
switch (msgp->hdr.type) {
case DS_PRI_DATA:
break;
case DS_PRI_UPDATE:
request_pri(sp);
goto done;
default:
goto done;
}
if (!(sp->state & DS_PRI_REQUESTED)) {
cmn_err(CE_WARN, "Received DS pri data without request");
goto done;
}
ASSERT(!(sp->state & DS_PRI_HAS_PRI));
ASSERT(sp->ds_pri_len == 0);
ASSERT(sp->ds_pri == NULL);
if (msgp->hdr.seq_num != sp->last_req_id) {
cmn_err(CE_WARN, "Received DS pri data out of sequence with "
"request");
goto done;
}
pri_size = buflen - sizeof (msgp->hdr);
if (pri_size == 0) {
cmn_err(CE_WARN, "Received DS pri data of size 0");
goto done;
}
data = kmem_alloc(pri_size, KM_SLEEP);
sp->ds_pri = data;
sp->ds_pri_len = pri_size;
bcopy(msgp->data, data, sp->ds_pri_len);
sp->state &= ~DS_PRI_REQUESTED;
sp->state |= DS_PRI_HAS_PRI;
sp->gencount++;
cv_broadcast(&sp->cv);
done:;
mutex_exit(&sp->lock);
}
static uint64_t
ds_get_hv_pri(ds_pri_state_t *sp)
{
uint64_t status;
uint64_t pri_size;
uint64_t buf_size;
uint64_t buf_pa;
caddr_t buf_va = NULL;
caddr_t pri_data;
pri_size = 0LL;
status = hv_mach_pri((uint64_t)0, &pri_size);
if (status == H_ENOTSUPPORTED || status == H_ENOACCESS) {
(void) hsvc_unregister(&pboot_hsvc);
hsvc_pboot_available = B_FALSE;
DS_PRI_DBG("ds_get_hv_pri: hv_mach_pri service is not "
"available. errorno: 0x%lx\n", status);
return (0);
} else if (pri_size == 0) {
return (1);
} else {
DS_PRI_DBG("ds_get_hv_pri: hv_mach_pri pri size: 0x%lx\n",
pri_size);
}
if (!ISP2(pri_size))
buf_size = 1 << highbit(pri_size);
DS_PRI_DBG("ds_get_hv_pri: buf_size = 0x%lx\n", buf_size);
buf_va = contig_mem_alloc(buf_size);
if (buf_va == NULL)
return (1);
buf_pa = va_to_pa(buf_va);
DS_PRI_DBG("ds_get_hv_pri: buf_pa 0x%lx\n", buf_pa);
status = hv_mach_pri(buf_pa, &pri_size);
DS_PRI_DBG("ds_get_hv_pri: hv_mach_pri status = 0x%lx\n", status);
if (status == H_EOK) {
pri_data = kmem_alloc(pri_size, KM_SLEEP);
sp->ds_pri = pri_data;
sp->ds_pri_len = pri_size;
bcopy(buf_va, pri_data, sp->ds_pri_len);
sp->state |= DS_PRI_HAS_PRI;
sp->gencount++;
}
contig_mem_free(buf_va, buf_size);
return (status);
}