1 /*
   2  * This file and its contents are supplied under the terms of the
   3  * Common Development and Distribution License ("CDDL"), version 1.0.
   4  * You may only use this file in accordance with the terms of version
   5  * 1.0 of the CDDL.
   6  *
   7  * A full copy of the text of the CDDL should have accompanied this
   8  * source.  A copy of the CDDL is also available via the Internet at
   9  * http://www.illumos.org/license/CDDL.
  10  */
  11 /*
  12  * Copyright (c) 2013, Joyent, Inc. All rights reserved.
  13  */
  14 
  15 #include <sys/cmn_err.h>
  16 #include <sys/ddi_periodic.h>
  17 #include <sys/id_space.h>
  18 #include <sys/kobj.h>
  19 #include <sys/sysmacros.h>
  20 #include <sys/systm.h>
  21 #include <sys/taskq.h>
  22 #include <sys/taskq_impl.h>
  23 #include <sys/time.h>
  24 #include <sys/types.h>
  25 #include <sys/sdt.h>
  26 
  27 /*
  28  * The ddi_periodic_add(9F) Implementation
  29  *
  30  * This file contains the implementation of the ddi_periodic_add(9F) interface.
  31  * It is a thin wrapper around the cyclic subsystem (see documentation in
  32  * uts/common/os/cyclic.c), providing a DDI interface for registering
  33  * (and unregistering) callbacks for periodic invocation at arbitrary
  34  * interrupt levels, or in kernel context.
  35  *
  36  * Each call to ddi_periodic_add will result in a new opaque handle, as
  37  * allocated from an id_space, a new "periodic" object (ddi_periodic_impl_t)
  38  * and a registered cyclic.
  39  *
  40  * Operation
  41  *
  42  * Whenever the cyclic fires, our cyclic handler checks that the particular
  43  * periodic is not dispatched already (we do not support overlapping execution
  44  * of the consumer's handler function), and not yet cancelled.  If both of
  45  * these conditions hold, we mark the periodic as DPF_DISPATCHED and enqueue it
  46  * to either the taskq (for DDI_IPL_0) or to one of the soft interrupt queues
  47  * (DDI_IPL_1 to DDI_IPL_10).
  48  *
  49  * While the taskq (or soft interrupt handler) is handling a particular
  50  * periodic, we mark it as DPF_EXECUTING.  When complete, we reset both
  51  * DPF_DISPATCHED and DPF_EXECUTING.
  52  *
  53  * Cancellation
  54  *
  55  * ddi_periodic_delete(9F) historically had spectacularly loose semantics with
  56  * respect to cancellation concurrent with handler execution.  These semantics
  57  * are now tighter:
  58  *
  59  *   1. At most one invocation of ddi_periodic_delete(9F) will actually
  60  *      perform the deletion, all others will return immediately.
  61  *   2. The invocation that performs the deletion will _block_ until
  62  *      the handler is no longer running, and all resources have been
  63  *      released.
  64  *
  65  * We affect this model by removing the cancelling periodic from the
  66  * global list and marking it DPF_CANCELLED.  This will prevent further
  67  * execution of the handler.  We then wait on a CV until the DPF_EXECUTING
  68  * and DPF_DISPATCHED flags are clear, which means the periodic is removed
  69  * from all request queues, is no longer executing, and may be freed.  At this
  70  * point we return the opaque ID to the id_space and free the memory.
  71  *
  72  * NOTE:
  73  * The ddi_periodic_add(9F) interface is presently limited to a minimum period
  74  * of 10ms between firings.
  75  */
  76 
  77 /*
  78  * Tuneables:
  79  */
  80 int ddi_periodic_max_id = 1024;
  81 int ddi_periodic_taskq_threadcount = 4;
  82 hrtime_t ddi_periodic_resolution = 10000000;
  83 
  84 /*
  85  * Globals:
  86  */
  87 static kmem_cache_t *periodic_cache;
  88 static id_space_t *periodic_id_space;
  89 static taskq_t *periodic_taskq;
  90 
  91 /*
  92  * periodics_lock protects the list of all periodics (periodics), and
  93  * each of the soft interrupt request queues (periodic_softint_queue).
  94  *
  95  * Do not hold an individual periodic's lock while obtaining periodics_lock.
  96  * While in the periodic_softint_queue list, the periodic will be marked
  97  * DPF_DISPATCHED, and thus safe from frees.  Only the invocation of
  98  * i_untimeout() that removes the periodic from the global list is allowed
  99  * to free it.
 100  */
 101 static kmutex_t periodics_lock;
 102 static list_t periodics;
 103 static list_t periodic_softint_queue[10]; /* for IPL1 up to IPL10 */
 104 
 105 typedef enum periodic_ipl {
 106         PERI_IPL_0 = 0,
 107         PERI_IPL_1,
 108         PERI_IPL_2,
 109         PERI_IPL_3,
 110         PERI_IPL_4,
 111         PERI_IPL_5,
 112         PERI_IPL_6,
 113         PERI_IPL_7,
 114         PERI_IPL_8,
 115         PERI_IPL_9,
 116         PERI_IPL_10
 117 } periodic_ipl_t;
 118 
 119 static char *
 120 periodic_handler_symbol(ddi_periodic_impl_t *dpr)
 121 {
 122         ulong_t off;
 123 
 124         return (kobj_getsymname((uintptr_t)dpr->dpr_handler, &off));
 125 }
 126 
 127 /*
 128  * This function may be called either from a soft interrupt handler
 129  * (ddi_periodic_softintr), or as a taskq worker function.
 130  */
 131 static void
 132 periodic_execute(void *arg)
 133 {
 134         ddi_periodic_impl_t *dpr = arg;
 135         mutex_enter(&dpr->dpr_lock);
 136 
 137         /*
 138          * We must be DISPATCHED, but not yet EXECUTING:
 139          */
 140         VERIFY((dpr->dpr_flags & (DPF_DISPATCHED | DPF_EXECUTING)) ==
 141             DPF_DISPATCHED);
 142         VERIFY(dpr->dpr_thread == NULL);
 143 
 144         if (!(dpr->dpr_flags & DPF_CANCELLED)) {
 145                 int level = dpr->dpr_level;
 146                 uint64_t count = dpr->dpr_fire_count;
 147                 /*
 148                  * If we have not yet been cancelled, then
 149                  * mark us executing:
 150                  */
 151                 dpr->dpr_flags |= DPF_EXECUTING;
 152                 dpr->dpr_thread = curthread;
 153                 mutex_exit(&dpr->dpr_lock);
 154 
 155                 /*
 156                  * Execute the handler, without holding locks:
 157                  */
 158                 DTRACE_PROBE4(ddi__periodic__execute, void *, dpr->dpr_handler,
 159                     void *, dpr->dpr_arg, int, level, uint64_t, count);
 160                 (*dpr->dpr_handler)(dpr->dpr_arg);
 161                 DTRACE_PROBE4(ddi__periodic__done, void *, dpr->dpr_handler,
 162                     void *, dpr->dpr_arg, int, level, uint64_t, count);
 163 
 164                 mutex_enter(&dpr->dpr_lock);
 165                 dpr->dpr_thread = NULL;
 166                 dpr->dpr_fire_count++;
 167         }
 168 
 169         /*
 170          * We're done with this periodic for now, so release it and
 171          * wake anybody that was waiting for us to be finished:
 172          */
 173         dpr->dpr_flags &= ~(DPF_DISPATCHED | DPF_EXECUTING);
 174         cv_broadcast(&dpr->dpr_cv);
 175         mutex_exit(&dpr->dpr_lock);
 176 }
 177 
 178 void
 179 ddi_periodic_softintr(int level)
 180 {
 181         ddi_periodic_impl_t *dpr;
 182         VERIFY(level >= PERI_IPL_1 && level <= PERI_IPL_10);
 183 
 184         mutex_enter(&periodics_lock);
 185         /*
 186          * Pull the first scheduled periodic off the queue for this priority
 187          * level:
 188          */
 189         while ((dpr = list_remove_head(&periodic_softint_queue[level - 1])) !=
 190             NULL) {
 191                 mutex_exit(&periodics_lock);
 192                 /*
 193                  * And execute it:
 194                  */
 195                 periodic_execute(dpr);
 196                 mutex_enter(&periodics_lock);
 197         }
 198         mutex_exit(&periodics_lock);
 199 }
 200 
 201 void
 202 ddi_periodic_init(void)
 203 {
 204         int i;
 205 
 206         /*
 207          * Create a kmem_cache for request tracking objects, and a list
 208          * to store them in so we can later delete based on opaque handles:
 209          */
 210         periodic_cache = kmem_cache_create("ddi_periodic",
 211             sizeof (ddi_periodic_impl_t), 0, NULL, NULL, NULL, NULL, NULL, 0);
 212         list_create(&periodics, sizeof (ddi_periodic_impl_t),
 213             offsetof(ddi_periodic_impl_t, dpr_link));
 214 
 215         /*
 216          * Initialise the identifier space for ddi_periodic_add(9F):
 217          */
 218         periodic_id_space = id_space_create("ddi_periodic", 1,
 219             ddi_periodic_max_id);
 220 
 221         /*
 222          * Initialise the request queue for each soft interrupt level:
 223          */
 224         for (i = PERI_IPL_1; i <= PERI_IPL_10; i++) {
 225                 list_create(&periodic_softint_queue[i - 1],
 226                     sizeof (ddi_periodic_impl_t), offsetof(ddi_periodic_impl_t,
 227                     dpr_softint_link));
 228         }
 229 
 230         /*
 231          * Create the taskq for running PERI_IPL_0 handlers.  This taskq will
 232          * _only_ be used with taskq_dispatch_ent(), and a taskq_ent_t
 233          * pre-allocated with the ddi_periodic_impl_t.
 234          */
 235         periodic_taskq = taskq_create_instance("ddi_periodic_taskq", -1,
 236             ddi_periodic_taskq_threadcount, maxclsyspri, 0, 0, 0);
 237 
 238         /*
 239          * Initialize the mutex lock used for the soft interrupt request
 240          * queues.
 241          */
 242         mutex_init(&periodics_lock, NULL, MUTEX_ADAPTIVE, NULL);
 243 }
 244 
 245 void
 246 ddi_periodic_fini(void)
 247 {
 248         int i;
 249         ddi_periodic_impl_t *dpr;
 250 
 251         /*
 252          * Find all periodics that have not yet been unregistered and,
 253          * on DEBUG bits, print a warning about this resource leak.
 254          */
 255         mutex_enter(&periodics_lock);
 256         while ((dpr = list_head(&periodics)) != NULL) {
 257 #ifdef  DEBUG
 258                 printf("DDI periodic handler not deleted (id=%lx, hdlr=%s)\n",
 259                     (unsigned long)dpr->dpr_id, periodic_handler_symbol(dpr));
 260 #endif
 261 
 262                 mutex_exit(&periodics_lock);
 263                 /*
 264                  * Delete the periodic ourselves:
 265                  */
 266                 i_untimeout((timeout_t)(uintptr_t)dpr->dpr_id);
 267                 mutex_enter(&periodics_lock);
 268         }
 269         mutex_exit(&periodics_lock);
 270 
 271         /*
 272          * At this point there are no remaining cyclics, so clean up the
 273          * remaining resources:
 274          */
 275         taskq_destroy(periodic_taskq);
 276         periodic_taskq = NULL;
 277 
 278         id_space_destroy(periodic_id_space);
 279         periodic_id_space = NULL;
 280 
 281         kmem_cache_destroy(periodic_cache);
 282         periodic_cache = NULL;
 283 
 284         list_destroy(&periodics);
 285         for (i = PERI_IPL_1; i <= PERI_IPL_10; i++)
 286                 list_destroy(&periodic_softint_queue[i - 1]);
 287 
 288         mutex_destroy(&periodics_lock);
 289 }
 290 
 291 static void
 292 periodic_cyclic_handler(void *arg)
 293 {
 294         extern void sir_on(int);
 295         ddi_periodic_impl_t *dpr = arg;
 296 
 297         mutex_enter(&dpr->dpr_lock);
 298         /*
 299          * If we've been cancelled, or we're already dispatched, then exit
 300          * immediately:
 301          */
 302         if (dpr->dpr_flags & (DPF_CANCELLED | DPF_DISPATCHED)) {
 303                 mutex_exit(&dpr->dpr_lock);
 304                 return;
 305         }
 306         VERIFY(!(dpr->dpr_flags & DPF_EXECUTING));
 307 
 308         /*
 309          * This periodic is not presently dispatched, so dispatch it now:
 310          */
 311         dpr->dpr_flags |= DPF_DISPATCHED;
 312         mutex_exit(&dpr->dpr_lock);
 313 
 314         if (dpr->dpr_level == PERI_IPL_0) {
 315                 /*
 316                  * DDI_IPL_0 periodics are dispatched onto the taskq:
 317                  */
 318                 taskq_dispatch_ent(periodic_taskq, periodic_execute,
 319                     dpr, 0, &dpr->dpr_taskq_ent);
 320         } else {
 321                 /*
 322                  * Higher priority periodics are handled by a soft
 323                  * interrupt handler.  Enqueue us for processing and
 324                  * fire the soft interrupt:
 325                  */
 326                 mutex_enter(&periodics_lock);
 327                 list_insert_tail(&periodic_softint_queue[dpr->dpr_level - 1],
 328                     dpr);
 329                 mutex_exit(&periodics_lock);
 330 
 331                 /*
 332                  * Raise the soft interrupt level for this periodic:
 333                  */
 334                 sir_on(dpr->dpr_level);
 335         }
 336 }
 337 
 338 static void
 339 periodic_destroy(ddi_periodic_impl_t *dpr)
 340 {
 341         if (dpr == NULL)
 342                 return;
 343 
 344         /*
 345          * By now, we should have a periodic that is not busy, and has been
 346          * cancelled:
 347          */
 348         VERIFY(dpr->dpr_flags == DPF_CANCELLED);
 349         VERIFY(dpr->dpr_thread == NULL);
 350 
 351         id_free(periodic_id_space, dpr->dpr_id);
 352         cv_destroy(&dpr->dpr_cv);
 353         mutex_destroy(&dpr->dpr_lock);
 354         kmem_cache_free(periodic_cache, dpr);
 355 }
 356 
 357 static ddi_periodic_impl_t *
 358 periodic_create(void)
 359 {
 360         ddi_periodic_impl_t *dpr;
 361 
 362         dpr = kmem_cache_alloc(periodic_cache, KM_SLEEP);
 363         bzero(dpr, sizeof (*dpr));
 364         dpr->dpr_id = id_alloc(periodic_id_space);
 365         mutex_init(&dpr->dpr_lock, NULL, MUTEX_ADAPTIVE, NULL);
 366         cv_init(&dpr->dpr_cv, NULL, CV_DEFAULT, NULL);
 367 
 368         return (dpr);
 369 }
 370 
 371 /*
 372  * This function provides the implementation for the ddi_periodic_add(9F)
 373  * interface.  It registers a periodic handler and returns an opaque identifier
 374  * that can be unregistered via ddi_periodic_delete(9F)/i_untimeout().
 375  *
 376  * It may be called in user or kernel context, provided cpu_lock is not held.
 377  */
 378 timeout_t
 379 i_timeout(void (*func)(void *), void *arg, hrtime_t interval, int level)
 380 {
 381         cyc_handler_t cyh;
 382         cyc_time_t cyt;
 383         ddi_periodic_impl_t *dpr;
 384 
 385         VERIFY(func != NULL);
 386         VERIFY(level >= 0 && level <= 10);
 387 
 388         /*
 389          * Allocate object to track this periodic:
 390          */
 391         dpr = periodic_create();
 392         dpr->dpr_level = level;
 393         dpr->dpr_handler = func;
 394         dpr->dpr_arg = arg;
 395 
 396         /*
 397          * The minimum supported interval between firings of the periodic
 398          * handler is 10ms; see ddi_periodic_add(9F) for more details.  If a
 399          * shorter interval is requested, round up.
 400          */
 401         if (ddi_periodic_resolution > interval) {
 402                 cmn_err(CE_WARN,
 403                     "The periodic timeout (handler=%s, interval=%lld) "
 404                     "requests a finer interval than the supported resolution. "
 405                     "It rounds up to %lld\n", periodic_handler_symbol(dpr),
 406                     interval, ddi_periodic_resolution);
 407                 interval = ddi_periodic_resolution;
 408         }
 409 
 410         /*
 411          * Ensure that the interval is an even multiple of the base resolution
 412          * that is at least as long as the requested interval.
 413          */
 414         dpr->dpr_interval = roundup(interval, ddi_periodic_resolution);
 415 
 416         /*
 417          * Create the underlying cyclic:
 418          */
 419         cyh.cyh_func = periodic_cyclic_handler;
 420         cyh.cyh_arg = dpr;
 421         cyh.cyh_level = CY_LOCK_LEVEL;
 422 
 423         cyt.cyt_when = 0;
 424         cyt.cyt_interval = dpr->dpr_interval;
 425 
 426         mutex_enter(&cpu_lock);
 427         dpr->dpr_cyclic_id = cyclic_add(&cyh, &cyt);
 428         mutex_exit(&cpu_lock);
 429 
 430         /*
 431          * Make the id visible to ddi_periodic_delete(9F) before we
 432          * return it:
 433          */
 434         mutex_enter(&periodics_lock);
 435         list_insert_tail(&periodics, dpr);
 436         mutex_exit(&periodics_lock);
 437 
 438         return ((timeout_t)(uintptr_t)dpr->dpr_id);
 439 }
 440 
 441 /*
 442  * This function provides the implementation for the ddi_periodic_delete(9F)
 443  * interface.  It cancels a periodic handler previously registered through
 444  * ddi_periodic_add(9F)/i_timeout().
 445  *
 446  * It may be called in user or kernel context, provided cpu_lock is not held.
 447  * It may NOT be called from within a periodic handler.
 448  */
 449 void
 450 i_untimeout(timeout_t id)
 451 {
 452         ddi_periodic_impl_t *dpr;
 453 
 454         /*
 455          * Find the periodic in the list of all periodics and remove it.
 456          * If we find in (and remove it from) the global list, we have
 457          * license to free it once it is no longer busy.
 458          */
 459         mutex_enter(&periodics_lock);
 460         for (dpr = list_head(&periodics); dpr != NULL; dpr =
 461             list_next(&periodics, dpr)) {
 462                 if (dpr->dpr_id == (id_t)(uintptr_t)id) {
 463                         list_remove(&periodics, dpr);
 464                         break;
 465                 }
 466         }
 467         mutex_exit(&periodics_lock);
 468 
 469         /*
 470          * We could not find a periodic for this id, so bail out:
 471          */
 472         if (dpr == NULL)
 473                 return;
 474 
 475         mutex_enter(&dpr->dpr_lock);
 476         /*
 477          * We should be the only one trying to cancel this periodic:
 478          */
 479         VERIFY(!(dpr->dpr_flags & DPF_CANCELLED));
 480         /*
 481          * Removing a periodic from within its own handler function will
 482          * cause a deadlock, so panic explicitly.
 483          */
 484         if (dpr->dpr_thread == curthread) {
 485                 panic("ddi_periodic_delete(%lx) called from its own handler\n",
 486                     (unsigned long)dpr->dpr_id);
 487         }
 488         /*
 489          * Mark the periodic as cancelled:
 490          */
 491         dpr->dpr_flags |= DPF_CANCELLED;
 492         mutex_exit(&dpr->dpr_lock);
 493 
 494         /*
 495          * Cancel our cyclic.  cyclic_remove() guarantees that the cyclic
 496          * handler will not run again after it returns.  Note that the cyclic
 497          * handler merely _dispatches_ the periodic, so this does _not_ mean
 498          * the periodic handler is also finished running.
 499          */
 500         mutex_enter(&cpu_lock);
 501         cyclic_remove(dpr->dpr_cyclic_id);
 502         mutex_exit(&cpu_lock);
 503 
 504         /*
 505          * Wait until the periodic handler is no longer running:
 506          */
 507         mutex_enter(&dpr->dpr_lock);
 508         while (dpr->dpr_flags & (DPF_DISPATCHED | DPF_EXECUTING)) {
 509                 cv_wait(&dpr->dpr_cv, &dpr->dpr_lock);
 510         }
 511         mutex_exit(&dpr->dpr_lock);
 512 
 513         periodic_destroy(dpr);
 514 }