Print this page
OS-2366 ddi_periodic_add(9F) is entirely rubbish (MORE)

Split Close
Expand all
Collapse all
          --- old/usr/src/uts/common/os/ddi_timer.c
          +++ new/usr/src/uts/common/os/ddi_periodic.c
   1    1  /*
   2      - * CDDL HEADER START
        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.
   3    6   *
   4      - * The contents of this file are subject to the terms of the
   5      - * Common Development and Distribution License (the "License").
   6      - * You may not use this file except in compliance with the License.
   7      - *
   8      - * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
   9      - * or http://www.opensolaris.org/os/licensing.
  10      - * See the License for the specific language governing permissions
  11      - * and limitations under the License.
  12      - *
  13      - * When distributing Covered Code, include this CDDL HEADER in each
  14      - * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  15      - * If applicable, add the following below this CDDL HEADER, with the
  16      - * fields enclosed by brackets "[]" replaced with your own identifying
  17      - * information: Portions Copyright [yyyy] [name of copyright owner]
  18      - *
  19      - * CDDL HEADER END
        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.
  20   10   */
  21      -
  22   11  /*
  23      - * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
  24      - * Use is subject to license terms.
  25      - */
  26      -/*
  27   12   * Copyright (c) 2013, Joyent, Inc. All rights reserved.
  28   13   */
  29   14  
  30   15  #include <sys/cmn_err.h>
  31      -#include <sys/ddi_timer.h>
       16 +#include <sys/ddi_periodic.h>
  32   17  #include <sys/id_space.h>
  33   18  #include <sys/kobj.h>
  34   19  #include <sys/sysmacros.h>
  35   20  #include <sys/systm.h>
  36   21  #include <sys/taskq.h>
  37   22  #include <sys/taskq_impl.h>
  38   23  #include <sys/time.h>
  39   24  #include <sys/types.h>
  40   25  #include <sys/sdt.h>
  41   26  
