2012-04-09 15:56:20 +02:00
|
|
|
/* VirtualBox driver - by D.C. van Moolenbroek */
|
|
|
|
#include <minix/drivers.h>
|
|
|
|
#include <minix/vboxtype.h>
|
|
|
|
#include <minix/vboxif.h>
|
|
|
|
#include <assert.h>
|
|
|
|
|
|
|
|
#include "vmmdev.h"
|
|
|
|
#include "proto.h"
|
|
|
|
|
|
|
|
#define MAX_CONNS 4 /* maximum number of HGCM connections */
|
|
|
|
#define MAX_REQS 2 /* number of concurrent requests per conn. */
|
|
|
|
#define MAX_PARAMS 8 /* maximum number of parameters per request */
|
|
|
|
|
|
|
|
/* HGCM connection states. */
|
|
|
|
enum {
|
|
|
|
STATE_FREE,
|
|
|
|
STATE_OPENING,
|
|
|
|
STATE_OPEN,
|
|
|
|
STATE_CLOSING
|
|
|
|
};
|
|
|
|
|
|
|
|
/* HGCM connection information. */
|
|
|
|
static struct {
|
|
|
|
int state; /* connection state */
|
|
|
|
endpoint_t endpt; /* caller endpoint */
|
|
|
|
u32_t client_id; /* VMMDev-given client ID */
|
|
|
|
struct {
|
|
|
|
int busy; /* is this request ongoing? */
|
|
|
|
struct VMMDevHGCMHeader *ptr; /* request buffer */
|
|
|
|
phys_bytes addr; /* buffer's physical address */
|
|
|
|
|
|
|
|
int status; /* IPC status of request */
|
|
|
|
long id; /* request ID */
|
|
|
|
|
|
|
|
cp_grant_id_t grant; /* grant for parameters */
|
|
|
|
int count; /* number of parameters */
|
|
|
|
vbox_param_t param[MAX_PARAMS]; /* local copy of parameters */
|
|
|
|
} req[MAX_REQS]; /* concurrent requests */
|
|
|
|
} hgcm_conn[MAX_CONNS];
|
|
|
|
|
|
|
|
/*===========================================================================*
|
|
|
|
* convert_result *
|
|
|
|
*===========================================================================*/
|
|
|
|
static int convert_result(int res)
|
|
|
|
{
|
|
|
|
/* Convert a VirtualBox result code to a POSIX error code.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* HGCM transport error codes. */
|
|
|
|
switch (res) {
|
|
|
|
case VMMDEV_ERR_HGCM_NOT_FOUND: return ESRCH;
|
|
|
|
case VMMDEV_ERR_HGCM_DENIED: return EPERM;
|
|
|
|
case VMMDEV_ERR_HGCM_INVALID_ADDR: return EFAULT;
|
|
|
|
case VMMDEV_ERR_HGCM_ASYNC_EXEC: return EDONTREPLY;
|
|
|
|
case VMMDEV_ERR_HGCM_INTERNAL: return EGENERIC;
|
|
|
|
case VMMDEV_ERR_HGCM_INVALID_ID: return EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Positive codes are success codes. */
|
|
|
|
if (res >= 0)
|
|
|
|
return OK;
|
|
|
|
|
|
|
|
/* Unsupported negative codes are translated to EGENERIC; it is up to
|
|
|
|
* the caller to check the actual VirtualBox result code in that case.
|
|
|
|
*/
|
|
|
|
return convert_err(res);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*===========================================================================*
|
|
|
|
* send_reply *
|
|
|
|
*===========================================================================*/
|
|
|
|
static void send_reply(endpoint_t endpt, int ipc_status, int result, int code,
|
|
|
|
long id)
|
|
|
|
{
|
|
|
|
/* Send a reply to an earlier request. */
|
|
|
|
message m;
|
|
|
|
int r;
|
|
|
|
|
|
|
|
memset(&m, 0, sizeof(m));
|
|
|
|
m.m_type = VBOX_REPLY;
|
|
|
|
m.VBOX_RESULT = result;
|
|
|
|
m.VBOX_CODE = code;
|
|
|
|
m.VBOX_ID = id;
|
|
|
|
|
|
|
|
if (IPC_STATUS_CALL(ipc_status) == SENDREC)
|
|
|
|
r = sendnb(endpt, &m);
|
|
|
|
else
|
|
|
|
r = asynsend3(endpt, &m, AMF_NOREPLY);
|
|
|
|
|
|
|
|
if (r != OK)
|
|
|
|
printf("VBOX: unable to send reply to %d: %d\n", endpt, r);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*===========================================================================*
|
|
|
|
* alloc_req *
|
|
|
|
*===========================================================================*/
|
|
|
|
static int alloc_req(int conn)
|
|
|
|
{
|
|
|
|
/* Allocate a request for the given connection. Allocate memory as
|
|
|
|
* necessary. Do not mark the request as busy, as it may end up not
|
|
|
|
* being used.
|
|
|
|
*/
|
|
|
|
phys_bytes addr;
|
|
|
|
void *ptr;
|
|
|
|
int req;
|
|
|
|
|
|
|
|
for (req = 0; req < MAX_REQS; req++)
|
|
|
|
if (!hgcm_conn[conn].req[req].busy)
|
|
|
|
break;
|
|
|
|
|
|
|
|
if (req == MAX_REQS)
|
|
|
|
return EMFILE;
|
|
|
|
|
|
|
|
if (hgcm_conn[conn].req[req].ptr == NULL) {
|
|
|
|
if ((ptr = alloc_contig(VMMDEV_BUF_SIZE, 0, &addr)) == NULL)
|
|
|
|
return ENOMEM;
|
|
|
|
|
|
|
|
hgcm_conn[conn].req[req].ptr = (struct VMMDevHGCMHeader *) ptr;
|
|
|
|
hgcm_conn[conn].req[req].addr = addr;
|
|
|
|
}
|
|
|
|
|
|
|
|
return req;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*===========================================================================*
|
|
|
|
* free_conn *
|
|
|
|
*===========================================================================*/
|
|
|
|
static void free_conn(int conn)
|
|
|
|
{
|
|
|
|
/* Free the memory for all requests of the given connections, and mark
|
|
|
|
* the connection as free.
|
|
|
|
*/
|
|
|
|
void *ptr;
|
|
|
|
int req;
|
|
|
|
|
|
|
|
for (req = 0; req < MAX_REQS; req++) {
|
|
|
|
if ((ptr = (void *) hgcm_conn[conn].req[req].ptr) != NULL) {
|
|
|
|
assert(!hgcm_conn[conn].req[req].busy);
|
|
|
|
|
|
|
|
free_contig(ptr, VMMDEV_BUF_SIZE);
|
|
|
|
|
|
|
|
hgcm_conn[conn].req[req].ptr = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
hgcm_conn[conn].state = STATE_FREE;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*===========================================================================*
|
|
|
|
* start_req *
|
|
|
|
*===========================================================================*/
|
|
|
|
static int start_req(int conn, int req, int type, size_t size, int ipc_status,
|
|
|
|
long id, int *code)
|
|
|
|
{
|
|
|
|
/* Start a request. */
|
|
|
|
int r, res;
|
|
|
|
|
|
|
|
hgcm_conn[conn].req[req].ptr->flags = 0;
|
|
|
|
hgcm_conn[conn].req[req].ptr->result = VMMDEV_ERR_GENERIC;
|
|
|
|
|
|
|
|
*code = res = vbox_request(&hgcm_conn[conn].req[req].ptr->header,
|
|
|
|
hgcm_conn[conn].req[req].addr, type, size);
|
|
|
|
|
|
|
|
r = convert_result(res);
|
|
|
|
|
|
|
|
if (r != OK && r != EDONTREPLY)
|
|
|
|
return r;
|
|
|
|
|
|
|
|
/* The request may be processed either immediately or asynchronously.
|
|
|
|
* The caller of this function must be able to cope with both
|
|
|
|
* situations. In either case, mark the current request as ongoing.
|
|
|
|
*/
|
|
|
|
hgcm_conn[conn].req[req].busy = TRUE;
|
|
|
|
hgcm_conn[conn].req[req].status = ipc_status;
|
|
|
|
hgcm_conn[conn].req[req].id = id;
|
|
|
|
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*===========================================================================*
|
|
|
|
* cancel_req *
|
|
|
|
*===========================================================================*/
|
|
|
|
static void cancel_req(int conn, int req)
|
|
|
|
{
|
|
|
|
/* Cancel an ongoing request. */
|
|
|
|
|
|
|
|
assert(hgcm_conn[conn].req[req].ptr != NULL);
|
|
|
|
|
|
|
|
/* The cancel request consists only of the HGCM header. The physical
|
|
|
|
* location determines the request to cancel. Note that request
|
|
|
|
* cancellation this is full of race conditions, so we simply ignore
|
|
|
|
* the return value and assumed all went well.
|
|
|
|
*/
|
|
|
|
hgcm_conn[conn].req[req].ptr->flags = 0;
|
|
|
|
hgcm_conn[conn].req[req].ptr->result = VMMDEV_ERR_GENERIC;
|
|
|
|
|
|
|
|
vbox_request(&hgcm_conn[conn].req[req].ptr->header,
|
|
|
|
hgcm_conn[conn].req[req].addr, VMMDEV_REQ_HGCMCANCEL,
|
|
|
|
sizeof(struct VMMDevHGCMCancel));
|
|
|
|
|
|
|
|
hgcm_conn[conn].req[req].busy = FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*===========================================================================*
|
|
|
|
* finish_req *
|
|
|
|
*===========================================================================*/
|
|
|
|
static int finish_req(int conn, int req, int *code)
|
|
|
|
{
|
|
|
|
/* The given request has finished. Take the appropriate action.
|
|
|
|
*/
|
|
|
|
struct VMMDevHGCMConnect *connreq;
|
|
|
|
struct VMMDevHGCMCall *callreq;
|
|
|
|
struct VMMDevHGCMParam *inp;
|
|
|
|
vbox_param_t *outp;
|
|
|
|
int i, count, res, r = OK;
|
|
|
|
|
|
|
|
hgcm_conn[conn].req[req].busy = FALSE;
|
|
|
|
|
|
|
|
*code = res = hgcm_conn[conn].req[req].ptr->result;
|
|
|
|
|
|
|
|
r = convert_result(res);
|
|
|
|
|
|
|
|
/* The request has finished, so it cannot still be in progress. */
|
|
|
|
if (r == EDONTREPLY)
|
|
|
|
r = EGENERIC;
|
|
|
|
|
|
|
|
switch (hgcm_conn[conn].state) {
|
|
|
|
case STATE_FREE:
|
|
|
|
assert(0);
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case STATE_OPENING:
|
|
|
|
if (r == OK) {
|
|
|
|
connreq = (struct VMMDevHGCMConnect *)
|
|
|
|
hgcm_conn[conn].req[req].ptr;
|
|
|
|
hgcm_conn[conn].client_id = connreq->client_id;
|
|
|
|
hgcm_conn[conn].state = STATE_OPEN;
|
|
|
|
|
|
|
|
r = conn;
|
|
|
|
} else {
|
|
|
|
free_conn(conn);
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case STATE_CLOSING:
|
|
|
|
/* Neither we nor the caller can do anything with failures. */
|
|
|
|
if (r != OK)
|
|
|
|
printf("VBOX: disconnection failure #2 (%d)\n", res);
|
|
|
|
|
|
|
|
free_conn(conn);
|
|
|
|
|
|
|
|
r = OK;
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case STATE_OPEN:
|
|
|
|
/* On success, extract and copy back parameters to the caller.
|
|
|
|
*/
|
|
|
|
if (r == OK) {
|
|
|
|
callreq = (struct VMMDevHGCMCall *)
|
|
|
|
hgcm_conn[conn].req[req].ptr;
|
|
|
|
inp = (struct VMMDevHGCMParam *) &callreq[1];
|
|
|
|
outp = &hgcm_conn[conn].req[req].param[0];
|
|
|
|
count = hgcm_conn[conn].req[req].count;
|
|
|
|
|
|
|
|
for (i = 0; i < count; i++) {
|
|
|
|
switch (outp->type) {
|
|
|
|
case VBOX_TYPE_U32:
|
|
|
|
outp->u32 = inp->u32;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case VBOX_TYPE_U64:
|
|
|
|
outp->u64 = inp->u64;
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
inp++;
|
|
|
|
outp++;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (count > 0) {
|
|
|
|
r = sys_safecopyto(hgcm_conn[conn].endpt,
|
|
|
|
hgcm_conn[conn].req[req].grant, 0,
|
|
|
|
(vir_bytes)
|
|
|
|
hgcm_conn[conn].req[req].param,
|
2012-06-16 03:46:15 +02:00
|
|
|
count * sizeof(vbox_param_t));
|
2012-04-09 15:56:20 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*===========================================================================*
|
|
|
|
* check_conn *
|
|
|
|
*===========================================================================*/
|
|
|
|
static void check_conn(int conn)
|
|
|
|
{
|
|
|
|
/* Check all requests for the given connection for completion. */
|
|
|
|
int r, req, code;
|
|
|
|
|
|
|
|
for (req = 0; req < MAX_REQS; req++) {
|
|
|
|
if (!hgcm_conn[conn].req[req].busy) continue;
|
|
|
|
|
|
|
|
if (!(hgcm_conn[conn].req[req].ptr->flags &
|
|
|
|
VMMDEV_HGCM_REQ_DONE))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
r = finish_req(conn, req, &code);
|
|
|
|
|
|
|
|
assert(r != EDONTREPLY);
|
|
|
|
|
|
|
|
send_reply(hgcm_conn[conn].endpt,
|
|
|
|
hgcm_conn[conn].req[req].status, r, code,
|
|
|
|
hgcm_conn[conn].req[req].id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*===========================================================================*
|
|
|
|
* do_open *
|
|
|
|
*===========================================================================*/
|
|
|
|
static int do_open(message *m_ptr, int ipc_status, int *code)
|
|
|
|
{
|
|
|
|
/* Process a connection request. */
|
|
|
|
struct VMMDevHGCMConnect *connreq;
|
|
|
|
int i, r, conn, count;
|
|
|
|
|
|
|
|
if (m_ptr->VBOX_COUNT < 0 || m_ptr->VBOX_COUNT > VMMDEV_HGCM_NAME_SIZE)
|
|
|
|
return EINVAL;
|
|
|
|
|
|
|
|
/* Find a free connection slot. Make sure the sending endpoint is not
|
|
|
|
* already using up half of the connection slots.
|
|
|
|
*/
|
|
|
|
conn = -1;
|
|
|
|
count = 0;
|
|
|
|
for (i = 0; i < MAX_CONNS; i++) {
|
|
|
|
if (conn < 0 && hgcm_conn[i].state == STATE_FREE)
|
|
|
|
conn = i;
|
|
|
|
if (hgcm_conn[i].endpt == m_ptr->m_source)
|
|
|
|
count++;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (count >= MAX(MAX_CONNS / 2, 2))
|
|
|
|
return EMFILE;
|
|
|
|
|
|
|
|
if (conn < 0)
|
|
|
|
return ENFILE;
|
|
|
|
|
|
|
|
/* Initialize the connection and request structures. */
|
|
|
|
hgcm_conn[conn].state = STATE_OPENING;
|
|
|
|
hgcm_conn[conn].endpt = m_ptr->m_source;
|
|
|
|
|
|
|
|
for (i = 0; i < MAX_REQS; i++) {
|
|
|
|
hgcm_conn[conn].req[i].busy = FALSE;
|
|
|
|
hgcm_conn[conn].req[i].ptr = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Set up and start the connection request. */
|
|
|
|
r = alloc_req(conn);
|
|
|
|
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
assert(r == 0);
|
|
|
|
|
|
|
|
connreq = (struct VMMDevHGCMConnect *) hgcm_conn[conn].req[0].ptr;
|
|
|
|
connreq->type = VMMDEV_HGCM_SVCLOC_LOCALHOST_EXISTING;
|
|
|
|
if ((r = sys_safecopyfrom(m_ptr->m_source, m_ptr->VBOX_GRANT, 0,
|
2012-06-16 03:46:15 +02:00
|
|
|
(vir_bytes) connreq->name, m_ptr->VBOX_COUNT)) !=
|
2012-04-09 15:56:20 +02:00
|
|
|
OK) {
|
|
|
|
free_conn(conn);
|
|
|
|
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
connreq->name[VMMDEV_HGCM_NAME_SIZE-1] = 0;
|
|
|
|
|
|
|
|
r = start_req(conn, 0, VMMDEV_REQ_HGCMCONNECT, sizeof(*connreq),
|
|
|
|
ipc_status, m_ptr->VBOX_ID, code);
|
|
|
|
|
|
|
|
if (r != OK && r != EDONTREPLY) {
|
|
|
|
free_conn(conn);
|
|
|
|
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
return (r == OK) ? finish_req(conn, 0, code) : r;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*===========================================================================*
|
|
|
|
* do_close *
|
|
|
|
*===========================================================================*/
|
|
|
|
static int do_close(message *m_ptr, int ipc_status, int *code)
|
|
|
|
{
|
|
|
|
/* Process a disconnection request. */
|
|
|
|
struct VMMDevHGCMDisconnect *discreq;
|
|
|
|
int r, conn, req;
|
|
|
|
|
|
|
|
conn = m_ptr->VBOX_CONN;
|
|
|
|
|
|
|
|
/* Sanity checks. */
|
|
|
|
if (conn < 0 || conn >= MAX_CONNS)
|
|
|
|
return EINVAL;
|
|
|
|
if (hgcm_conn[conn].endpt != m_ptr->m_source ||
|
|
|
|
hgcm_conn[conn].state != STATE_OPEN)
|
|
|
|
return EINVAL;
|
|
|
|
|
|
|
|
/* Cancel any ongoing requests. */
|
|
|
|
for (req = 0; req < MAX_REQS; req++)
|
|
|
|
if (hgcm_conn[conn].req[req].busy)
|
|
|
|
cancel_req(conn, req);
|
|
|
|
|
|
|
|
assert(hgcm_conn[conn].req[0].ptr != NULL);
|
|
|
|
|
|
|
|
discreq = (struct VMMDevHGCMDisconnect *) hgcm_conn[conn].req[0].ptr;
|
|
|
|
discreq->client_id = hgcm_conn[conn].client_id;
|
|
|
|
|
|
|
|
r = start_req(conn, 0, VMMDEV_REQ_HGCMDISCONNECT, sizeof(*discreq),
|
|
|
|
ipc_status, m_ptr->VBOX_ID, code);
|
|
|
|
|
|
|
|
if (r != OK && r != EDONTREPLY) {
|
|
|
|
/* Neither we nor the caller can do anything with failures. */
|
|
|
|
printf("VBOX: disconnection failure #1 (%d)\n", r);
|
|
|
|
|
|
|
|
free_conn(conn);
|
|
|
|
|
|
|
|
return OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
hgcm_conn[conn].state = STATE_CLOSING;
|
|
|
|
|
|
|
|
return (r == OK) ? finish_req(conn, 0, code) : r;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*===========================================================================*
|
|
|
|
* store_pages *
|
|
|
|
*===========================================================================*/
|
|
|
|
static int store_pages(int conn, int req, vbox_param_t *inp, size_t *offp)
|
|
|
|
{
|
|
|
|
/* Create a page list of physical pages that make up the provided
|
|
|
|
* buffer area.
|
|
|
|
*/
|
|
|
|
struct vumap_vir vvec;
|
|
|
|
struct vumap_phys pvec[MAPVEC_NR];
|
|
|
|
struct VMMDevHGCMPageList *pagelist;
|
|
|
|
size_t offset, size, skip;
|
|
|
|
int i, j, r, first, access, count, pages;
|
|
|
|
|
|
|
|
/* Empty strings are allowed. */
|
|
|
|
if (inp->ptr.size == 0)
|
|
|
|
return OK;
|
|
|
|
|
|
|
|
pagelist = (struct VMMDevHGCMPageList *)
|
|
|
|
(((u8_t *) hgcm_conn[conn].req[req].ptr) + *offp);
|
|
|
|
|
|
|
|
pagelist->flags = 0;
|
|
|
|
if (inp->ptr.dir & VBOX_DIR_IN)
|
|
|
|
pagelist->flags |= VMMDEV_HGCM_FLAG_FROM_HOST;
|
|
|
|
if (inp->ptr.dir & VBOX_DIR_OUT)
|
|
|
|
pagelist->flags |= VMMDEV_HGCM_FLAG_TO_HOST;
|
|
|
|
pagelist->count = 0;
|
|
|
|
|
|
|
|
/* Make sure there is room for the header (but no actual pages yet). */
|
|
|
|
*offp += sizeof(*pagelist) - sizeof(pagelist->addr[0]);
|
|
|
|
if (*offp > VMMDEV_BUF_SIZE)
|
|
|
|
return ENOMEM;
|
|
|
|
|
|
|
|
access = 0;
|
|
|
|
if (inp->ptr.dir & VBOX_DIR_IN) access |= VUA_WRITE;
|
|
|
|
if (inp->ptr.dir & VBOX_DIR_OUT) access |= VUA_READ;
|
|
|
|
|
|
|
|
offset = 0;
|
|
|
|
first = TRUE;
|
|
|
|
do {
|
|
|
|
/* If the caller gives us a huge buffer, we might need multiple
|
|
|
|
* calls to sys_vumap(). Note that the caller currently has no
|
|
|
|
* reliable way to know whether such a buffer will fit in our
|
|
|
|
* request page. In the future, we may dynamically reallocate
|
|
|
|
* the request area to make more room as necessary; for now we
|
|
|
|
* just return an ENOMEM error in such cases.
|
|
|
|
*/
|
|
|
|
vvec.vv_grant = inp->ptr.grant;
|
|
|
|
vvec.vv_size = inp->ptr.off + inp->ptr.size;
|
|
|
|
count = MAPVEC_NR;
|
|
|
|
if ((r = sys_vumap(hgcm_conn[conn].endpt, &vvec, 1,
|
|
|
|
inp->ptr.off + offset, access, pvec,
|
|
|
|
&count)) != OK)
|
|
|
|
return r;
|
|
|
|
|
|
|
|
/* First get the number of bytes processed, before (possibly)
|
|
|
|
* adjusting the size of the first element.
|
|
|
|
*/
|
|
|
|
for (i = size = 0; i < count; i++)
|
|
|
|
size += pvec[i].vp_size;
|
|
|
|
|
|
|
|
/* VirtualBox wants aligned page addresses only, and an offset
|
|
|
|
* into the first page. All other pages except the last are
|
|
|
|
* full pages, and the last page is cut off using the size.
|
|
|
|
*/
|
|
|
|
skip = 0;
|
|
|
|
if (first) {
|
|
|
|
skip = pvec[0].vp_addr & (PAGE_SIZE - 1);
|
|
|
|
pvec[0].vp_addr -= skip;
|
|
|
|
pvec[0].vp_size += skip;
|
|
|
|
pagelist->offset = skip;
|
|
|
|
first = FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* How many pages were mapped? */
|
|
|
|
pages = (skip + size + PAGE_SIZE - 1) / PAGE_SIZE;
|
|
|
|
|
|
|
|
/* Make sure there is room to store this many extra pages. */
|
|
|
|
*offp += sizeof(pagelist->addr[0]) * pages;
|
|
|
|
if (*offp > VMMDEV_BUF_SIZE)
|
|
|
|
return ENOMEM;
|
|
|
|
|
|
|
|
/* Actually store the pages in the page list. */
|
|
|
|
for (i = j = 0; i < pages; i++) {
|
|
|
|
assert(!(pvec[j].vp_addr & (PAGE_SIZE - 1)));
|
|
|
|
|
|
|
|
pagelist->addr[pagelist->count++] =
|
2013-06-17 10:31:12 +02:00
|
|
|
((u64_t)(pvec[j].vp_addr));
|
2012-04-09 15:56:20 +02:00
|
|
|
|
|
|
|
if (pvec[j].vp_size > PAGE_SIZE) {
|
|
|
|
pvec[j].vp_addr += PAGE_SIZE;
|
|
|
|
pvec[j].vp_size -= PAGE_SIZE;
|
|
|
|
}
|
|
|
|
else j++;
|
|
|
|
}
|
|
|
|
assert(j == count);
|
|
|
|
|
|
|
|
offset += size;
|
|
|
|
} while (offset < inp->ptr.size);
|
|
|
|
|
|
|
|
assert(offset == inp->ptr.size);
|
|
|
|
|
|
|
|
return OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*===========================================================================*
|
|
|
|
* do_call *
|
|
|
|
*===========================================================================*/
|
|
|
|
static int do_call(message *m_ptr, int ipc_status, int *code)
|
|
|
|
{
|
|
|
|
/* Perform a HGCM call. */
|
|
|
|
vbox_param_t *inp;
|
|
|
|
struct VMMDevHGCMParam *outp;
|
|
|
|
struct VMMDevHGCMCall *callreq;
|
|
|
|
size_t size;
|
|
|
|
int i, r, conn, req, count;
|
|
|
|
|
|
|
|
conn = m_ptr->VBOX_CONN;
|
|
|
|
count = m_ptr->VBOX_COUNT;
|
|
|
|
|
|
|
|
/* Sanity checks. */
|
|
|
|
if (conn < 0 || conn >= MAX_CONNS)
|
|
|
|
return EINVAL;
|
|
|
|
if (hgcm_conn[conn].endpt != m_ptr->m_source ||
|
|
|
|
hgcm_conn[conn].state != STATE_OPEN)
|
|
|
|
return EINVAL;
|
|
|
|
|
|
|
|
/* Allocate a request, and copy in the parameters. */
|
|
|
|
req = alloc_req(conn);
|
|
|
|
|
|
|
|
if (req < 0)
|
|
|
|
return req;
|
|
|
|
|
|
|
|
hgcm_conn[conn].req[req].grant = m_ptr->VBOX_GRANT;
|
|
|
|
hgcm_conn[conn].req[req].count = count;
|
|
|
|
|
|
|
|
if (count > 0) {
|
|
|
|
if ((r = sys_safecopyfrom(m_ptr->m_source, m_ptr->VBOX_GRANT,
|
|
|
|
0, (vir_bytes) hgcm_conn[conn].req[req].param,
|
2012-06-16 03:46:15 +02:00
|
|
|
count * sizeof(vbox_param_t))) != OK)
|
2012-04-09 15:56:20 +02:00
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Set up the basic request. */
|
|
|
|
callreq = (struct VMMDevHGCMCall *) hgcm_conn[conn].req[req].ptr;
|
|
|
|
callreq->client_id = hgcm_conn[conn].client_id;
|
|
|
|
callreq->function = m_ptr->VBOX_FUNCTION;
|
|
|
|
callreq->count = count;
|
|
|
|
|
|
|
|
/* Rewrite and convert the parameters. */
|
|
|
|
inp = &hgcm_conn[conn].req[req].param[0];
|
|
|
|
outp = (struct VMMDevHGCMParam *) &callreq[1];
|
|
|
|
|
|
|
|
size = sizeof(*callreq) + sizeof(*outp) * count;
|
|
|
|
assert(size < VMMDEV_BUF_SIZE);
|
|
|
|
|
|
|
|
for (i = 0; i < count; i++) {
|
|
|
|
switch (inp->type) {
|
|
|
|
case VBOX_TYPE_U32:
|
|
|
|
outp->type = VMMDEV_HGCM_PARAM_U32;
|
|
|
|
outp->u32 = inp->u32;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case VBOX_TYPE_U64:
|
|
|
|
outp->type = VMMDEV_HGCM_PARAM_U64;
|
|
|
|
outp->u64 = inp->u64;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case VBOX_TYPE_PTR:
|
|
|
|
outp->type = VMMDEV_HGCM_PARAM_PAGELIST;
|
|
|
|
outp->pagelist.offset = size;
|
|
|
|
outp->pagelist.size = inp->ptr.size;
|
|
|
|
|
|
|
|
if ((r = store_pages(conn, req, inp, &size)) != OK)
|
|
|
|
return r;
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
return EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
inp++;
|
|
|
|
outp++;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Start the request. */
|
|
|
|
r = start_req(conn, req, VMMDEV_REQ_HGCMCALL, size, ipc_status,
|
|
|
|
m_ptr->VBOX_ID, code);
|
|
|
|
|
|
|
|
if (r != OK && r != EDONTREPLY)
|
|
|
|
return r;
|
|
|
|
|
|
|
|
return (r == OK) ? finish_req(conn, req, code) : r;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*===========================================================================*
|
|
|
|
* do_cancel *
|
|
|
|
*===========================================================================*/
|
|
|
|
static int do_cancel(message *m_ptr, int ipc_status)
|
|
|
|
{
|
|
|
|
/* Cancel an ongoing call. */
|
|
|
|
int conn, req;
|
|
|
|
|
|
|
|
conn = m_ptr->VBOX_CONN;
|
|
|
|
|
|
|
|
/* Sanity checks. Note that connection and disconnection requests
|
|
|
|
* cannot be cancelled.
|
|
|
|
*/
|
|
|
|
if (conn < 0 || conn >= MAX_CONNS)
|
|
|
|
return EINVAL;
|
|
|
|
if (hgcm_conn[conn].endpt != m_ptr->m_source ||
|
|
|
|
hgcm_conn[conn].state != STATE_OPEN)
|
|
|
|
return EINVAL;
|
|
|
|
|
|
|
|
/* Find the request. */
|
|
|
|
for (req = 0; req < MAX_REQS; req++) {
|
|
|
|
if (hgcm_conn[conn].req[req].busy &&
|
|
|
|
hgcm_conn[conn].req[req].id == m_ptr->VBOX_ID)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If no such request was ongoing, then our behavior depends on the
|
|
|
|
* way the request was made: we do not want to send two asynchronous
|
|
|
|
* replies for one request, but if the caller used SENDREC, we have to
|
|
|
|
* reply with something or the caller would deadlock.
|
|
|
|
*/
|
|
|
|
if (req == MAX_REQS) {
|
|
|
|
if (IPC_STATUS_CALL(ipc_status) == SENDREC)
|
|
|
|
return EINVAL;
|
|
|
|
else
|
|
|
|
return EDONTREPLY;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Actually cancel the request, and send a reply. */
|
|
|
|
cancel_req(conn, req);
|
|
|
|
|
|
|
|
return EINTR;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*===========================================================================*
|
|
|
|
* hgcm_message *
|
|
|
|
*===========================================================================*/
|
|
|
|
void hgcm_message(message *m_ptr, int ipc_status)
|
|
|
|
{
|
|
|
|
/* Process a request message. */
|
|
|
|
int r, code = VMMDEV_ERR_GENERIC;
|
|
|
|
|
|
|
|
switch (m_ptr->m_type) {
|
|
|
|
case VBOX_OPEN: r = do_open(m_ptr, ipc_status, &code); break;
|
|
|
|
case VBOX_CLOSE: r = do_close(m_ptr, ipc_status, &code); break;
|
|
|
|
case VBOX_CALL: r = do_call(m_ptr, ipc_status, &code); break;
|
|
|
|
case VBOX_CANCEL: r = do_cancel(m_ptr, ipc_status); break;
|
|
|
|
default: r = ENOSYS; break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (r != EDONTREPLY)
|
|
|
|
send_reply(m_ptr->m_source, ipc_status, r, code,
|
|
|
|
m_ptr->VBOX_ID);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*===========================================================================*
|
|
|
|
* hgcm_intr *
|
|
|
|
*===========================================================================*/
|
|
|
|
void hgcm_intr(void)
|
|
|
|
{
|
|
|
|
/* We received an HGCM event. Check ongoing requests for completion. */
|
|
|
|
int conn;
|
|
|
|
|
|
|
|
for (conn = 0; conn < MAX_CONNS; conn++)
|
|
|
|
if (hgcm_conn[conn].state != STATE_FREE)
|
|
|
|
check_conn(conn);
|
|
|
|
}
|