1 
   2 /*
   3  * CDDL HEADER START
   4  *
   5  * The contents of this file are subject to the terms of the
   6  * Common Development and Distribution License (the "License").
   7  * You may not use this file except in compliance with the License.
   8  *
   9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
  10  * or http://www.opensolaris.org/os/licensing.
  11  * See the License for the specific language governing permissions
  12  * and limitations under the License.
  13  *
  14  * When distributing Covered Code, include this CDDL HEADER in each
  15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  16  * If applicable, add the following below this CDDL HEADER, with the
  17  * fields enclosed by brackets "[]" replaced with your own identifying
  18  * information: Portions Copyright [yyyy] [name of copyright owner]
  19  *
  20  * CDDL HEADER END
  21  */
  22 
  23 /*
  24  * Copyright (c) 1990, 2010, Oracle and/or its affiliates. All rights reserved.
  25  * Copyright 2014 Garrett D'Amore <garrett@damore.org>
  26  * Copyright 2017 Nexenta Systems, Inc.  All rights reserved.
  27  */
  28 
  29 #include <sys/types.h>
  30 #include <sys/debug.h>
  31 #include <sys/sunddi.h>
  32 
  33 #define MIN_N_ITEMS     8
  34 
  35 typedef struct i_ddi_soft_state {
  36         void            **array;        /* the array of pointers */
  37         kmutex_t        lock;           /* serialize access to this struct */
  38         size_t          size;           /* how many bytes per state struct */
  39         size_t          n_items;        /* how many structs herein */
  40         void            *next;          /* unused */
  41 } i_ddi_soft_state;
  42 
  43 
  44 void *
  45 ddi_get_soft_state(void *state, int item)
  46 {
  47         i_ddi_soft_state        *ss = (i_ddi_soft_state *)state;
  48         void *ret = NULL;
  49 
  50         ASSERT((ss != NULL) && (item >= 0));
  51 
  52         mutex_enter(&ss->lock);
  53 
  54         if (item < ss->n_items && ss->array != NULL)
  55                 ret = ss->array[item];
  56 
  57         mutex_exit(&ss->lock);
  58 
  59         return (ret);
  60 }
  61 
  62 
  63 int
  64 ddi_soft_state_init(void **state_p, size_t size, size_t n_items)
  65 {
  66         i_ddi_soft_state        *ss;
  67 
  68         if (state_p == NULL || size == 0)
  69                 return (EINVAL);
  70 
  71         ss = kmem_zalloc(sizeof (*ss), KM_SLEEP);
  72         mutex_init(&ss->lock, NULL, MUTEX_DRIVER, NULL);
  73         ss->size = size;
  74 
  75         if (n_items < MIN_N_ITEMS)
  76                 ss->n_items = MIN_N_ITEMS;
  77         else {
  78                 ss->n_items = n_items;
  79         }
  80 
  81         ASSERT(ss->n_items >= n_items);
  82 
  83         ss->array = kmem_zalloc(ss->n_items * sizeof (void *), KM_SLEEP);
  84 
  85         *state_p = ss;
  86         return (0);
  87 }
  88 
  89 /*
  90  * Allocate a state structure of size 'size' to be associated
  91  * with item 'item'.
  92  *
  93  * In this implementation, the array is extended to
  94  * allow the requested offset, if needed.
  95  */
  96 int
  97 ddi_soft_state_zalloc(void *state, int item)
  98 {
  99         i_ddi_soft_state        *ss = (i_ddi_soft_state *)state;
 100         void                    **array;
 101         void                    *new_element;
 102 
 103         if ((state == NULL) || (item < 0))
 104                 return (DDI_FAILURE);
 105 
 106         mutex_enter(&ss->lock);
 107         if (ss->size == 0) {
 108                 mutex_exit(&ss->lock);
 109                 cmn_err(CE_WARN, "ddi_soft_state_zalloc: bad handle: %s",
 110                     "fake");
 111                 return (DDI_FAILURE);
 112         }
 113 
 114         array = ss->array;   /* NULL if ss->n_items == 0 */
 115         ASSERT(ss->n_items != 0 && array != NULL);
 116 
 117         /*
 118          * refuse to tread on an existing element
 119          */
 120         if (item < ss->n_items && array[item] != NULL) {
 121                 mutex_exit(&ss->lock);
 122                 return (DDI_FAILURE);
 123         }
 124 
 125         /*
 126          * Allocate a new element to plug in
 127          */
 128         new_element = kmem_zalloc(ss->size, KM_SLEEP);
 129 
 130         /*
 131          * Check if the array is big enough, if not, grow it.
 132          */
 133         if (item >= ss->n_items) {
 134                 void                    **new_array;
 135                 size_t                  new_n_items;
 136 
 137                 /*
 138                  * Allocate a new array of the right length, copy
 139                  * all the old pointers to the new array, then
 140                  * if it exists at all, put the old array on the
 141                  * dirty list.
 142                  *
 143                  * Note that we can't kmem_free() the old array.
 144                  *
 145                  * Why -- well the 'get' operation is 'mutex-free', so we
 146                  * can't easily catch a suspended thread that is just about
 147                  * to dereference the array we just grew out of.  So we
 148                  * cons up a header and put it on a list of 'dirty'
 149                  * pointer arrays.  (Dirty in the sense that there may
 150                  * be suspended threads somewhere that are in the middle
 151                  * of referencing them).  Fortunately, we -can- garbage
 152                  * collect it all at ddi_soft_state_fini time.
 153                  */
 154                 new_n_items = ss->n_items;
 155                 while (new_n_items < (1 + item))
 156                         new_n_items <<= 1;        /* double array size .. */
 157 
 158                 ASSERT(new_n_items >= (1 + item));   /* sanity check! */
 159 
 160                 new_array = kmem_zalloc(new_n_items * sizeof (void *),
 161                     KM_SLEEP);
 162                 /*
 163                  * Copy the pointers into the new array
 164                  */
 165                 bcopy(array, new_array, ss->n_items * sizeof (void *));
 166 
 167                 /*
 168                  * Free the old array now.  Note that
 169                  * ddi_get_soft_state takes the mutex.
 170                  */
 171                 kmem_free(ss->array, ss->n_items * sizeof (void *));
 172 
 173                 ss->array = (array = new_array);
 174                 ss->n_items = new_n_items;
 175         }
 176 
 177         ASSERT(array != NULL && item < ss->n_items && array[item] == NULL);
 178 
 179         array[item] = new_element;
 180 
 181         mutex_exit(&ss->lock);
 182         return (DDI_SUCCESS);
 183 }
 184 
 185 void
 186 ddi_soft_state_free(void *state, int item)
 187 {
 188         i_ddi_soft_state        *ss = (i_ddi_soft_state *)state;
 189         void                    **array;
 190         void                    *element;
 191         static char             msg[] = "ddi_soft_state_free:";
 192 
 193         if (ss == NULL) {
 194                 cmn_err(CE_WARN, "%s null handle: %s",
 195                     msg, "fake");
 196                 return;
 197         }
 198 
 199         element = NULL;
 200 
 201         mutex_enter(&ss->lock);
 202 
 203         if ((array = ss->array) == NULL || ss->size == 0) {
 204                 cmn_err(CE_WARN, "%s bad handle: %s",
 205                     msg, "fake");
 206         } else if (item < 0 || item >= ss->n_items) {
 207                 cmn_err(CE_WARN, "%s item %d not in range [0..%lu]: %s",
 208                     msg, item, (ulong_t)ss->n_items - 1, "fake");
 209         } else if (array[item] != NULL) {
 210                 element = array[item];
 211                 array[item] = NULL;
 212         }
 213 
 214         mutex_exit(&ss->lock);
 215 
 216         if (element)
 217                 kmem_free(element, ss->size);
 218 }
 219 
 220 /*
 221  * Free the entire set of pointers, and any
 222  * soft state structures contained therein.
 223  *
 224  * Note that we don't grab the ss->lock mutex, even though
 225  * we're inspecting the various fields of the data structure.
 226  *
 227  * There is an implicit assumption that this routine will
 228  * never run concurrently with any of the above on this
 229  * particular state structure i.e. by the time the driver
 230  * calls this routine, there should be no other threads
 231  * running in the driver.
 232  */
 233 void
 234 ddi_soft_state_fini(void **state_p)
 235 {
 236         i_ddi_soft_state        *ss;
 237         int                     item;
 238         static char             msg[] = "ddi_soft_state_fini:";
 239 
 240         if (state_p == NULL ||
 241             (ss = (i_ddi_soft_state *)(*state_p)) == NULL) {
 242                 cmn_err(CE_WARN, "%s null handle: %s",
 243                     msg, "fake");
 244                 return;
 245         }
 246 
 247         if (ss->size == 0) {
 248                 cmn_err(CE_WARN, "%s bad handle: %s",
 249                     msg, "fake");
 250                 return;
 251         }
 252 
 253         if (ss->n_items > 0) {
 254                 for (item = 0; item < ss->n_items; item++)
 255                         ddi_soft_state_free(ss, item);
 256                 kmem_free(ss->array, ss->n_items * sizeof (void *));
 257         }
 258 
 259         mutex_destroy(&ss->lock);
 260         kmem_free(ss, sizeof (*ss));
 261 
 262         *state_p = NULL;
 263 }