↓ open down ↓ 79 lines elided ↑ open up ↑
 121  106          PERI_IPL_0 = 0,
 122  107          PERI_IPL_1,
 123  108          PERI_IPL_2,
 124  109          PERI_IPL_3,
 125  110          PERI_IPL_4,
 126  111          PERI_IPL_5,
 127  112          PERI_IPL_6,
 128  113          PERI_IPL_7,
 129  114          PERI_IPL_8,
 130  115          PERI_IPL_9,
 131      -        PERI_IPL_10,
      116 +        PERI_IPL_10
 132  117  } periodic_ipl_t;
 133  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 +
 134  127  /*
 135  128   * This function may be called either from a soft interrupt handler
 136  129   * (ddi_periodic_softintr), or as a taskq worker function.
 137  130   */
 138  131  static void
 139  132  periodic_execute(void *arg)
 140  133  {
 141  134          ddi_periodic_impl_t *dpr = arg;
 142  135          mutex_enter(&dpr->dpr_lock);
 143  136  
 144  137          /*
 145  138           * We must be DISPATCHED, but not yet EXECUTING:
 146  139           */
 147  140          VERIFY((dpr->dpr_flags & (DPF_DISPATCHED | DPF_EXECUTING)) ==
 148  141              DPF_DISPATCHED);
      142 +        VERIFY(dpr->dpr_thread == NULL);
 149  143  
 150  144          if (!(dpr->dpr_flags & DPF_CANCELLED)) {
 151  145                  int level = dpr->dpr_level;
 152  146                  uint64_t count = dpr->dpr_fire_count;
 153  147                  /*
 154  148                   * If we have not yet been cancelled, then
 155  149                   * mark us executing:
 156  150                   */
 157  151                  dpr->dpr_flags |= DPF_EXECUTING;
      152 +                dpr->dpr_thread = curthread;
 158  153                  mutex_exit(&dpr->dpr_lock);
 159  154  
 160  155                  /*
 161  156                   * Execute the handler, without holding locks:
 162  157                   */
 163  158                  DTRACE_PROBE4(ddi__periodic__execute, void *, dpr->dpr_handler,
 164  159                      void *, dpr->dpr_arg, int, level, uint64_t, count);
 165  160                  (*dpr->dpr_handler)(dpr->dpr_arg);
 166  161                  DTRACE_PROBE4(ddi__periodic__done, void *, dpr->dpr_handler,
 167  162                      void *, dpr->dpr_arg, int, level, uint64_t, count);
 168  163  
 169  164                  mutex_enter(&dpr->dpr_lock);
      165 +                dpr->dpr_thread = NULL;
 170  166                  dpr->dpr_fire_count++;
 171  167          }
 172  168  
 173  169          /*
 174  170           * We're done with this periodic for now, so release it and
 175  171           * wake anybody that was waiting for us to be finished:
 176  172           */
 177  173          dpr->dpr_flags &= ~(DPF_DISPATCHED | DPF_EXECUTING);
 178  174          cv_broadcast(&dpr->dpr_cv);
 179  175          mutex_exit(&dpr->dpr_lock);
↓ open down ↓ 3 lines elided ↑ open up ↑
 183  179  ddi_periodic_softintr(int level)
 184  180  {
 185  181          ddi_periodic_impl_t *dpr;
 186  182          VERIFY(level >= PERI_IPL_1 && level <= PERI_IPL_10);
 187  183  
 188  184          mutex_enter(&periodics_lock);
 189  185          /*
 190  186           * Pull the first scheduled periodic off the queue for this priority
 191  187           * level:
 192  188           */
 193      -        while ((dpr = list_remove_head(&periodic_softint_queue[level - 1]))
 194      -            != NULL) {
      189 +        while ((dpr = list_remove_head(&periodic_softint_queue[level - 1])) !=
      190 +            NULL) {
 195  191                  mutex_exit(&periodics_lock);
 196  192                  /*
 197  193                   * And execute it:
 198  194                   */
 199  195                  periodic_execute(dpr);
 200  196                  mutex_enter(&periodics_lock);
 201  197          }
 202  198          mutex_exit(&periodics_lock);
 203  199  }
 204  200  
↓ open down ↓ 34 lines elided ↑ open up ↑
 239  235          periodic_taskq = taskq_create_instance("ddi_periodic_taskq", -1,
 240  236              ddi_periodic_taskq_threadcount, maxclsyspri, 0, 0, 0);
 241  237  
 242  238          /*
 243  239           * Initialize the mutex lock used for the soft interrupt request
 244  240           * queues.
 245  241           */
 246  242          mutex_init(&periodics_lock, NULL, MUTEX_ADAPTIVE, NULL);
 247  243  }
 248  244  
      245 +void
      246 +ddi_periodic_fini(void)
      247 +{
      248 +        ddi_periodic_impl_t *dpr;
      249 +        /*
      250 +         * Find all periodics that have not yet been unregistered and,
      251 +         * on DEBUG bits, print a warning about this resource leak.
      252 +         */
      253 +        mutex_enter(&periodics_lock);
      254 +        while ((dpr = list_head(&periodics)) != NULL) {
      255 +#ifdef  DEBUG
      256 +                printf("DDI periodic handler not deleted (id=%p, handler=%s)\n",
      257 +                    dpr->dpr_id, periodic_handler_symbol(dpr));
      258 +#endif
      259 +
      260 +                mutex_exit(&periodics_lock);
      261 +                /*
      262 +                 * Delete the periodic ourselves:
      263 +                 */
      264 +                i_untimeout(dpr->dpr_id);
      265 +                mutex_enter(&periodics_lock);
      266 +        }
      267 +        mutex_exit(&periodics_lock);
      268 +}
      269 +
 249  270  static void
 250  271  periodic_cyclic_handler(void *arg)
 251  272  {
 252  273          extern void sir_on(int);
 253  274          ddi_periodic_impl_t *dpr = arg;
 254  275  
 255  276          mutex_enter(&dpr->dpr_lock);
 256  277          /*
 257  278           * If we've been cancelled, or we're already dispatched, then exit
 258  279           * immediately:
↓ open down ↓ 38 lines elided ↑ open up ↑
 297  318  periodic_destroy(ddi_periodic_impl_t *dpr)
 298  319  {
 299  320          if (dpr == NULL)
 300  321                  return;
 301  322  
 302  323          /*
 303  324           * By now, we should have a periodic that is not busy, and has been
 304  325           * cancelled:
 305  326           */
 306  327          VERIFY(dpr->dpr_flags == DPF_CANCELLED);
      328 +        VERIFY(dpr->dpr_thread == NULL);
 307  329  
 308  330          id_free(periodic_id_space, dpr->dpr_id);
      331 +        cv_destroy(dpr->dpr_cv);
      332 +        mutex_destroy(dpr->dpr_lock);
 309  333          kmem_cache_free(periodic_cache, dpr);
 310  334  }
 311  335  
 312  336  static ddi_periodic_impl_t *
 313  337  periodic_create(void)
 314  338  {
 315  339          ddi_periodic_impl_t *dpr;
 316  340  
 317  341          dpr = kmem_cache_alloc(periodic_cache, KM_SLEEP);
 318  342          bzero(dpr, sizeof (*dpr));
 319  343          dpr->dpr_id = id_alloc(periodic_id_space);
 320  344          mutex_init(&dpr->dpr_lock, NULL, MUTEX_ADAPTIVE, NULL);
 321  345          cv_init(&dpr->dpr_cv, NULL, CV_DEFAULT, NULL);
 322  346  
 323  347          return (dpr);
 324  348  }
 325  349  
      350 +/*
      351 + * This function provides the implementation for the ddi_periodic_add(9F)
      352 + * interface.  It registers a periodic handler and returns an opaque identifier
      353 + * that can be unregistered via ddi_periodic_delete(9F)/i_untimeout().
      354 + *
      355 + * It may be called in user or kernel context, provided cpu_lock is not held.
      356 + */
 326  357  timeout_t
 327  358  i_timeout(void (*func)(void *), void *arg, hrtime_t interval, int level)
 328  359  {
 329  360          cyc_handler_t cyh;
 330  361          cyc_time_t cyt;
 331  362          ddi_periodic_impl_t *dpr;
 332  363  
 333  364          VERIFY(func != NULL);
 334  365          VERIFY(level >= 0 && level <= 10);
 335  366  
 336  367          /*
 337  368           * Allocate object to track this periodic:
 338  369           */
 339  370          dpr = periodic_create();
 340  371          dpr->dpr_level = level;
 341  372          dpr->dpr_handler = func;
 342  373          dpr->dpr_arg = arg;
 343  374  
 344  375          /*
 345      -         * The resolution must be finer than or equal to
 346      -         * the requested interval. If it's not, set the resolution
 347      -         * to the interval.
 348      -         * Note. There is a restriction currently. Regardless of the
 349      -         * clock resolution used here, 10ms is set as the timer resolution.
 350      -         * Even on the 1ms resolution timer, the minimum interval is 10ms.
      376 +         * The minimum supported interval between firings of the periodic
      377 +         * handler is 10ms; see ddi_periodic_add(9F) for more details.  If a
      378 +         * shorter interval is requested, round up.
 351  379           */
 352  380          if (ddi_periodic_resolution > interval) {
 353      -                uintptr_t pc = (uintptr_t)dpr->dpr_handler;
 354      -                ulong_t off;
 355  381                  cmn_err(CE_WARN,
 356  382                      "The periodic timeout (handler=%s, interval=%lld) "
 357  383                      "requests a finer interval than the supported resolution. "
 358      -                    "It rounds up to %lld\n", kobj_getsymname(pc, &off),
      384 +                    "It rounds up to %lld\n", periodic_handler_symbol(dpr),
 359  385                      interval, ddi_periodic_resolution);
 360  386                  interval = ddi_periodic_resolution;
 361  387          }
 362  388  
 363  389          /*
 364      -         * If the specified interval is already multiples of
 365      -         * the resolution, use it as is. Otherwise, it rounds
 366      -         * up to multiples of the timer resolution.
      390 +         * Ensure that the interval is an even multiple of the base resolution
      391 +         * that is at least as long as the requested interval.
 367  392           */
 368  393          dpr->dpr_interval = roundup(interval, ddi_periodic_resolution);
 369  394  
 370  395          /*
 371  396           * Create the underlying cyclic:
 372  397           */
 373  398          cyh.cyh_func = periodic_cyclic_handler;
 374  399          cyh.cyh_arg = dpr;
 375  400          cyh.cyh_level = CY_LOCK_LEVEL;
 376  401  
 377      -        cyt.cyt_when = roundup(gethrtime() + dpr->dpr_interval,
 378      -            ddi_periodic_resolution);
      402 +        cyt.cyt_when = 0;
 379  403          cyt.cyt_interval = dpr->dpr_interval;
 380  404  
 381  405          mutex_enter(&cpu_lock);
 382  406          dpr->dpr_cyclic_id = cyclic_add(&cyh, &cyt);
 383  407          mutex_exit(&cpu_lock);
 384  408  
 385  409          /*
 386  410           * Make the id visible to ddi_periodic_delete(9F) before we
 387  411           * return it:
 388  412           */
 389  413          mutex_enter(&periodics_lock);
 390  414          list_insert_tail(&periodics, dpr);
 391  415          mutex_exit(&periodics_lock);
 392  416  
 393  417          return ((timeout_t)(uintptr_t)dpr->dpr_id);
 394  418  }
 395  419  
 396  420  /*
 397      - *  void
 398      - *  i_untimeout(timeout_t req)
      421 + * This function provides the implementation for the ddi_periodic_delete(9F)
      422 + * interface.  It cancels a periodic handler previously registered through
      423 + * ddi_periodic_add(9F)/i_timeout().
 399  424   *
 400      - *  Overview
 401      - *    i_untimeout() is an internal function canceling the i_timeout()
 402      - *    request previously issued.
 403      - *    This function is used for ddi_periodic_delete(9F).
 404      - *
 405      - *  Argument
 406      - *      req: timeout_t opaque value i_timeout() returned previously.
 407      - *
 408      - *  Return value
 409      - *      Nothing.
 410      - *
 411      - *  Caller's context
 412      - *    i_untimeout() can be called in user, kernel or interrupt context.
 413      - *    It cannot be called in high interrupt context.
 414      - *
 415      - *  Note. This function is used by ddi_periodic_delete(), which cannot
 416      - *  be called in interrupt context. As a result, this function is called
 417      - *  in user or kernel context only in practice.
      425 + * It may be called in user or kernel context, provided cpu_lock is not held.
      426 + * It may NOT be called from within a periodic handler.
 418  427   */
 419  428  void
 420  429  i_untimeout(timeout_t id)
 421  430  {
 422  431          ddi_periodic_impl_t *dpr;
 423  432  
 424  433          /*
 425  434           * Find the periodic in the list of all periodics and remove it.
 426  435           * If we find in (and remove it from) the global list, we have
 427  436           * license to free it once it is no longer busy.
↓ open down ↓ 13 lines elided ↑ open up ↑
 441  450           */
 442  451          if (dpr == NULL)
 443  452                  return;
 444  453  
 445  454          mutex_enter(&dpr->dpr_lock);
 446  455          /*
 447  456           * We should be the only one trying to cancel this periodic:
 448  457           */
 449  458          VERIFY(!(dpr->dpr_flags & DPF_CANCELLED));
 450  459          /*
      460 +         * Removing a periodic from within its own handler function will
      461 +         * cause a deadlock, so panic explicitly.
      462 +         */
      463 +        if (dpr->dpr_thread == curthread) {
      464 +                panic("ddi_periodic_delete(%p) called from its own handler\n",
      465 +                    dpr->dpr_id);
      466 +        }
      467 +        /*
 451  468           * Mark the periodic as cancelled:
 452  469           */
 453  470          dpr->dpr_flags |= DPF_CANCELLED;
 454  471          mutex_exit(&dpr->dpr_lock);
 455  472  
 456  473          /*
 457  474           * Cancel our cyclic.  cyclic_remove() guarantees that the cyclic
 458  475           * handler will not run again after it returns.  Note that the cyclic
 459  476           * handler merely _dispatches_ the periodic, so this does _not_ mean
 460  477           * the periodic handler is also finished running.
↓ open down ↓ 16 lines elided ↑ open up ↑
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX