
Since the grant table is allocated dynamically, a system service always runs the risk of running out of memory at run time when trying to allocate a grant. In order to allow services to mitigate that risk, grants can now be preallocated, typically at system service startup, using the new cpf_prealloc(3) libsys function. The function takes a 'count' parameter that indicates the number of additional grants to preallocate. Thus, the function may be called from multiple submodules within a service, each preallocating their own maximum of grants that it may need at run time. Change-Id: I6904726a722a8c27dfe2efa470e683718f310272
382 lines
9.7 KiB
C
382 lines
9.7 KiB
C
|
|
/* Library functions to maintain internal data copying tables.
|
|
*
|
|
* April 21 2006: Initial version (Ben Gras)
|
|
*
|
|
*/
|
|
|
|
#include <lib.h>
|
|
#include <errno.h>
|
|
#include <minix/sysutil.h>
|
|
#include <assert.h>
|
|
#include <stdlib.h>
|
|
#include <minix/syslib.h>
|
|
#include <minix/safecopies.h>
|
|
#include <minix/com.h>
|
|
#include <string.h>
|
|
|
|
#define ACCESS_CHECK(a) { \
|
|
if((a) & ~(CPF_READ|CPF_WRITE|CPF_TRY)) { \
|
|
errno = EINVAL; \
|
|
return -1; \
|
|
} \
|
|
}
|
|
|
|
#define GID_CHECK(gid) { \
|
|
if(!GRANT_VALID(gid) || GRANT_IDX(gid) >= ngrants || \
|
|
GRANT_SEQ(gid) != grants[GRANT_IDX(gid)].cp_seq) { \
|
|
errno = EINVAL; \
|
|
return -1; \
|
|
} \
|
|
}
|
|
|
|
#define GID_CHECK_USED(gid) { \
|
|
GID_CHECK(gid); \
|
|
if(!(grants[GRANT_IDX(gid)].cp_flags & CPF_USED)) { \
|
|
errno = EINVAL; \
|
|
return -1; \
|
|
} \
|
|
}
|
|
|
|
#define NR_STATIC_GRANTS 3
|
|
static cp_grant_t static_grants[NR_STATIC_GRANTS];
|
|
static cp_grant_t *grants = NULL;
|
|
static int ngrants = 0;
|
|
static int freelist = -1;
|
|
|
|
/*
|
|
* Preallocate more grants that will be free for subsequent use. If a specific
|
|
* number of grants is given (i.e., count > 0), the total number of grants will
|
|
* be increased by that amount. If no number of grants is given (count == 0),
|
|
* double(ish) the size of the table. The latter is used internally. This
|
|
* function may fail, either because the maximum number of slots is reached or
|
|
* because no new memory can be allocated. In that case, nothing will change;
|
|
* the caller must check afterward whether there are newly available grants.
|
|
*/
|
|
void
|
|
cpf_prealloc(unsigned int count)
|
|
{
|
|
cp_grant_t *new_grants;
|
|
int g, new_size;
|
|
|
|
if (!ngrants && count <= NR_STATIC_GRANTS) {
|
|
/* Use statically allocated grants the first time. */
|
|
new_size = NR_STATIC_GRANTS;
|
|
new_grants = static_grants;
|
|
}
|
|
else {
|
|
if (ngrants >= GRANT_MAX_IDX)
|
|
return;
|
|
if (count != 0) {
|
|
if (count > (unsigned)(GRANT_MAX_IDX - ngrants))
|
|
count = (unsigned)(GRANT_MAX_IDX - ngrants);
|
|
new_size = ngrants + (int)count;
|
|
} else
|
|
new_size = (1+ngrants)*2;
|
|
if (new_size >= GRANT_MAX_IDX)
|
|
new_size = GRANT_MAX_IDX;
|
|
assert(new_size > ngrants);
|
|
|
|
/* Allocate a block of new size. */
|
|
if(!(new_grants=malloc(new_size * sizeof(grants[0])))) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Copy old block to new block. */
|
|
if(grants && ngrants > 0)
|
|
memcpy(new_grants, grants, ngrants * sizeof(grants[0]));
|
|
|
|
/*
|
|
* Make sure new slots are marked unused (CPF_USED is clear).
|
|
* Also start with a zero sequence number, for consistency; since the
|
|
* grant table is never shrunk, this introduces no issues by itself.
|
|
* Finally, form a new free list, in ascending order so that the lowest
|
|
* IDs get allocated first. Both the zeroed sequence number and the
|
|
* ascending order are necessary so that the first grant to be
|
|
* allocated has a zero ID (see the live update comment below).
|
|
*/
|
|
for(g = ngrants; g < new_size; g++) {
|
|
new_grants[g].cp_flags = 0;
|
|
new_grants[g].cp_seq = 0;
|
|
new_grants[g].cp_u.cp_free.cp_next =
|
|
(g < new_size - 1) ? (g + 1) : freelist;
|
|
}
|
|
|
|
/* Inform kernel about new size (and possibly new location). */
|
|
if((sys_setgrant(new_grants, new_size))) {
|
|
if(new_grants != static_grants) free(new_grants);
|
|
return; /* Failed - don't grow then. */
|
|
}
|
|
|
|
/* Update internal data. */
|
|
if(grants && ngrants > 0 && grants != static_grants) free(grants);
|
|
freelist = ngrants;
|
|
grants = new_grants;
|
|
ngrants = new_size;
|
|
}
|
|
|
|
static int
|
|
cpf_new_grantslot(void)
|
|
{
|
|
/* Find a new, free grant slot in the grant table, grow it if
|
|
* necessary. If no free slot is found and the grow failed,
|
|
* return -1. Otherwise, return grant slot number.
|
|
*/
|
|
int g;
|
|
|
|
/* Obtain a free slot. */
|
|
if ((g = freelist) == -1) {
|
|
/* Table full - try to make the table larger. */
|
|
cpf_prealloc(0);
|
|
if ((g = freelist) == -1) {
|
|
/* ngrants hasn't increased. */
|
|
errno = ENOSPC;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* Basic sanity checks - if we get this far, g must be a valid,
|
|
* free slot.
|
|
*/
|
|
assert(g >= 0);
|
|
assert(g < ngrants);
|
|
assert(!(grants[g].cp_flags & CPF_USED));
|
|
|
|
/* Take the slot off the free list, and return its slot number. */
|
|
freelist = grants[g].cp_u.cp_free.cp_next;
|
|
|
|
return g;
|
|
}
|
|
|
|
cp_grant_id_t
|
|
cpf_grant_direct(endpoint_t who_to, vir_bytes addr, size_t bytes, int access)
|
|
{
|
|
int g;
|
|
|
|
ACCESS_CHECK(access);
|
|
|
|
/* Get new slot to put new grant in. */
|
|
if((g = cpf_new_grantslot()) < 0)
|
|
return -1;
|
|
|
|
/* Fill in new slot data. */
|
|
grants[g].cp_u.cp_direct.cp_who_to = who_to;
|
|
grants[g].cp_u.cp_direct.cp_start = addr;
|
|
grants[g].cp_u.cp_direct.cp_len = bytes;
|
|
grants[g].cp_faulted = GRANT_INVALID;
|
|
__insn_barrier();
|
|
grants[g].cp_flags = access | CPF_DIRECT | CPF_USED | CPF_VALID;
|
|
|
|
return GRANT_ID(g, grants[g].cp_seq);
|
|
}
|
|
|
|
cp_grant_id_t
|
|
cpf_grant_indirect(endpoint_t who_to, endpoint_t who_from, cp_grant_id_t gr)
|
|
{
|
|
/* Grant process A access into process B. B has granted us access as grant
|
|
* id 'gr'.
|
|
*/
|
|
int g;
|
|
|
|
/* Obtain new slot. */
|
|
if((g = cpf_new_grantslot()) < 0)
|
|
return -1;
|
|
|
|
/* Fill in new slot data. */
|
|
grants[g].cp_u.cp_indirect.cp_who_to = who_to;
|
|
grants[g].cp_u.cp_indirect.cp_who_from = who_from;
|
|
grants[g].cp_u.cp_indirect.cp_grant = gr;
|
|
grants[g].cp_faulted = GRANT_INVALID;
|
|
__insn_barrier();
|
|
grants[g].cp_flags = CPF_USED | CPF_INDIRECT | CPF_VALID;
|
|
|
|
return GRANT_ID(g, grants[g].cp_seq);
|
|
}
|
|
|
|
cp_grant_id_t
|
|
cpf_grant_magic(endpoint_t who_to, endpoint_t who_from,
|
|
vir_bytes addr, size_t bytes, int access)
|
|
{
|
|
/* Grant process A access into process B. Not everyone can do this. */
|
|
int g;
|
|
|
|
ACCESS_CHECK(access);
|
|
|
|
/* Obtain new slot. */
|
|
if((g = cpf_new_grantslot()) < 0)
|
|
return -1;
|
|
|
|
/* Fill in new slot data. */
|
|
grants[g].cp_u.cp_magic.cp_who_to = who_to;
|
|
grants[g].cp_u.cp_magic.cp_who_from = who_from;
|
|
grants[g].cp_u.cp_magic.cp_start = addr;
|
|
grants[g].cp_u.cp_magic.cp_len = bytes;
|
|
grants[g].cp_faulted = GRANT_INVALID;
|
|
__insn_barrier();
|
|
grants[g].cp_flags = CPF_USED | CPF_MAGIC | CPF_VALID | access;
|
|
|
|
return GRANT_ID(g, grants[g].cp_seq);
|
|
}
|
|
|
|
/*
|
|
* Revoke previously granted access, identified by grant ID. Return -1 on
|
|
* error, with errno set as appropriate. Return 0 on success, with one
|
|
* exception: return GRANT_FAULTED (1) if a grant was created with CPF_TRY and
|
|
* during its lifetime, a copy from or to the grant experienced a soft fault.
|
|
*/
|
|
int
|
|
cpf_revoke(cp_grant_id_t grant)
|
|
{
|
|
int r, g;
|
|
|
|
GID_CHECK_USED(grant);
|
|
|
|
g = GRANT_IDX(grant);
|
|
|
|
/*
|
|
* If a safecopy action on a (direct or magic) grant with the CPF_TRY
|
|
* flag failed on a soft fault, the kernel will have set the cp_faulted
|
|
* field to the grant identifier. Here, we test this and return
|
|
* GRANT_FAULTED (1) on a match.
|
|
*/
|
|
r = ((grants[g].cp_flags & CPF_TRY) &&
|
|
grants[g].cp_faulted == grant) ? GRANT_FAULTED : 0;
|
|
|
|
/*
|
|
* Make grant invalid by setting flags to 0, clearing CPF_USED.
|
|
* This invalidates the grant.
|
|
*/
|
|
grants[g].cp_flags = 0;
|
|
__insn_barrier();
|
|
|
|
/*
|
|
* Increase the grant slot's sequence number now, rather than on
|
|
* allocation, because live update relies on the first allocated grant
|
|
* having a zero ID (SEF_STATE_TRANSFER_GID) and thus a zero sequence
|
|
* number.
|
|
*/
|
|
if (grants[g].cp_seq < GRANT_MAX_SEQ - 1)
|
|
grants[g].cp_seq++;
|
|
else
|
|
grants[g].cp_seq = 0;
|
|
|
|
/*
|
|
* Put the grant back on the free list. The list is single-headed, so
|
|
* the last freed grant will be the first to be reused. Especially
|
|
* given the presence of sequence numbers, this is not a problem.
|
|
*/
|
|
grants[g].cp_u.cp_free.cp_next = freelist;
|
|
freelist = g;
|
|
|
|
return r;
|
|
}
|
|
|
|
/*
|
|
* START OF DEPRECATED API
|
|
*
|
|
* The grant preallocation and (re)assignment API below imposes that grant IDs
|
|
* stay the same across reuse, thus disallowing that the grants' sequence
|
|
* numbers be updated as a part of reassignment. As a result, this API does
|
|
* not offer the same protection against accidental reuse of an old grant by a
|
|
* remote party as the regular API does, and is therefore deprecated.
|
|
*/
|
|
int
|
|
cpf_getgrants(cp_grant_id_t *grant_ids, int n)
|
|
{
|
|
int i;
|
|
|
|
for(i = 0; i < n; i++) {
|
|
if((grant_ids[i] = cpf_new_grantslot()) < 0)
|
|
break;
|
|
grants[grant_ids[i]].cp_flags = CPF_USED;
|
|
grants[grant_ids[i]].cp_seq = 0;
|
|
}
|
|
|
|
/* return however many grants were assigned. */
|
|
return i;
|
|
}
|
|
|
|
int
|
|
cpf_setgrant_direct(gid, who, addr, bytes, access)
|
|
cp_grant_id_t gid;
|
|
endpoint_t who;
|
|
vir_bytes addr;
|
|
size_t bytes;
|
|
int access;
|
|
{
|
|
GID_CHECK(gid);
|
|
ACCESS_CHECK(access);
|
|
|
|
/* Fill in new slot data. */
|
|
grants[gid].cp_flags = access | CPF_DIRECT | CPF_USED | CPF_VALID;
|
|
grants[gid].cp_u.cp_direct.cp_who_to = who;
|
|
grants[gid].cp_u.cp_direct.cp_start = addr;
|
|
grants[gid].cp_u.cp_direct.cp_len = bytes;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
cpf_setgrant_indirect(gid, who_to, who_from, his_gid)
|
|
cp_grant_id_t gid;
|
|
endpoint_t who_to, who_from;
|
|
cp_grant_id_t his_gid;
|
|
{
|
|
GID_CHECK(gid);
|
|
|
|
/* Fill in new slot data. */
|
|
grants[gid].cp_flags = CPF_USED | CPF_INDIRECT | CPF_VALID;
|
|
grants[gid].cp_u.cp_indirect.cp_who_to = who_to;
|
|
grants[gid].cp_u.cp_indirect.cp_who_from = who_from;
|
|
grants[gid].cp_u.cp_indirect.cp_grant = his_gid;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
cpf_setgrant_magic(gid, who_to, who_from, addr, bytes, access)
|
|
cp_grant_id_t gid;
|
|
endpoint_t who_to, who_from;
|
|
vir_bytes addr;
|
|
size_t bytes;
|
|
int access;
|
|
{
|
|
GID_CHECK(gid);
|
|
ACCESS_CHECK(access);
|
|
|
|
/* Fill in new slot data. */
|
|
grants[gid].cp_flags = CPF_USED | CPF_MAGIC | CPF_VALID | access;
|
|
grants[gid].cp_u.cp_magic.cp_who_to = who_to;
|
|
grants[gid].cp_u.cp_magic.cp_who_from = who_from;
|
|
grants[gid].cp_u.cp_magic.cp_start = addr;
|
|
grants[gid].cp_u.cp_magic.cp_len = bytes;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
cpf_setgrant_disable(gid)
|
|
cp_grant_id_t gid;
|
|
{
|
|
GID_CHECK(gid);
|
|
|
|
/* Grant is now no longer valid, but still in use. */
|
|
grants[gid].cp_flags = CPF_USED;
|
|
|
|
return 0;
|
|
}
|
|
/*
|
|
* END OF DEPRECATED API
|
|
*/
|
|
|
|
void
|
|
cpf_reload(void)
|
|
{
|
|
/* Inform the kernel about the location of the grant table. This is needed
|
|
* after a fork.
|
|
*/
|
|
if (grants)
|
|
sys_setgrant(grants, ngrants); /* Do we need error checking? */
|
|
}
|