/*
* 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 (c) 2010, Oracle and/or its affiliates. All rights reserved.
*/
/*
* User Objects.
*
* User objects are used to manage and protect resources that
* have been created for a user context. Each user object
* maintains a reference count and a read/write mutex to
* provide the appropriate access to the object depending
* on the operation at hand.
*
* For example when initializing or creating a PD user object,
* the active context would hold a write lock, but to simply
* reference the PD object as in a CQ create operation, a
* read lock is only required.
*
* Each user object also maintains a "live" flag. If this flag
* is not set, then lookups on this user object will fail
* even if it still resides in the associated user object
* management table. This specifically handles the case
* where a get operation blocks and does not acquire the lock
* until after the object has been destroyed (but not yet
* released). Destroy operations set the "live" flag to 0
* prior to dropping their write lock on the user object.
* This allows the reader to realize when it receives the
* lock that the object has been destroyed so it can then
* release it's reference to the user object, and allow it to
* be freed (the storage will not be freed until the last reference
* is released).
*/
#include <sys/debug.h>
#include <sys/kmem.h>
#include <sys/sunddi.h>
#include <sys/ib/clients/of/sol_ofs/sol_ofs_common.h>
extern char *sol_ofs_dbg_str;
static sol_ofs_uobj_t *ofs_uobj_find(sol_ofs_uobj_table_t *,
uint_t, int);
/*
* Function:
* sol_ofs_uobj_tbl_init
* Input:
* uo_tbl - A pointer to the user object resource management table
* to initialize.
* Output:
* None
* Returns:
* None
* Description:
* Initializes the specified user object resource managment table.
*/
void
sol_ofs_uobj_tbl_init(sol_ofs_uobj_table_t *uo_tbl, size_t uobj_sz)
{
ASSERT(uo_tbl != NULL);
rw_init(&uo_tbl->uobj_tbl_lock, NULL, RW_DRIVER, NULL);
uo_tbl->uobj_tbl_used_blks = 0;
uo_tbl->uobj_tbl_num_blks = 0;
uo_tbl->uobj_tbl_uo_cnt = 0;
uo_tbl->uobj_tbl_uo_sz = uobj_sz;
uo_tbl->uobj_tbl_uo_root = NULL;
}
/*
* Function:
* sol_ofs_uobj_tbl_fini
* Input:
* uo_tbl - A pointer to the user object resource management table
* to be released.
* Output:
* None
* Returns:
* None
* Description:
* Releases any resources held by the specified user object resource
* managment table. The table is no longer valid upon return. NOTE:
* the table should be empty when this routine is called, so this
* really is more of just a sanity check.
*/
void
sol_ofs_uobj_tbl_fini(sol_ofs_uobj_table_t *uo_tbl)
{
int i, j;
uint32_t size;
sol_ofs_uobj_blk_t *blk;
ASSERT(uo_tbl != NULL);
rw_enter(&uo_tbl->uobj_tbl_lock, RW_WRITER);
if (uo_tbl->uobj_tbl_uo_cnt > 0) {
SOL_OFS_DPRINTF_L2(sol_ofs_dbg_str,
"UOBJ TBL FINI: object count not zero (cnt=%d)",
uo_tbl->uobj_tbl_uo_cnt);
}
/*
* Go through the roots looking for blocks to free. Warn if any
* our found (there shouldn't be any).
*/
for (i = 0; i < uo_tbl->uobj_tbl_used_blks; i++) {
blk = uo_tbl->uobj_tbl_uo_root[i];
if (!blk) {
continue;
}
for (j = 0; j < SOL_OFS_UO_BLKSZ; j++) {
if (blk->ofs_uoblk_blks[j]) {
/*
* This is an error, we may want to free
* ultimately sol_ofs_uobj_free
* (blk->ofs_uoblk_blks[j]);
*/
SOL_OFS_DPRINTF_L2(sol_ofs_dbg_str,
"UOBJ TBL FINI: blk %p, slot %d non null",
blk, j);
}
}
kmem_free(blk, sizeof (*blk));
}
if (uo_tbl->uobj_tbl_uo_root) {
size = uo_tbl->uobj_tbl_num_blks *
sizeof (sol_ofs_uobj_blk_t *);
kmem_free(uo_tbl->uobj_tbl_uo_root, size);
}
rw_exit(&uo_tbl->uobj_tbl_lock);
rw_destroy(&uo_tbl->uobj_tbl_lock);
}
/*
* Function:
* uverbs_uob_init
* Input:
* uobj - Pointer to the user object to initialize.
* user_handle - A user space handle to associates with the object.
* Generally used to identify object in asynchronous
* notifications.
* uob_type - The type of user object.
* Ouput:
* uobj - Initialized user object.
* Returns:
* None
* Description:
* Initialize a new user object. The object will have one reference
* placed on it.
*/
void
sol_ofs_uobj_init(sol_ofs_uobj_t *uobj,
uint64_t user_handle, sol_ofs_uobj_type_t uobj_type)
{
uobj->uo_user_handle = user_handle;
uobj->uo_refcnt = 1;
uobj->uo_type = uobj_type;
uobj->uo_id = -1;
uobj->uo_live = 0;
rw_init(&uobj->uo_lock, NULL, RW_DRIVER, NULL);
mutex_init(&uobj->uo_reflock, NULL, MUTEX_DRIVER, NULL);
}
/*
* Function:
* ofs_uobj_fini
* Input:
* uobj - Pointer to the user object to be cleaned up.
* Ouput:
* None
* Returns:
* None
* Description:
* Performs user object cleanup prior to releasing memory.
*/
static void
ofs_uobj_fini(sol_ofs_uobj_t *uobj)
{
rw_destroy(&uobj->uo_lock);
mutex_destroy(&uobj->uo_reflock);
}
/*
* Function:
* sol_ofs_uobj_ref
* Input:
* uobj - Pointer to the user object
* Ouput:
* None
* Returns:
* None
* Description:
* Place a reference on the specified user object.
*/
void
sol_ofs_uobj_ref(sol_ofs_uobj_t *uobj)
{
mutex_enter(&uobj->uo_reflock);
uobj->uo_refcnt++;
ASSERT(uobj->uo_refcnt != 0);
mutex_exit(&uobj->uo_reflock);
}
/*
* Function:
* sol_ofs_uobj_deref
* Input:
* uobj - Pointer to the user object
* free_func - Pointer to release function, called if the
* last reference is removed for the user object.
* Ouput:
* None
* Returns:
* None
* Description:
* Remove a reference to a user object. If a free function
* was specified and the last reference is released, then the
* free function is invoked to release the user object.
*/
void
sol_ofs_uobj_deref(sol_ofs_uobj_t *uobj,
void (*free_func)(sol_ofs_uobj_t *uobj))
{
SOL_OFS_DPRINTF_L5(sol_ofs_dbg_str, "UOBJ_DEREF: uobj = %p, "
"refcnt=%d", uobj, uobj->uo_refcnt);
mutex_enter(&uobj->uo_reflock);
ASSERT(uobj->uo_refcnt != 0);
uobj->uo_refcnt--;
if (uobj->uo_refcnt == 0) {
mutex_exit(&uobj->uo_reflock);
if (free_func)
free_func(uobj);
} else {
mutex_exit(&uobj->uo_reflock);
}
}
/*
* Function:
* sol_ofs_uobj_add
* Input:
* uo_tbl - A pointer to the user object resource management table
* to which the object should be added.
* uobj - A pointer ot the user object to be added; a reference
* should exist on this object prior to addition, and the
* object should be removed prior to all references being
* removed.
* Output:
* uobj - The user object "uo_id" is updated and should be
* used in subsequent lookup operations.
* Returns:
* DDI_SUCCESS on success, else error code.
* Description:
* Add a user object to the specified user object resource management
* table.
*
*/
int
sol_ofs_uobj_add(sol_ofs_uobj_table_t *uo_tbl, sol_ofs_uobj_t *uobj)
{
int i, j, empty = -1;
sol_ofs_uobj_blk_t *blk;
rw_enter(&uo_tbl->uobj_tbl_lock, RW_WRITER);
/*
* Try to find an empty slot for the new user object.
*/
for (i = 0; i < uo_tbl->uobj_tbl_used_blks; i++) {
blk = uo_tbl->uobj_tbl_uo_root[i];
if (blk != NULL && blk->ofs_uo_blk_avail > 0) {
SOL_OFS_DPRINTF_L5(sol_ofs_dbg_str,
"UOBJ ADD: table:%p, available blks:%d",
uo_tbl, blk->ofs_uo_blk_avail);
for (j = 0; j < SOL_OFS_UO_BLKSZ; j++) {
if (blk->ofs_uoblk_blks[j] == NULL) {
blk->ofs_uoblk_blks[j] = uobj;
uobj->uo_id = j + (i *
SOL_OFS_UO_BLKSZ);
uobj->uo_uobj_sz =
uo_tbl->uobj_tbl_uo_sz;
blk->ofs_uo_blk_avail--;
uo_tbl->uobj_tbl_uo_cnt++;
goto obj_added;
}
}
} else if (blk == NULL && empty < 0) {
/*
* Remember the first empty blk we came across.
*/
empty = i;
}
}
/*
* No entries were available, we must allocate a new block. If we did
* not find a empty block available, then we must allocate/reallocate
* the root array (copying any existing blk pointers to it).
*/
if (empty < 0) {
if (uo_tbl->uobj_tbl_used_blks == uo_tbl->uobj_tbl_num_blks) {
sol_ofs_uobj_blk_t **p;
uint_t newsz;
newsz = uo_tbl->uobj_tbl_num_blks + SOL_OFS_UO_BLKSZ;
SOL_OFS_DPRINTF_L5(sol_ofs_dbg_str,
"UOBJ ADD: Increasing uobj table size to %d",
newsz);
p = kmem_zalloc(newsz * sizeof (*p), KM_NOSLEEP);
if (!p) {
SOL_OFS_DPRINTF_L2(sol_ofs_dbg_str,
"UOBJ ADD: Mem alloc fail\n");
rw_exit(&uo_tbl->uobj_tbl_lock);
return (1);
}
if (uo_tbl->uobj_tbl_uo_root) {
uint_t oldsz;
oldsz = (uint_t)uo_tbl->uobj_tbl_num_blks *
(int)(sizeof (*p));
bcopy(uo_tbl->uobj_tbl_uo_root, p, oldsz);
kmem_free(uo_tbl->uobj_tbl_uo_root, oldsz);
}
uo_tbl->uobj_tbl_uo_root = p;
uo_tbl->uobj_tbl_num_blks = newsz;
}
empty = uo_tbl->uobj_tbl_used_blks;
uo_tbl->uobj_tbl_used_blks++;
}
/*
* There are enough free block pointers in the root, allocate
* a new block.
*/
blk = kmem_zalloc(sizeof (*blk), KM_NOSLEEP);
if (!blk) {
SOL_OFS_DPRINTF_L2(sol_ofs_dbg_str,
"UOBJ ADD: Mem alloc fail\n");
rw_exit(&uo_tbl->uobj_tbl_lock);
return (1);
}
ASSERT(uo_tbl->uobj_tbl_uo_root[empty] == NULL);
uo_tbl->uobj_tbl_uo_root[empty] = blk;
blk->ofs_uo_blk_avail = SOL_OFS_UO_BLKSZ - 1;
/*
* Use the first slot in this new block to add the new user object.
*/
uobj->uo_id = empty * SOL_OFS_UO_BLKSZ;
blk->ofs_uoblk_blks[0] = uobj;
uobj->uo_uobj_sz = uo_tbl->uobj_tbl_uo_sz;
uo_tbl->uobj_tbl_uo_cnt++;
obj_added:
rw_exit(&uo_tbl->uobj_tbl_lock);
return (0);
}
/*
* Function:
* sol_ofs_uobj_remove
* Input:
* uo_tbl - A pointer to the user object resource management table
* from which the object should be removed.
* uobj - A pointer ot the user object to be removed.
* Output:
* None
* Returns:
* A pointer to the user object that was removed on success, otherwise
* NULL.
* Description:
* Remove a user object from the specified user resource management
* table.
*
* The uobj uo_lock must be held as a writer before calling this.
*/
sol_ofs_uobj_t *
sol_ofs_uobj_remove(sol_ofs_uobj_table_t *uo_tbl, sol_ofs_uobj_t *uobj)
{
uint_t i, j;
sol_ofs_uobj_blk_t *blk;
sol_ofs_uobj_t *p;
ASSERT(uo_tbl != NULL);
ASSERT(uobj != NULL);
p = NULL;
rw_enter(&uo_tbl->uobj_tbl_lock, RW_WRITER);
if (!uobj->uo_live) {
SOL_OFS_DPRINTF_L2(sol_ofs_dbg_str,
"UOBJ REMOVE: object 0x%P, already removed", (void *)uobj);
goto remove_done;
}
if ((uo_tbl->uobj_tbl_uo_cnt == 0) || !(uo_tbl->uobj_tbl_uo_root)) {
/*
* The table is empty, just return not found
* Don't panic, userland app could have double free'd
* let them deal with it.
*/
SOL_OFS_DPRINTF_L2(sol_ofs_dbg_str,
"UOBJ REMOVE: table 0x%P empty", (void *)uo_tbl);
goto remove_done;
}
i = uobj->uo_id / SOL_OFS_UO_BLKSZ;
j = uobj->uo_id % SOL_OFS_UO_BLKSZ;
if (i >= uo_tbl->uobj_tbl_used_blks) {
SOL_OFS_DPRINTF_L2(sol_ofs_dbg_str,
"UOBJ REMOVE: object id %d exceeds table size",
uobj->uo_id);
goto remove_done;
}
ASSERT(i < uo_tbl->uobj_tbl_num_blks);
blk = uo_tbl->uobj_tbl_uo_root[i];
if (blk == NULL) {
SOL_OFS_DPRINTF_L2(sol_ofs_dbg_str,
"UOBJ REMOVE: object id %d points to invalid root",
uobj->uo_id);
goto remove_done;
}
if (blk->ofs_uoblk_blks[j] == NULL) {
SOL_OFS_DPRINTF_L2(sol_ofs_dbg_str,
"UOBJ REMOVE: object id %d points to invalid block",
uobj->uo_id);
goto remove_done;
}
/*
* Mark as dead
*/
uobj->uo_live = 0;
p = blk->ofs_uoblk_blks[j];
blk->ofs_uoblk_blks[j] = NULL;
blk->ofs_uo_blk_avail++;
if (blk->ofs_uo_blk_avail == SOL_OFS_UO_BLKSZ) {
kmem_free(blk, sizeof (*blk));
uo_tbl->uobj_tbl_uo_root[i] = NULL;
}
uo_tbl->uobj_tbl_uo_cnt--;
remove_done:
rw_exit(&uo_tbl->uobj_tbl_lock);
return (p);
}
/*
* Function:
* ofs_uobj_find
* Input:
* uo_tbl - A pointer to the user object resource management table
* to be used for the lookup.
* uo_id - The user object ID to lookup. This ID was set when
* the object was added to the resource management table.
* add_ref - A non zero value indicates that the user objects reference
* count should be updated to reflect and additional
* reference before it is returned.
* Output:
* None
* Returns:
* A pointer to the user object associated with the uo_id if found,
* otherwise NULL.
* Description:
* Lookup and return a user object from the specified user resource
* management table.
*/
static sol_ofs_uobj_t *
ofs_uobj_find(sol_ofs_uobj_table_t *uo_tbl, uint32_t uo_id, int add_ref)
{
uint32_t i, j;
sol_ofs_uobj_blk_t *blk;
sol_ofs_uobj_t *uobj;
ASSERT(uo_tbl != NULL);
uobj = NULL;
rw_enter(&uo_tbl->uobj_tbl_lock, RW_READER);
if ((uo_tbl->uobj_tbl_uo_cnt == 0) || !(uo_tbl->uobj_tbl_uo_root)) {
/*
* The table is empty, just return not found
* Don't panic, userland app could have double free'd
* let them deal with it.
*/
SOL_OFS_DPRINTF_L2(sol_ofs_dbg_str,
"UOBJ FIND: id %d in tbl 0x%P - tbl empty", uo_id,
(void *)uo_tbl);
goto find_done;
}
i = uo_id / SOL_OFS_UO_BLKSZ;
j = uo_id % SOL_OFS_UO_BLKSZ;
if (i >= uo_tbl->uobj_tbl_used_blks) {
SOL_OFS_DPRINTF_L2(sol_ofs_dbg_str,
"UOBJ FIND: Index not valid, %d", uo_id);
goto find_done;
}
/*
* Get the user object, and if valid perform a get (ref++).
* The caller issuing the find, must release the reference
* when done.
*/
blk = uo_tbl->uobj_tbl_uo_root[i];
if (blk != NULL) {
ASSERT(i < uo_tbl->uobj_tbl_num_blks);
uobj = blk->ofs_uoblk_blks[j];
if (uobj == NULL) {
SOL_OFS_DPRINTF_L2(sol_ofs_dbg_str,
"UOBJ FIND: Index %d not found, blk = %p",
uo_id, blk->ofs_uoblk_blks[j]);
} else if (add_ref) {
sol_ofs_uobj_ref(uobj);
}
} else {
SOL_OFS_DPRINTF_L2(sol_ofs_dbg_str,
"UOBJ FIND: Uobject not found, %d", uo_id);
goto find_done;
}
find_done:
rw_exit(&uo_tbl->uobj_tbl_lock);
return (uobj);
}
/*
* Function:
* sol_ofs_uobj_get_read
* Input:
* tbl - Pointer to the user object managment table to
* be used in the lookup.
* uo_id - The ID to object mapping, assigned to the user
* object at addition to the table.
* Ouput:
* None
* Returns:
* A pointer to the user object associated with uo_id or NULL
* if the entry does not exist.
* Description:
* Lookup a user object and place a reference on it. Acquires
* the object with a READ lock. The reference and lock should
* be released using the sol_ofs_uobj_put() call.
*/
sol_ofs_uobj_t *
sol_ofs_uobj_get_read(sol_ofs_uobj_table_t *tbl, uint32_t uo_id)
{
sol_ofs_uobj_t *uobj;
uobj = ofs_uobj_find(tbl, uo_id, 1);
if (!uobj)
return (NULL);
rw_enter(&uobj->uo_lock, RW_READER);
/*
* If object was destroyed before we got the lock, just release
* our reference and indicate we didn't find the object.
*/
if (!uobj->uo_live) {
sol_ofs_uobj_put(uobj);
return (NULL);
}
return (uobj);
}
/*
* Function:
* sol_ofs_uobj_get_write
* Input:
* tbl - Pointer to the user object managment table to
* be used in the lookup.
* uo_id - The ID to object mapping, assigned to the user
* object at addition to the table.
* Ouput:
* None
* Returns:
* A pointer to the user object associated with uo_id or NULL
* if the entry does not exist.
* Description:
* Lookup a user object and place a reference on it. Acquires
* the object with a WRITE lock. The reference and lock should
* be released using the sol_ofs_uobj_put() call.
*/
sol_ofs_uobj_t *
sol_ofs_uobj_get_write(sol_ofs_uobj_table_t *tbl, uint32_t uo_id)
{
sol_ofs_uobj_t *uobj;
uobj = ofs_uobj_find(tbl, uo_id, 1);
if (!uobj)
return (NULL);
rw_enter(&uobj->uo_lock, RW_WRITER);
/*
* If object was destroyed before we got the lock, just release
* our reference and indicate we didn't find the object.
*/
if (!uobj->uo_live) {
sol_ofs_uobj_put(uobj);
return (NULL);
}
return (uobj);
}
/*
* Function:
* sol_ofs_uobj_free
* Input:
* uobj - A pointer to the Solaris User Verbs kernel agent user
* object to be freed.
* Output:
* None.
* Returns:
* None.
* Description:
* Called when the user object is no longer referenced, it will release
* any user object resources and free the container object memory.
* NOTE: Currently there is a stipulation that the user object be the
* first element of any user object specialization.
*/
void
sol_ofs_uobj_free(sol_ofs_uobj_t *uobj)
{
size_t sz;
ASSERT(uobj);
/*
* Cleanup common user object and then free memory using
* length based on associated object type.
*/
ofs_uobj_fini(uobj);
sz = uobj->uo_uobj_sz;
if (sz)
kmem_free(uobj, sz);
}
/*
* Function:
* sol_ofs_uobj_put
* Input:
* uobj - Pointer to the user object
* Ouput:
* None
* Returns:
* None
* Description:
* Remove a lock associated with a user object, and decrement
* the reference held. On the last deference the user object
* will be freed.
*/
void
sol_ofs_uobj_put(sol_ofs_uobj_t *uobj)
{
rw_exit(&uobj->uo_lock);
sol_ofs_uobj_deref(uobj, sol_ofs_uobj_free);
}