summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoshua M. Clulow <jmc@joyent.com>2013-07-29 11:05:02 -0700
committerJoshua M. Clulow <jmc@joyent.com>2013-07-29 18:35:03 +0000
commit78e78e4a1a42fe8915a50b36c14133ec19018ac8 (patch)
tree6fd04f5aa1c8d7c35b788776019a4898d0f0b446
parent4d48a1076df6f2309f4faf65ee88126c63e00477 (diff)
downloadillumos-joyent-78e78e4a1a42fe8915a50b36c14133ec19018ac8.tar.gz
OS-2366 ddi_periodic_add(9F) is entirely rubbish
Reviewed by: Robert Mustacchi <rm@joyent.com> Reviewed by: Richard Lowe <richlowe@richlowe.net>
-rw-r--r--manifest2
-rw-r--r--usr/src/cmd/mdb/common/modules/genunix/Makefile.files2
-rw-r--r--usr/src/cmd/mdb/common/modules/genunix/ddi_periodic.c71
-rw-r--r--usr/src/cmd/mdb/common/modules/genunix/ddi_periodic.h30
-rw-r--r--usr/src/cmd/mdb/common/modules/genunix/genunix.c6
-rw-r--r--usr/src/man/man9f/ddi_periodic_add.9f49
-rw-r--r--usr/src/man/man9f/ddi_periodic_delete.9f91
-rw-r--r--usr/src/pkg/manifests/system-header.mf2
-rw-r--r--usr/src/uts/common/Makefile.files2
-rw-r--r--usr/src/uts/common/os/clock.c16
-rw-r--r--usr/src/uts/common/os/damap.c2
-rw-r--r--usr/src/uts/common/os/ddi_periodic.c515
-rw-r--r--usr/src/uts/common/os/ddi_timer.c901
-rw-r--r--usr/src/uts/common/os/main.c8
-rw-r--r--usr/src/uts/common/os/sunddi.c2
-rw-r--r--usr/src/uts/common/sys/Makefile4
-rw-r--r--usr/src/uts/common/sys/ddi_periodic.h78
-rw-r--r--usr/src/uts/common/sys/ddi_timer.h147
-rw-r--r--usr/src/uts/common/syscall/uadmin.c9
-rw-r--r--usr/src/uts/i86pc/io/apix/apix.c23
-rw-r--r--usr/src/uts/i86pc/io/pcplusmp/apic.c26
-rw-r--r--usr/src/uts/i86pc/io/pcplusmp/apic_common.c5
-rw-r--r--usr/src/uts/i86pc/os/startup.c9
-rw-r--r--usr/src/uts/i86pc/sys/apic_common.h6
-rw-r--r--usr/src/uts/sun4/os/intr.c8
25 files changed, 853 insertions, 1161 deletions
diff --git a/manifest b/manifest
index 7abab81ea6..b76db25721 100644
--- a/manifest
+++ b/manifest
@@ -3511,7 +3511,7 @@ f usr/include/sys/ddi_intr.h 0644 root bin
f usr/include/sys/ddi_intr_impl.h 0644 root bin
f usr/include/sys/ddi_isa.h 0644 root bin
f usr/include/sys/ddi_obsolete.h 0644 root bin
-f usr/include/sys/ddi_timer.h 0644 root bin
+f usr/include/sys/ddi_periodic.h 0644 root bin
f usr/include/sys/ddidevmap.h 0644 root bin
f usr/include/sys/ddidmareq.h 0644 root bin
f usr/include/sys/ddifm.h 0644 root bin
diff --git a/usr/src/cmd/mdb/common/modules/genunix/Makefile.files b/usr/src/cmd/mdb/common/modules/genunix/Makefile.files
index 37f7a984da..bbbf70e0b1 100644
--- a/usr/src/cmd/mdb/common/modules/genunix/Makefile.files
+++ b/usr/src/cmd/mdb/common/modules/genunix/Makefile.files
@@ -21,6 +21,7 @@
#
# Copyright 2011 Nexenta Systems, Inc. All rights reserved.
# Copyright (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2013, Joyent, Inc. All rights reserved.
#
#
@@ -39,6 +40,7 @@ GENUNIX_SRCS = \
ctxop.c \
cyclic.c \
damap.c \
+ ddi_periodic.c \
devinfo.c \
dist.c \
findstack.c \
diff --git a/usr/src/cmd/mdb/common/modules/genunix/ddi_periodic.c b/usr/src/cmd/mdb/common/modules/genunix/ddi_periodic.c
new file mode 100644
index 0000000000..beafcb31d8
--- /dev/null
+++ b/usr/src/cmd/mdb/common/modules/genunix/ddi_periodic.c
@@ -0,0 +1,71 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source. A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+/*
+ * Copyright (c) 2013, Joyent, Inc. All rights reserved.
+ */
+
+#include "ddi_periodic.h"
+
+#include <mdb/mdb_modapi.h>
+#include <sys/ddi_periodic.h>
+#include <sys/sysmacros.h>
+#include <stdio.h>
+
+/*ARGSUSED*/
+int
+dprinfo(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
+{
+ char prflags[4];
+ ddi_periodic_impl_t dpr;
+ boolean_t verbose = B_FALSE;
+
+ if (!(flags & DCMD_ADDRSPEC)) {
+ if (mdb_walk_dcmd("ddi_periodic", "ddi_periodic", argc, argv)
+ == -1) {
+ mdb_warn("cannot walk 'ddi_periodic'");
+ return (DCMD_ERR);
+ }
+ return (DCMD_OK);
+ }
+
+ if (mdb_getopts(argc, argv,
+ 'v', MDB_OPT_SETBITS, B_TRUE, &verbose, NULL) != argc)
+ return (DCMD_USAGE);
+
+ if (mdb_vread(&dpr, sizeof (dpr), addr) == -1) {
+ mdb_warn("could not read ddi_periodic_impl_t at %p", addr);
+ return (DCMD_ERR);
+ }
+
+ if (DCMD_HDRSPEC(flags)) {
+ mdb_printf("%16s %4s %3s %5s %5s %12s %s\n", "ADDR", "ID",
+ "LVL", "FLAGS", "MS", "FIRE_COUNT", "HANDLER");
+ if (verbose) {
+ mdb_printf("%16s %16s %16s %s\n", "", "THREAD",
+ "CYCLIC_ID", "ARGUMENT");
+ }
+ }
+
+ prflags[0] = dpr.dpr_flags & DPF_DISPATCHED ? 'D' : '-';
+ prflags[1] = dpr.dpr_flags & DPF_EXECUTING ? 'X' : '-';
+ prflags[2] = dpr.dpr_flags & DPF_CANCELLED ? 'C' : '-';
+ prflags[3] = '\0';
+
+ mdb_printf("%16p %4x %3d %5s %5d %12x %a\n", addr, dpr.dpr_id,
+ dpr.dpr_level, prflags, (int)(dpr.dpr_interval / 1000000),
+ dpr.dpr_fire_count, dpr.dpr_handler);
+ if (verbose) {
+ mdb_printf("%16s %16p %16p %a\n", "", dpr.dpr_thread,
+ dpr.dpr_cyclic_id, dpr.dpr_arg);
+ }
+
+ return (DCMD_OK);
+}
diff --git a/usr/src/cmd/mdb/common/modules/genunix/ddi_periodic.h b/usr/src/cmd/mdb/common/modules/genunix/ddi_periodic.h
new file mode 100644
index 0000000000..fdfa0acd43
--- /dev/null
+++ b/usr/src/cmd/mdb/common/modules/genunix/ddi_periodic.h
@@ -0,0 +1,30 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source. A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+/*
+ * Copyright (c) 2013, Joyent, Inc. All rights reserved.
+ */
+
+#ifndef _MDB_DDI_PERIODIC_H
+#define _MDB_DDI_PERIODIC_H
+
+#include <mdb/mdb_modapi.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern int dprinfo(uintptr_t, uint_t, int, const mdb_arg_t *);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _MDB_DDI_PERIODIC_H */
diff --git a/usr/src/cmd/mdb/common/modules/genunix/genunix.c b/usr/src/cmd/mdb/common/modules/genunix/genunix.c
index 48578a52f4..3f9ee389a2 100644
--- a/usr/src/cmd/mdb/common/modules/genunix/genunix.c
+++ b/usr/src/cmd/mdb/common/modules/genunix/genunix.c
@@ -21,7 +21,7 @@
/*
* Copyright 2011 Nexenta Systems, Inc. All rights reserved.
* Copyright (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2012 Joyent, Inc. All rights reserved.
+ * Copyright (c) 2013 Joyent, Inc. All rights reserved.
*/
#include <mdb/mdb_param.h>
@@ -71,6 +71,7 @@
#include "ctxop.h"
#include "cyclic.h"
#include "damap.h"
+#include "ddi_periodic.h"
#include "devinfo.h"
#include "findstack.h"
#include "fm.h"
@@ -3882,6 +3883,9 @@ static const mdb_dcmd_t dcmds[] = {
/* from damap.c */
{ "damap", ":", "display a damap_t", damap, damap_help },
+ /* from ddi_periodic.c */
+ { "ddi_periodic", "?[-v]", "dump ddi_periodic_impl_t info", dprinfo },
+
/* from devinfo.c */
{ "devbindings", "?[-qs] [device-name | major-num]",
"print devinfo nodes bound to device-name or major-num",
diff --git a/usr/src/man/man9f/ddi_periodic_add.9f b/usr/src/man/man9f/ddi_periodic_add.9f
index 5501409e01..2b50d41630 100644
--- a/usr/src/man/man9f/ddi_periodic_add.9f
+++ b/usr/src/man/man9f/ddi_periodic_add.9f
@@ -1,11 +1,12 @@
'\" te
.\" Copyright (c) 2009, Sun Microsystems, Inc. All Rights Reserved
+.\" Copyright 2013, Joyent, Inc. All Rights Reserved.
.\" 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]
-.TH DDI_PERIODIC_ADD 9F "Apr 13, 2009"
+.TH DDI_PERIODIC_ADD 9F "Jul 23, 2013"
.SH NAME
-ddi_periodic_add \- issue nanosecond periodic timeout requests
+ddi_periodic_add \- request periodic function invocation
.SH SYNOPSIS
.LP
.nf
@@ -27,9 +28,7 @@ Solaris DDI specific (Solaris DDI)
\fB\fIfunc\fR\fR
.ad
.RS 12n
-The callback function is invoked periodically in the specified interval. If the
-argument level is zero, the function is invoked in kernel context. Otherwise,
-it's invoked in interrupt context at the specified level.
+The callback function to be invoked periodically in the specified interval.
.RE
.sp
@@ -47,7 +46,7 @@ The argument passed to the callback function.
\fB\fIinterval\fR\fR
.ad
.RS 12n
-Interval time in nanoseconds.
+The periodic interval time in nanoseconds.
.RE
.sp
@@ -56,12 +55,13 @@ Interval time in nanoseconds.
\fB\fIlevel\fR\fR
.ad
.RS 12n
-Callback interrupt level. If the value is zero, the callback function is
-invoked in kernel context. If the value is more than zero, but less than or
-equal to ten, the callback function is invoked in interrupt context at the
-specified interrupt level, which may be used for real time applications.
+The callback function is invoked at this priority level. If the value of
+\fIlevel\fR is zero, the callback function is invoked in kernel context.
+If the value is greater than zero, but less than or equal to ten, the callback
+function is invoked in interrupt context at the specified interrupt level,
+which may be used for real time applications.
.sp
-This value must be in range of 0-10, which can be either a numeric number, a
+This value must be in range of 0-10, which can be either an integer literal, a
pre-defined macro (\fBDDI_IPL_0\fR, ... , \fBDDI_IPL_10\fR), or the
\fBDDI_INTR_PRI\fR macro with the interrupt priority.
.RE
@@ -75,13 +75,15 @@ periodically invoked in the nanosecond interval time.
.LP
As with \fBtimeout\fR(9F), the exact time interval over which the function
takes effect cannot be guaranteed, but the value given is a close
-approximation.
+approximation. If the callback function has not finished execution when the
+next interval expires, the system will skip running the callback for that
+interval.
.SH RETURN VALUES
.sp
.LP
-\fBddi_periodic_add()\fRreturns the non-zero opaque value
-(\fBddi_periodic_t\fR), which might be used for \fBddi_periodic_delete\fR(9F)
-to specify the request.
+\fBddi_periodic_add()\fR returns the non-zero opaque value
+(\fBddi_periodic_t\fR), which is later used to cancel the periodic request
+with \fBddi_periodic_delete\fR(9F).
.SH CONTEXT
.sp
.LP
@@ -108,7 +110,7 @@ my_periodic_func(void *arg)
mutex_enter(&statep->lock);
if (load_unbalanced(statep)) {
- balance_tasks(statep);
+ balance_tasks(statep);
}
mutex_exit(&statep->lock);
}
@@ -118,8 +120,7 @@ start_periodic_timer(struct my_state *statep)
{
hrtime_t interval = CHECK_INTERVAL;
- mutex_init(&statep->lock, NULL, MUTEX_DRIVER,
- (void *)DDI_IPL_0);
+ mutex_init(&statep->lock, NULL, MUTEX_DRIVER, DDI_IPL_0);
/*
* Register my_callback which is invoked periodically
@@ -154,8 +155,7 @@ invoked in interrupt context at level 7.
{
hrtime_t interval = MONITOR_INTERVAL;
- mutex_init(&statep->lock, NULL, MUTEX_DRIVER,
- (void *)DDI_IPL_7);
+ mutex_init(&statep->lock, NULL, MUTEX_DRIVER, DDI_IPL_7);
/*
* Register the callback function invoked periodically
@@ -172,11 +172,10 @@ invoked in interrupt context at level 7.
.LP
\fBcv_timedwait\fR(9F), \fBddi_intr_get_pri\fR(9F),
\fBddi_periodic_delete\fR(9F), \fBddi_intr_get_softint_pri\fR(9F),
-\fBdelay\fR(9F), \fBdrv_usectohz\fR(9F), \fBqtimeout\fR(9F),
-\fBquntimeout\fR(9F), \fBtimeout\fR(9F), \fBuntimeout\fR(9F)
+\fBqtimeout\fR(9F), \fBquntimeout\fR(9F), \fBtimeout\fR(9F), \fBuntimeout\fR(9F)
.SH NOTES
.sp
.LP
-A caller can only specify an interval in an integral multiple of 10ms. No other
-values are supported at this time. The interval specified is a lower bound on
-the interval on which the callback occurs.
+The caller must specify \fIinterval\fR as an even, non-zero multiple of 10ms.
+No other values are supported at this time. The interval specified is a lower
+bound on the interval between executions of the callback.
diff --git a/usr/src/man/man9f/ddi_periodic_delete.9f b/usr/src/man/man9f/ddi_periodic_delete.9f
index cfaf0fc8a7..c8fdf8cb75 100644
--- a/usr/src/man/man9f/ddi_periodic_delete.9f
+++ b/usr/src/man/man9f/ddi_periodic_delete.9f
@@ -1,18 +1,19 @@
'\" te
+.\" Copyright 2013, Joyent, Inc. All Rights Reserved.
.\" Copyright (c) 2009, Sun Microsystems, Inc. All Rights Reserved
.\" 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]
-.TH DDI_PERIODIC_DELETE 9F "May 6, 2009"
+.TH DDI_PERIODIC_DELETE 9F "Jul 23, 2013"
.SH NAME
-ddi_periodic_delete \- cancel nanosecond periodic timeout requests
+ddi_periodic_delete \- cancel periodic function invocation requests
.SH SYNOPSIS
.LP
.nf
#include <sys/dditypes.h>
#include <sys/sunddi.h>
-\fBvoid\fR \fBddi_periodic_delete\fR(\fBddi_periodic_t\fR \fIreq\fR\fB);\fR
+\fBvoid\fR \fBddi_periodic_delete\fR(\fBddi_periodic_t\fR \fIrequest\fR\fB);\fR
.fi
.SH INTERFACE LEVEL
@@ -23,7 +24,7 @@ Solaris DDI specific (Solaris DDI)
.sp
.ne 2
.na
-\fB\fIreq\fR\fR
+\fB\fIrequest\fR\fR
.ad
.RS 7n
\fBddi_periodic_t\fR opaque value returned by \fBddi_periodic_add\fR(9F)
@@ -32,15 +33,20 @@ Solaris DDI specific (Solaris DDI)
.SH DESCRIPTION
.sp
.LP
-The \fBddi_periodic_delete()\fR function cancels the \fBddi_periodic_add\fR(9F)
-request that was previously issued.
+The \fBddi_periodic_delete()\fR function cancels a periodic invocation request
+previously established with \fBddi_periodic_add\fR(9F).
.sp
.LP
-As with \fBuntimeout\fR(9F), calling \fBddi_periodic_delete()\fR against a
-periodic \fItimeout\fR request which is either running on another CPU, or has
-already been canceled causes no problems. Unlike \fBuntimeout\fR(9F), there are
-no restrictions on the lock which might be held across the call to
-\fBddi_periodic_delete()\fR.
+It is not possible to cancel a periodic invocation request from within the
+periodic callback itself; to do so is a programming error that will panic the
+system. Instead, requests must be cancelled from some other user or kernel
+context routine, such as the \fBdetach\fR(9E) entry point of a module.
+.sp
+.LP
+If the callback function is already executing (for instance, on another CPU)
+when the request is cancelled, \fBddi_periodic_delete()\fR will block until
+it finishes executing and is completely unregistered. The callback will not
+be invoked again after the call to \fBddi_periodic_delete()\fR returns.
.SH CONTEXT
.sp
.LP
@@ -48,56 +54,54 @@ The \fBddi_periodic_delete()\fR function may be called from user or kernel
context.
.SH EXAMPLES
.LP
-\fBExample 1 \fRCancelling a timeout request
+\fBExample 1 \fRCancelling a periodic invocation request
.sp
.LP
-In the following example, the device driver cancels the \fItimeout\fR request
-by calling \fBddi_periodic_delete()\fR against the request that was previously
-issued.
+In the following example, the device driver cancels the request
+by calling \fBddi_periodic_delete()\fR and passing the opaque \fIrequest\fR
+identifier returned by a previous call to \fBddi_periodic_add\fR(9F).
.sp
.in +2
.nf
/*
-* Stop the periodic timer
-*/
+ * Stop the periodic timer.
+ */
static void
stop_periodic_timer(struct my_state *statep)
{
- ddi_periodic_delete(statep->periodic_id);
- delay(1); /* wait for one tick */
- mutex_destory(&statep->lock);
+ ddi_periodic_delete(statep->periodic_id);
+ mutex_destroy(&statep->lock);
}
static void
start_periodic_timer(struct my_state *statep)
{
- hrtime_t interval = CHECK_INTERVAL;
+ hrtime_t interval = CHECK_INTERVAL;
- mutex_init(&statep->lock, NULL, MUTEX_DRIVER,
- (void *)DDI_IPL_0);
+ mutex_init(&statep->lock, NULL, MUTEX_DRIVER, DDI_IPL_0);
- /*
- * Register my_callback which is invoked periodically
- * in CHECK_INTERVAL in kernel context.
- */
- statep->periodic_id = ddi_periodic_add(my_periodic_func,
- statep, interval, DDI_IPL_0);
+ /*
+ * Register my_callback which is invoked periodically
+ * in CHECK_INTERVAL in kernel context.
+ */
+ statep->periodic_id = ddi_periodic_add(my_periodic_func,
+ statep, interval, DDI_IPL_0);
}
static void
my_periodic_func(void *arg)
{
- /*
- * This handler is invoked periodically.
- */
- struct my_state *statep = (struct my_state *)arg;
+ /*
+ * This handler is invoked periodically.
+ */
+ struct my_state *statep = (struct my_state *)arg;
- mutex_enter(&statep->lock);
- if (load_unbalanced(statep)) {
- balance_tasks(statep);
- }
- mutex_exit(&statep->lock);
+ mutex_enter(&statep->lock);
+ if (load_unbalanced(statep)) {
+ balance_tasks(statep);
+ }
+ mutex_exit(&statep->lock);
}
.fi
.in -2
@@ -106,12 +110,11 @@ my_periodic_func(void *arg)
.sp
.LP
\fBcv_timedwait\fR(9F), \fBddi_intr_get_pri\fR(9F), \fBddi_periodic_add\fR(9F),
-\fBdelay\fR(9F), \fBdrv_usectohz\fR(9F), \fBqtimeout\fR(9F),
-\fBquntimeout\fR(9F), \fBtimeout\fR(9F), \fBuntimeout\fR(9F)
+\fBqtimeout\fR(9F), \fBquntimeout\fR(9F), \fBtimeout\fR(9F), \fBuntimeout\fR(9F)
.SH NOTES
.sp
.LP
-There might be a race between a callback invocation and
-\fBddi_periodic_delete()\fR. A device driver should take a responsibility for
-this avoidance if needed by using the kernel synchronization such as a mutex
-lock or calling \fBdelay\fR(9F) as in the example above.
+Historically this interface was advertised as safe for use from within the
+periodic callback function. In order to ensure the correct operation of the
+system, and as reflected in the documentation above, this unlikely (and
+unsafe) usage pattern is no longer allowed.
diff --git a/usr/src/pkg/manifests/system-header.mf b/usr/src/pkg/manifests/system-header.mf
index 3fb3c1351c..c5323637fe 100644
--- a/usr/src/pkg/manifests/system-header.mf
+++ b/usr/src/pkg/manifests/system-header.mf
@@ -900,7 +900,7 @@ file path=usr/include/sys/ddi_intr.h
file path=usr/include/sys/ddi_intr_impl.h
file path=usr/include/sys/ddi_isa.h
file path=usr/include/sys/ddi_obsolete.h
-file path=usr/include/sys/ddi_timer.h
+file path=usr/include/sys/ddi_periodic.h
file path=usr/include/sys/ddidevmap.h
file path=usr/include/sys/ddidmareq.h
file path=usr/include/sys/ddifm.h
diff --git a/usr/src/uts/common/Makefile.files b/usr/src/uts/common/Makefile.files
index c180719472..370e19147a 100644
--- a/usr/src/uts/common/Makefile.files
+++ b/usr/src/uts/common/Makefile.files
@@ -147,7 +147,7 @@ GENUNIX_OBJS += \
ddi_intr_impl.o \
ddi_intr_irm.o \
ddi_nodeid.o \
- ddi_timer.o \
+ ddi_periodic.o \
devcfg.o \
devcache.o \
device.o \
diff --git a/usr/src/uts/common/os/clock.c b/usr/src/uts/common/os/clock.c
index 3f4dd63c82..1aeb7678c6 100644
--- a/usr/src/uts/common/os/clock.c
+++ b/usr/src/uts/common/os/clock.c
@@ -23,7 +23,7 @@
/*
* Copyright (c) 1988, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2011, Joyent, Inc. All rights reserved.
+ * Copyright (c) 2013, Joyent, Inc. All rights reserved.
*/
#include <sys/param.h>
@@ -64,7 +64,7 @@
#include <sys/rctl.h>
#include <sys/task.h>
#include <sys/sdt.h>
-#include <sys/ddi_timer.h>
+#include <sys/ddi_periodic.h>
#include <sys/random.h>
#include <sys/modctl.h>
#include <sys/zone.h>
@@ -316,7 +316,6 @@ static int tod_broken = 0; /* clock chip doesn't work */
time_t boot_time = 0; /* Boot time in seconds since 1970 */
cyclic_id_t clock_cyclic; /* clock()'s cyclic_id */
cyclic_id_t deadman_cyclic; /* deadman()'s cyclic_id */
-cyclic_id_t ddi_timer_cyclic; /* cyclic_timer()'s cyclic_id */
extern void clock_tick_schedule(int);
@@ -947,7 +946,7 @@ clock(void)
void
clock_init(void)
{
- cyc_handler_t clk_hdlr, timer_hdlr, lbolt_hdlr;
+ cyc_handler_t clk_hdlr, lbolt_hdlr;
cyc_time_t clk_when, lbolt_when;
int i, sz;
intptr_t buf;
@@ -963,14 +962,6 @@ clock_init(void)
clk_when.cyt_interval = nsec_per_tick;
/*
- * cyclic_timer is dedicated to the ddi interface, which
- * uses the same clock resolution as the system one.
- */
- timer_hdlr.cyh_func = (cyc_func_t)cyclic_timer;
- timer_hdlr.cyh_level = CY_LOCK_LEVEL;
- timer_hdlr.cyh_arg = NULL;
-
- /*
* The lbolt cyclic will be reprogramed to fire at a nsec_per_tick
* interval to satisfy performance needs of the DDI lbolt consumers.
* It is off by default.
@@ -1045,7 +1036,6 @@ clock_init(void)
mutex_enter(&cpu_lock);
clock_cyclic = cyclic_add(&clk_hdlr, &clk_when);
- ddi_timer_cyclic = cyclic_add(&timer_hdlr, &clk_when);
lb_info->id.lbi_cyclic_id = cyclic_add(&lbolt_hdlr, &lbolt_when);
mutex_exit(&cpu_lock);
diff --git a/usr/src/uts/common/os/damap.c b/usr/src/uts/common/os/damap.c
index 4ad989dfbf..e311d75867 100644
--- a/usr/src/uts/common/os/damap.c
+++ b/usr/src/uts/common/os/damap.c
@@ -34,7 +34,7 @@
#include <sys/sunndi.h>
#include <sys/kstat.h>
#include <sys/conf.h>
-#include <sys/ddi_timer.h>
+#include <sys/ddi_periodic.h>
#include <sys/devctl.h>
#include <sys/callb.h>
#include <sys/sysevent.h>
diff --git a/usr/src/uts/common/os/ddi_periodic.c b/usr/src/uts/common/os/ddi_periodic.c
new file mode 100644
index 0000000000..d6c6006825
--- /dev/null
+++ b/usr/src/uts/common/os/ddi_periodic.c
@@ -0,0 +1,515 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source. A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+/*
+ * Copyright (c) 2013, Joyent, Inc. All rights reserved.
+ */
+
+#include <sys/cmn_err.h>
+#include <sys/ddi_periodic.h>
+#include <sys/id_space.h>
+#include <sys/kobj.h>
+#include <sys/sysmacros.h>
+#include <sys/systm.h>
+#include <sys/taskq.h>
+#include <sys/taskq_impl.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/sdt.h>
+
+extern void sir_on(int);
+
+/*
+ * The ddi_periodic_add(9F) Implementation
+ *
+ * This file contains the implementation of the ddi_periodic_add(9F) interface.
+ * It is a thin wrapper around the cyclic subsystem (see documentation in
+ * uts/common/os/cyclic.c), providing a DDI interface for registering
+ * (and unregistering) callbacks for periodic invocation at arbitrary
+ * interrupt levels, or in kernel context.
+ *
+ * Each call to ddi_periodic_add will result in a new opaque handle, as
+ * allocated from an id_space, a new "periodic" object (ddi_periodic_impl_t)
+ * and a registered cyclic.
+ *
+ * Operation
+ *
+ * Whenever the cyclic fires, our cyclic handler checks that the particular
+ * periodic is not dispatched already (we do not support overlapping execution
+ * of the consumer's handler function), and not yet cancelled. If both of
+ * these conditions hold, we mark the periodic as DPF_DISPATCHED and enqueue it
+ * to either the taskq (for DDI_IPL_0) or to one of the soft interrupt queues
+ * (DDI_IPL_1 to DDI_IPL_10).
+ *
+ * While the taskq (or soft interrupt handler) is handling a particular
+ * periodic, we mark it as DPF_EXECUTING. When complete, we reset both
+ * DPF_DISPATCHED and DPF_EXECUTING.
+ *
+ * Cancellation
+ *
+ * ddi_periodic_delete(9F) historically had spectacularly loose semantics with
+ * respect to cancellation concurrent with handler execution. These semantics
+ * are now tighter:
+ *
+ * 1. At most one invocation of ddi_periodic_delete(9F) will actually
+ * perform the deletion, all others will return immediately.
+ * 2. The invocation that performs the deletion will _block_ until
+ * the handler is no longer running, and all resources have been
+ * released.
+ *
+ * We affect this model by removing the cancelling periodic from the
+ * global list and marking it DPF_CANCELLED. This will prevent further
+ * execution of the handler. We then wait on a CV until the DPF_EXECUTING
+ * and DPF_DISPATCHED flags are clear, which means the periodic is removed
+ * from all request queues, is no longer executing, and may be freed. At this
+ * point we return the opaque ID to the id_space and free the memory.
+ *
+ * NOTE:
+ * The ddi_periodic_add(9F) interface is presently limited to a minimum period
+ * of 10ms between firings.
+ */
+
+/*
+ * Tuneables:
+ */
+int ddi_periodic_max_id = 1024;
+int ddi_periodic_taskq_threadcount = 4;
+hrtime_t ddi_periodic_resolution = 10000000;
+
+/*
+ * Globals:
+ */
+static kmem_cache_t *periodic_cache;
+static id_space_t *periodic_id_space;
+static taskq_t *periodic_taskq;
+
+/*
+ * periodics_lock protects the list of all periodics (periodics), and
+ * each of the soft interrupt request queues (periodic_softint_queue).
+ *
+ * Do not hold an individual periodic's lock while obtaining periodics_lock.
+ * While in the periodic_softint_queue list, the periodic will be marked
+ * DPF_DISPATCHED, and thus safe from frees. Only the invocation of
+ * i_untimeout() that removes the periodic from the global list is allowed
+ * to free it.
+ */
+static kmutex_t periodics_lock;
+static list_t periodics;
+static list_t periodic_softint_queue[10]; /* for IPL1 up to IPL10 */
+
+typedef enum periodic_ipl {
+ PERI_IPL_0 = 0,
+ PERI_IPL_1,
+ PERI_IPL_2,
+ PERI_IPL_3,
+ PERI_IPL_4,
+ PERI_IPL_5,
+ PERI_IPL_6,
+ PERI_IPL_7,
+ PERI_IPL_8,
+ PERI_IPL_9,
+ PERI_IPL_10
+} periodic_ipl_t;
+
+static char *
+periodic_handler_symbol(ddi_periodic_impl_t *dpr)
+{
+ ulong_t off;
+
+ return (kobj_getsymname((uintptr_t)dpr->dpr_handler, &off));
+}
+
+/*
+ * This function may be called either from a soft interrupt handler
+ * (ddi_periodic_softintr), or as a taskq worker function.
+ */
+static void
+periodic_execute(void *arg)
+{
+ ddi_periodic_impl_t *dpr = arg;
+ mutex_enter(&dpr->dpr_lock);
+
+ /*
+ * We must be DISPATCHED, but not yet EXECUTING:
+ */
+ VERIFY((dpr->dpr_flags & (DPF_DISPATCHED | DPF_EXECUTING)) ==
+ DPF_DISPATCHED);
+ VERIFY(dpr->dpr_thread == NULL);
+
+ if (!(dpr->dpr_flags & DPF_CANCELLED)) {
+ int level = dpr->dpr_level;
+ uint64_t count = dpr->dpr_fire_count;
+ /*
+ * If we have not yet been cancelled, then
+ * mark us executing:
+ */
+ dpr->dpr_flags |= DPF_EXECUTING;
+ dpr->dpr_thread = curthread;
+ mutex_exit(&dpr->dpr_lock);
+
+ /*
+ * Execute the handler, without holding locks:
+ */
+ DTRACE_PROBE4(ddi__periodic__execute, void *, dpr->dpr_handler,
+ void *, dpr->dpr_arg, int, level, uint64_t, count);
+ (*dpr->dpr_handler)(dpr->dpr_arg);
+ DTRACE_PROBE4(ddi__periodic__done, void *, dpr->dpr_handler,
+ void *, dpr->dpr_arg, int, level, uint64_t, count);
+
+ mutex_enter(&dpr->dpr_lock);
+ dpr->dpr_thread = NULL;
+ dpr->dpr_fire_count++;
+ }
+
+ /*
+ * We're done with this periodic for now, so release it and
+ * wake anybody that was waiting for us to be finished:
+ */
+ dpr->dpr_flags &= ~(DPF_DISPATCHED | DPF_EXECUTING);
+ cv_broadcast(&dpr->dpr_cv);
+ mutex_exit(&dpr->dpr_lock);
+}
+
+void
+ddi_periodic_softintr(int level)
+{
+ ddi_periodic_impl_t *dpr;
+ VERIFY(level >= PERI_IPL_1 && level <= PERI_IPL_10);
+
+ mutex_enter(&periodics_lock);
+ /*
+ * Pull the first scheduled periodic off the queue for this priority
+ * level:
+ */
+ while ((dpr = list_remove_head(&periodic_softint_queue[level - 1])) !=
+ NULL) {
+ mutex_exit(&periodics_lock);
+ /*
+ * And execute it:
+ */
+ periodic_execute(dpr);
+ mutex_enter(&periodics_lock);
+ }
+ mutex_exit(&periodics_lock);
+}
+
+void
+ddi_periodic_init(void)
+{
+ int i;
+
+ /*
+ * Create a kmem_cache for request tracking objects, and a list
+ * to store them in so we can later delete based on opaque handles:
+ */
+ periodic_cache = kmem_cache_create("ddi_periodic",
+ sizeof (ddi_periodic_impl_t), 0, NULL, NULL, NULL, NULL, NULL, 0);
+ list_create(&periodics, sizeof (ddi_periodic_impl_t),
+ offsetof(ddi_periodic_impl_t, dpr_link));
+
+ /*
+ * Initialise the identifier space for ddi_periodic_add(9F):
+ */
+ periodic_id_space = id_space_create("ddi_periodic", 1,
+ ddi_periodic_max_id);
+
+ /*
+ * Initialise the request queue for each soft interrupt level:
+ */
+ for (i = PERI_IPL_1; i <= PERI_IPL_10; i++) {
+ list_create(&periodic_softint_queue[i - 1],
+ sizeof (ddi_periodic_impl_t), offsetof(ddi_periodic_impl_t,
+ dpr_softint_link));
+ }
+
+ /*
+ * Create the taskq for running PERI_IPL_0 handlers. This taskq will
+ * _only_ be used with taskq_dispatch_ent(), and a taskq_ent_t
+ * pre-allocated with the ddi_periodic_impl_t.
+ */
+ periodic_taskq = taskq_create_instance("ddi_periodic_taskq", -1,
+ ddi_periodic_taskq_threadcount, maxclsyspri, 0, 0, 0);
+
+ /*
+ * Initialize the mutex lock used for the soft interrupt request
+ * queues.
+ */
+ mutex_init(&periodics_lock, NULL, MUTEX_ADAPTIVE, NULL);
+}
+
+void
+ddi_periodic_fini(void)
+{
+ int i;
+ ddi_periodic_impl_t *dpr;
+
+ /*
+ * Find all periodics that have not yet been unregistered and,
+ * on DEBUG bits, print a warning about this resource leak.
+ */
+ mutex_enter(&periodics_lock);
+ while ((dpr = list_head(&periodics)) != NULL) {
+#ifdef DEBUG
+ printf("DDI periodic handler not deleted (id=%lx, hdlr=%s)\n",
+ (unsigned long)dpr->dpr_id, periodic_handler_symbol(dpr));
+#endif
+
+ mutex_exit(&periodics_lock);
+ /*
+ * Delete the periodic ourselves:
+ */
+ i_untimeout((timeout_t)(uintptr_t)dpr->dpr_id);
+ mutex_enter(&periodics_lock);
+ }
+ mutex_exit(&periodics_lock);
+
+ /*
+ * At this point there are no remaining cyclics, so clean up the
+ * remaining resources:
+ */
+ taskq_destroy(periodic_taskq);
+ periodic_taskq = NULL;
+
+ id_space_destroy(periodic_id_space);
+ periodic_id_space = NULL;
+
+ kmem_cache_destroy(periodic_cache);
+ periodic_cache = NULL;
+
+ list_destroy(&periodics);
+ for (i = PERI_IPL_1; i <= PERI_IPL_10; i++)
+ list_destroy(&periodic_softint_queue[i - 1]);
+
+ mutex_destroy(&periodics_lock);
+}
+
+static void
+periodic_cyclic_handler(void *arg)
+{
+ ddi_periodic_impl_t *dpr = arg;
+
+ mutex_enter(&dpr->dpr_lock);
+ /*
+ * If we've been cancelled, or we're already dispatched, then exit
+ * immediately:
+ */
+ if (dpr->dpr_flags & (DPF_CANCELLED | DPF_DISPATCHED)) {
+ mutex_exit(&dpr->dpr_lock);
+ return;
+ }
+ VERIFY(!(dpr->dpr_flags & DPF_EXECUTING));
+
+ /*
+ * This periodic is not presently dispatched, so dispatch it now:
+ */
+ dpr->dpr_flags |= DPF_DISPATCHED;
+ mutex_exit(&dpr->dpr_lock);
+
+ if (dpr->dpr_level == PERI_IPL_0) {
+ /*
+ * DDI_IPL_0 periodics are dispatched onto the taskq:
+ */
+ taskq_dispatch_ent(periodic_taskq, periodic_execute,
+ dpr, 0, &dpr->dpr_taskq_ent);
+ } else {
+ /*
+ * Higher priority periodics are handled by a soft interrupt
+ * handler. Enqueue us for processing by the handler:
+ */
+ mutex_enter(&periodics_lock);
+ list_insert_tail(&periodic_softint_queue[dpr->dpr_level - 1],
+ dpr);
+ mutex_exit(&periodics_lock);
+
+ /*
+ * Request the execution of the soft interrupt handler for this
+ * periodic's priority level.
+ */
+ sir_on(dpr->dpr_level);
+ }
+}
+
+static void
+periodic_destroy(ddi_periodic_impl_t *dpr)
+{
+ if (dpr == NULL)
+ return;
+
+ /*
+ * By now, we should have a periodic that is not busy, and has been
+ * cancelled:
+ */
+ VERIFY(dpr->dpr_flags == DPF_CANCELLED);
+ VERIFY(dpr->dpr_thread == NULL);
+
+ id_free(periodic_id_space, dpr->dpr_id);
+ cv_destroy(&dpr->dpr_cv);
+ mutex_destroy(&dpr->dpr_lock);
+ kmem_cache_free(periodic_cache, dpr);
+}
+
+static ddi_periodic_impl_t *
+periodic_create(void)
+{
+ ddi_periodic_impl_t *dpr;
+
+ dpr = kmem_cache_alloc(periodic_cache, KM_SLEEP);
+ bzero(dpr, sizeof (*dpr));
+ dpr->dpr_id = id_alloc(periodic_id_space);
+ mutex_init(&dpr->dpr_lock, NULL, MUTEX_ADAPTIVE, NULL);
+ cv_init(&dpr->dpr_cv, NULL, CV_DEFAULT, NULL);
+
+ return (dpr);
+}
+
+/*
+ * This function provides the implementation for the ddi_periodic_add(9F)
+ * interface. It registers a periodic handler and returns an opaque identifier
+ * that can be unregistered via ddi_periodic_delete(9F)/i_untimeout().
+ *
+ * It may be called in user or kernel context, provided cpu_lock is not held.
+ */
+timeout_t
+i_timeout(void (*func)(void *), void *arg, hrtime_t interval, int level)
+{
+ cyc_handler_t cyh;
+ cyc_time_t cyt;
+ ddi_periodic_impl_t *dpr;
+
+ VERIFY(func != NULL);
+ VERIFY(level >= 0 && level <= 10);
+
+ /*
+ * Allocate object to track this periodic:
+ */
+ dpr = periodic_create();
+ dpr->dpr_level = level;
+ dpr->dpr_handler = func;
+ dpr->dpr_arg = arg;
+
+ /*
+ * The minimum supported interval between firings of the periodic
+ * handler is 10ms; see ddi_periodic_add(9F) for more details. If a
+ * shorter interval is requested, round up.
+ */
+ if (ddi_periodic_resolution > interval) {
+ cmn_err(CE_WARN,
+ "The periodic timeout (handler=%s, interval=%lld) "
+ "requests a finer interval than the supported resolution. "
+ "It rounds up to %lld\n", periodic_handler_symbol(dpr),
+ interval, ddi_periodic_resolution);
+ interval = ddi_periodic_resolution;
+ }
+
+ /*
+ * Ensure that the interval is an even multiple of the base resolution
+ * that is at least as long as the requested interval.
+ */
+ dpr->dpr_interval = roundup(interval, ddi_periodic_resolution);
+
+ /*
+ * Create the underlying cyclic:
+ */
+ cyh.cyh_func = periodic_cyclic_handler;
+ cyh.cyh_arg = dpr;
+ cyh.cyh_level = CY_LOCK_LEVEL;
+
+ cyt.cyt_when = 0;
+ cyt.cyt_interval = dpr->dpr_interval;
+
+ mutex_enter(&cpu_lock);
+ dpr->dpr_cyclic_id = cyclic_add(&cyh, &cyt);
+ mutex_exit(&cpu_lock);
+
+ /*
+ * Make the id visible to ddi_periodic_delete(9F) before we
+ * return it:
+ */
+ mutex_enter(&periodics_lock);
+ list_insert_tail(&periodics, dpr);
+ mutex_exit(&periodics_lock);
+
+ return ((timeout_t)(uintptr_t)dpr->dpr_id);
+}
+
+/*
+ * This function provides the implementation for the ddi_periodic_delete(9F)
+ * interface. It cancels a periodic handler previously registered through
+ * ddi_periodic_add(9F)/i_timeout().
+ *
+ * It may be called in user or kernel context, provided cpu_lock is not held.
+ * It may NOT be called from within a periodic handler.
+ */
+void
+i_untimeout(timeout_t id)
+{
+ ddi_periodic_impl_t *dpr;
+
+ /*
+ * Find the periodic in the list of all periodics and remove it.
+ * If we find in (and remove it from) the global list, we have
+ * license to free it once it is no longer busy.
+ */
+ mutex_enter(&periodics_lock);
+ for (dpr = list_head(&periodics); dpr != NULL; dpr =
+ list_next(&periodics, dpr)) {
+ if (dpr->dpr_id == (id_t)(uintptr_t)id) {
+ list_remove(&periodics, dpr);
+ break;
+ }
+ }
+ mutex_exit(&periodics_lock);
+
+ /*
+ * We could not find a periodic for this id, so bail out:
+ */
+ if (dpr == NULL)
+ return;
+
+ mutex_enter(&dpr->dpr_lock);
+ /*
+ * We should be the only one trying to cancel this periodic:
+ */
+ VERIFY(!(dpr->dpr_flags & DPF_CANCELLED));
+ /*
+ * Removing a periodic from within its own handler function will
+ * cause a deadlock, so panic explicitly.
+ */
+ if (dpr->dpr_thread == curthread) {
+ panic("ddi_periodic_delete(%lx) called from its own handler\n",
+ (unsigned long)dpr->dpr_id);
+ }
+ /*
+ * Mark the periodic as cancelled:
+ */
+ dpr->dpr_flags |= DPF_CANCELLED;
+ mutex_exit(&dpr->dpr_lock);
+
+ /*
+ * Cancel our cyclic. cyclic_remove() guarantees that the cyclic
+ * handler will not run again after it returns. Note that the cyclic
+ * handler merely _dispatches_ the periodic, so this does _not_ mean
+ * the periodic handler is also finished running.
+ */
+ mutex_enter(&cpu_lock);
+ cyclic_remove(dpr->dpr_cyclic_id);
+ mutex_exit(&cpu_lock);
+
+ /*
+ * Wait until the periodic handler is no longer running:
+ */
+ mutex_enter(&dpr->dpr_lock);
+ while (dpr->dpr_flags & (DPF_DISPATCHED | DPF_EXECUTING)) {
+ cv_wait(&dpr->dpr_cv, &dpr->dpr_lock);
+ }
+ mutex_exit(&dpr->dpr_lock);
+
+ periodic_destroy(dpr);
+}
diff --git a/usr/src/uts/common/os/ddi_timer.c b/usr/src/uts/common/os/ddi_timer.c
deleted file mode 100644
index 84107032a4..0000000000
--- a/usr/src/uts/common/os/ddi_timer.c
+++ /dev/null
@@ -1,901 +0,0 @@
-/*
- * 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 2009 Sun Microsystems, Inc. All rights reserved.
- * Use is subject to license terms.
- */
-
-#include <sys/atomic.h>
-#include <sys/callb.h>
-#include <sys/conf.h>
-#include <sys/cmn_err.h>
-#include <sys/taskq.h>
-#include <sys/dditypes.h>
-#include <sys/ddi_timer.h>
-#include <sys/disp.h>
-#include <sys/kobj.h>
-#include <sys/note.h>
-#include <sys/param.h>
-#include <sys/sysmacros.h>
-#include <sys/systm.h>
-#include <sys/time.h>
-#include <sys/types.h>
-
-/*
- * global variables for timeout request
- */
-static kmem_cache_t *req_cache; /* kmem cache for timeout request */
-
-/*
- * taskq parameters for cyclic_timer
- *
- * timer_taskq_num:
- * timer_taskq_num represents the number of taskq threads.
- * Currently 4 threads are pooled to handle periodic timeout requests.
- * This number is chosen based on the fact that the callout (one-time
- * timeout framework) uses 8 threads with TQ_NOSLEEP; the periodic timeout
- * calls taskq_dispatch() with TQ_SLEEP instead, and in this case, 4 threads
- * should be sufficient to handle periodic timeout requests. (see also
- * timer_taskq_max_num below)
- *
- * timer_taskq_min_num:
- * timer_taskq_min_num represents the number of pre-populated taskq_ent
- * structures, and this variable holds the same value as timer_taskq_num does.
- *
- * timer_taskq_max_num:
- * Since TQ_SLEEP is set when taskq_dispatch() is called, the framework waits
- * for one second if more taskq_ent structures than timer_taskq_max_num are
- * required. However, from the timeout point of view, one second is much longer
- * than expected, and to prevent this occurrence, timer_taskq_max_num should
- * hold a sufficiently-large value, which is 128 here. Note that since the size
- * of taskq_ent_t is relatively small, this doesn't use up the resource so much.
- * (Currently the size is less than 8k at most)
- *
- * About the detailed explanation of the taskq function arguments, please see
- * usr/src/uts/common/os/taskq.c.
- */
-int timer_taskq_num = 4; /* taskq thread number */
-int timer_taskq_min_num = 4; /* min. number of taskq_ent structs */
-int timer_taskq_max_num = 128; /* max. number of taskq_ent structs */
-static taskq_t *tm_taskq; /* taskq thread pool */
-static kthread_t *tm_work_thread; /* work thread invoking taskq */
-
-/*
- * timer variables
- */
-static cyc_timer_t *ddi_timer; /* ddi timer based on the cyclic */
-static volatile hrtime_t timer_hrtime; /* current tick time on the timer */
-
-/*
- * Variable used for the suspend/resume.
- */
-static volatile boolean_t timer_suspended;
-
-/*
- * Kernel taskq queue to ddi timer
- */
-static list_t kern_queue; /* kernel thread request queue */
-static kcondvar_t kern_cv; /* condition variable for taskq queue */
-
-/*
- * Software interrupt queue dedicated to ddi timer
- */
-static list_t intr_queue; /* software interrupt request queue */
-static uint_t intr_state; /* software interrupt state */
-
-/*
- * This lock is used to protect the intr_queue and kern_queue.
- * It's also used to protect the intr_state which represents the software
- * interrupt state for the timer.
- */
-static kmutex_t disp_req_lock;
-
-/*
- * the periodic timer interrupt priority level
- */
-enum {
- TM_IPL_0 = 0, /* kernel context */
- TM_IPL_1, TM_IPL_2, TM_IPL_3, /* level 1-3 */
- TM_IPL_4, TM_IPL_5, TM_IPL_6, /* level 4-6 */
- TM_IPL_7, TM_IPL_8, TM_IPL_9, /* level 7-9 */
- TM_IPL_10 /* level 10 */
-};
-
-/*
- * A callback handler used by CPR to stop and resume callouts.
- * Since the taskq uses TASKQ_CPR_SAFE, the function just set the boolean
- * flag to timer_suspended here.
- */
-/*ARGSUSED*/
-static boolean_t
-timer_cpr_callb(void *arg, int code)
-{
- timer_suspended = (code == CB_CODE_CPR_CHKPT);
- return (B_TRUE);
-}
-
-/*
- * Return a proposed timeout request id. add_req() determines whether
- * or not the proposed one is used. If it's not suitable, add_req()
- * recalls get_req_cnt(). To reduce the lock contention between the
- * timer and i_untimeout(), the atomic instruction should be used here.
- */
-static timeout_t
-get_req_cnt(void)
-{
- static volatile ulong_t timeout_cnt = 0;
- return ((timeout_t)atomic_inc_ulong_nv(&timeout_cnt));
-}
-
-/*
- * Get the system resolution.
- * Note. currently there is a restriction about the system resolution, and
- * the 10ms tick (the default clock resolution) is only supported now.
- */
-static hrtime_t
-i_get_res(void)
-{
- return ((hrtime_t)10000000); /* 10ms tick only */
-}
-
-/*
- * Return the value for the cog of the timing wheel.
- * TICK_FACTOR is used to gain a finer cog on the clock resolution.
- */
-static hrtime_t
-tw_tick(hrtime_t time)
-{
- return ((time << TICK_FACTOR) / ddi_timer->res);
-}
-
-/*
- * Calculate the expiration time for the timeout request.
- */
-static hrtime_t
-expire_tick(tm_req_t *req)
-{
- return (tw_tick(req->exp_time));
-}
-
-/*
- * Register a timeout request to the timer. This function is used
- * in i_timeout().
- */
-static timeout_t
-add_req(tm_req_t *req)
-{
- timer_tw_t *tid, *tw;
- tm_req_t *next;
- timeout_t id;
-
-retry:
- /*
- * Retrieve a timeout request id. Since i_timeout() needs to return
- * a non-zero value, re-try if the zero is gotten.
- */
- if ((id = get_req_cnt()) == 0)
- id = get_req_cnt();
-
- /*
- * Check if the id is not used yet. Since the framework now deals
- * with the periodic timeout requests, we cannot assume the id
- * allocated (long) before doesn't exist any more when it will
- * be re-assigned again (especially on 32bit) but need to handle
- * this case to solve the conflicts. If it's used already, retry
- * another.
- */
- tid = &ddi_timer->idhash[TM_HASH((uintptr_t)id)];
- mutex_enter(&tid->lock);
- for (next = list_head(&tid->req); next != NULL;
- next = list_next(&tid->req, next)) {
- if (next->id == id) {
- mutex_exit(&tid->lock);
- goto retry;
- }
- }
- /* Nobody uses this id yet */
- req->id = id;
-
- /*
- * Register this request to the timer.
- * The list operation must be list_insert_head().
- * Other operations can degrade performance.
- */
- list_insert_head(&tid->req, req);
- mutex_exit(&tid->lock);
-
- tw = &ddi_timer->exhash[TM_HASH(expire_tick(req))];
- mutex_enter(&tw->lock);
- /*
- * Other operations than list_insert_head() can
- * degrade performance here.
- */
- list_insert_head(&tw->req, req);
- mutex_exit(&tw->lock);
-
- return (id);
-}
-
-/*
- * Periodic timeout requests cannot be removed until they are canceled
- * explicitly. Until then, they need to be re-registerd after they are
- * fired. transfer_req() re-registers the requests for the next fires.
- * Note. transfer_req() sends the cv_signal to timeout_execute(), which
- * runs in interrupt context. Make sure this function will not be blocked,
- * otherwise the deadlock situation can occur.
- */
-static void
-transfer_req(tm_req_t *req, timer_tw_t *tw)
-{
- timer_tw_t *new_tw;
- hrtime_t curr_time;
- ASSERT(tw && MUTEX_HELD(&tw->lock));
-
- /* Calculate the next expiration time by interval */
- req->exp_time += req->interval;
- curr_time = gethrtime();
-
- /*
- * If a long time (more than 1 clock resolution) has already
- * passed for some reason (e.g. debugger or high interrupt),
- * round up the next expiration to the appropriate one
- * since this request is periodic and never catches with it.
- */
- if (curr_time - req->exp_time >= ddi_timer->res) {
- req->exp_time = roundup(curr_time + req->interval,
- ddi_timer->res);
- }
-
- /*
- * Re-register this request.
- * Note. since it is guaranteed that the timer is invoked on only
- * one CPU at any time (by the cyclic subsystem), a deadlock
- * cannot occur regardless of the lock order here.
- */
- new_tw = &ddi_timer->exhash[TM_HASH(expire_tick(req))];
-
- /*
- * If it's on the timer cog already, there is nothing
- * to do. Just return.
- */
- if (new_tw == tw)
- return;
-
- /* Remove this request from the timer */
- list_remove(&tw->req, req);
-
- /* Re-register this request to the timer */
- mutex_enter(&new_tw->lock);
-
- /*
- * Other operations than list_insert_head() can
- * degrade performance here.
- */
- list_insert_head(&new_tw->req, req);
- mutex_exit(&new_tw->lock);
-
- /*
- * Set the TM_TRANSFER flag and notify the request is transfered
- * completely. This prevents a race in the case that this request
- * is serviced on another CPU already.
- */
- mutex_enter(&req->lock);
- req->flags |= TM_TRANSFER;
- cv_signal(&req->cv);
- mutex_exit(&req->lock);
-}
-
-/*
- * Execute timeout requests.
- * Note. since timeout_execute() can run in interrupt context and block
- * on condition variables, there are restrictions on the timer code that
- * signals these condition variables (see i_untimeout(), transfer_req(),
- * and condvar(9F)). Functions that signal these cvs must ensure that
- * they will not be blocked (for memory allocations or any other reason)
- * since condition variables don't support priority inheritance.
- */
-static void
-timeout_execute(void *arg)
-{
- tm_req_t *req = (tm_req_t *)arg;
- ASSERT(req->flags & TM_INVOKING && !(req->flags & TM_EXECUTING));
-
- for (;;) {
- /*
- * Check if this request is canceled. If it's canceled, do not
- * execute this request.
- */
- mutex_enter(&req->lock);
- if (!(req->flags & TM_CANCEL)) {
- /*
- * Set the current thread to prevent a dead lock
- * situation in case that this timeout request is
- * canceled in the handler being invoked now.
- * (this doesn't violate the spec) Set TM_EXECUTING
- * to show this handler is invoked soon.
- */
- req->h_thread = curthread;
- req->flags |= TM_EXECUTING;
- mutex_exit(&req->lock);
-
- /* The handler is invoked without holding any locks */
- (*req->handler)(req->arg);
-
- mutex_enter(&req->lock);
- }
-
- /*
- * Check if this request is canceled or not. If not, prepare
- * for the next fire.
- */
- if (req->flags & TM_CANCEL) {
- timer_tw_t *tw;
- /*
- * Wait until the timer finishes all things for
- * this request.
- */
- while (!(req->flags & TM_TRANSFER))
- cv_wait(&req->cv, &req->lock);
- mutex_exit(&req->lock);
- ASSERT(req->flags & TM_TRANSFER);
-
- /* Remove this request from the timer */
- tw = &ddi_timer->exhash[TM_HASH(expire_tick(req))];
- mutex_enter(&tw->lock);
- list_remove(&tw->req, req);
- mutex_exit(&tw->lock);
-
- /* Free this request */
- kmem_cache_free(req_cache, req);
- return;
- }
- ASSERT(req->flags & TM_EXECUTING);
-
- /*
- * TM_EXECUTING must be set at this point.
- * Unset the flag.
- */
- req->flags &= ~(TM_EXECUTING | TM_TRANSFER);
-
- /*
- * Decrease the request cnt. The reqest cnt shows
- * how many times this request is executed now.
- * If this counter becomes the zero, drop TM_INVOKING
- * to show there is no requests to do now.
- */
- req->cnt--;
- if (req->cnt == 0) {
- req->flags &= ~TM_INVOKING;
- mutex_exit(&req->lock);
- return;
- }
- mutex_exit(&req->lock);
- }
-}
-
-/*
- * Timeout worker thread for processing task queue.
- */
-static void
-timeout_taskq_thread(void *arg)
-{
- _NOTE(ARGUNUSED(arg));
- tm_req_t *kern_req;
- callb_cpr_t cprinfo;
-
- CALLB_CPR_INIT(&cprinfo, &disp_req_lock, callb_generic_cpr,
- "timeout_taskq_thread");
-
- /*
- * This thread is wakened up when a new request is added to
- * the queue. Then pick up all requests and dispatch them
- * via taskq_dispatch().
- */
- for (;;) {
- /*
- * Check the queue and pick up a request if the queue
- * is not NULL.
- */
- mutex_enter(&disp_req_lock);
- while ((kern_req = list_head(&kern_queue)) == NULL) {
- CALLB_CPR_SAFE_BEGIN(&cprinfo);
- cv_wait(&kern_cv, &disp_req_lock);
- CALLB_CPR_SAFE_END(&cprinfo, &disp_req_lock);
- }
- list_remove(&kern_queue, kern_req);
- mutex_exit(&disp_req_lock);
-
- /* Execute the timeout request via the taskq thread */
- (void) taskq_dispatch(tm_taskq, timeout_execute,
- (void *)kern_req, TQ_SLEEP);
- }
-}
-
-/*
- * Dispatch the timeout request based on the level specified.
- * If the level is equal to zero, notify the worker thread to
- * call taskq_dispatch() in kernel context. If the level is bigger
- * than zero, add a software interrupt request to the queue and raise
- * the interrupt level to the specified one.
- */
-static void
-timeout_dispatch(tm_req_t *req)
-{
- int level = req->level;
- extern void sir_on(int);
-
- if (level == TM_IPL_0) {
- /* Add a new request to the tail */
- mutex_enter(&disp_req_lock);
- list_insert_tail(&kern_queue, req);
- mutex_exit(&disp_req_lock);
-
- /*
- * notify the worker thread that this request
- * is newly added to the queue.
- * Note. this cv_signal() can be called after the
- * mutex_lock.
- */
- cv_signal(&kern_cv);
- } else {
- /* Add a new request to the tail */
- mutex_enter(&disp_req_lock);
- list_insert_tail(&intr_queue, req);
-
- /* Issue the software interrupt */
- if (intr_state & TM_INTR_START(level)) {
- /*
- * timer_softintr() is already running; no need to
- * raise a siron. Due to lock protection of
- * the intr_queue and intr_state, we know that
- * timer_softintr() will see the new addition to
- * the intr_queue.
- */
- mutex_exit(&disp_req_lock);
- } else {
- intr_state |= TM_INTR_SET(level);
- mutex_exit(&disp_req_lock);
-
- /* Raise an interrupt to execute timeout requests */
- sir_on(level);
- }
- }
-}
-
-/*
- * Check the software interrupt queue and invoke requests at the specified
- * interrupt level.
- * Note that the queue may change during call so that the disp_req_lock
- * and the intr_state are used to protect it.
- * The software interrupts supported here are up to the level 10. Higher
- * than 10 interrupts cannot be supported.
- */
-void
-timer_softintr(int level)
-{
- tm_req_t *intr_req;
- ASSERT(level >= TM_IPL_1 && level <= TM_IPL_10);
-
- /* Check if we are asked to process the softcall list */
- mutex_enter(&disp_req_lock);
- if (!(intr_state & TM_INTR_SET(level))) {
- mutex_exit(&disp_req_lock);
- return;
- }
-
- /* Notify this software interrupt request will be executed soon */
- intr_state |= TM_INTR_START(level);
- intr_state &= ~TM_INTR_SET(level);
-
- /* loop the link until there is no requests */
- for (intr_req = list_head(&intr_queue); intr_req != NULL;
- /* Nothing */) {
-
- /* Check the interrupt level */
- if (intr_req->level != level) {
- intr_req = list_next(&intr_queue, intr_req);
- continue;
- }
- list_remove(&intr_queue, intr_req);
- mutex_exit(&disp_req_lock);
-
- /* Execute the software interrupt request */
- timeout_execute(intr_req);
-
- mutex_enter(&disp_req_lock);
- /* Restart the loop since new requests might be added */
- intr_req = list_head(&intr_queue);
- }
-
- /* reset the interrupt state */
- intr_state &= ~TM_INTR_START(level);
- mutex_exit(&disp_req_lock);
-}
-
-/*
- * void
- * cyclic_timer(void)
- *
- * Overview
- * cyclic_timer() is a function invoked periodically by the cyclic
- * subsystem.
- *
- * The function calls timeout_invoke() with timeout requests whose
- * expiration time is already reached.
- *
- * Arguments
- * Nothing
- *
- * Return value
- * Nothing
- */
-void
-cyclic_timer(void)
-{
- tm_req_t *req;
- timer_tw_t *tw;
- hrtime_t curr_tick, curr;
-
- /* If the system is suspended, just return */
- if (timer_suspended)
- return;
-
- /* Get the current time */
- timer_hrtime = ddi_timer->tick_time = curr = gethrtime();
- curr_tick = tw_tick(ddi_timer->tick_time);
-
-restart:
- /*
- * Check the timer cogs to see if there are timeout requests
- * who reach the expiration time. Call timeout_invoke() to execute
- * the requests, then.
- */
- while (curr_tick >= ddi_timer->tick) {
- tm_req_t *next;
- tw = &ddi_timer->exhash[TM_HASH(ddi_timer->tick)];
- mutex_enter(&tw->lock);
- for (req = list_head(&tw->req); req != NULL; req = next) {
- next = list_next(&tw->req, req);
- /*
- * If this request is already obsolete, free
- * it here.
- */
- if (req->flags & TM_UTMCOMP) {
- /*
- * Remove this request from the timer,
- * then free it.
- */
- list_remove(&tw->req, req);
- kmem_cache_free(req_cache, req);
- } else if (curr >= req->exp_time) {
- mutex_enter(&req->lock);
- /*
- * Check if this request is canceled, but not
- * being executed now.
- */
- if (req->flags & TM_CANCEL &&
- !(req->flags & TM_INVOKING)) {
- mutex_exit(&req->lock);
- continue;
- }
- /*
- * Record how many times timeout_execute()
- * must be invoked.
- */
- req->cnt++;
- /*
- * Invoke timeout_execute() via taskq or
- * software interrupt.
- */
- if (req->flags & TM_INVOKING) {
- /*
- * If it's already invoked,
- * There is nothing to do.
- */
- mutex_exit(&req->lock);
- } else {
- req->flags |= TM_INVOKING;
- mutex_exit(&req->lock);
- /*
- * Dispatch this timeout request.
- * timeout_dispatch() chooses either
- * a software interrupt or taskq thread
- * based on the level.
- */
- timeout_dispatch(req);
- }
- /*
- * Periodic timeout requests must prepare for
- * the next fire.
- */
- transfer_req(req, tw);
- }
- }
- mutex_exit(&tw->lock);
- ddi_timer->tick++;
- }
-
- /*
- * Check the current time. If we spend some amount of time,
- * double-check if some of the requests reaches the expiration
- * time during the work.
- */
- curr = gethrtime();
- curr_tick = tw_tick(curr);
- if (curr_tick >= ddi_timer->tick) {
- ddi_timer->tick -= 1;
- goto restart;
- }
- /* Adjustment for the next rolling */
- ddi_timer->tick -= 1;
-}
-
-/*
- * void
- * timer_init(void)
- *
- * Overview
- * timer_init() allocates the internal data structures used by
- * i_timeout(), i_untimeout() and the timer.
- *
- * Arguments
- * Nothing
- *
- * Return value
- * Nothing
- *
- * Caller's context
- * timer_init() can be called in kernel context only.
- */
-void
-timer_init(void)
-{
- int i;
-
- /* Create kmem_cache for timeout requests */
- req_cache = kmem_cache_create("timeout_request", sizeof (tm_req_t),
- 0, NULL, NULL, NULL, NULL, NULL, 0);
-
- /* Initialize the timer which is invoked by the cyclic subsystem */
- ddi_timer = kmem_alloc(sizeof (cyc_timer_t), KM_SLEEP);
- ddi_timer->res = nsec_per_tick;
- ddi_timer->tick = tw_tick(gethrtime());
- ddi_timer->tick_time = 0;
-
- /* Initialize the timing wheel */
- bzero((char *)&ddi_timer->idhash[0], TM_HASH_SZ * sizeof (timer_tw_t));
- bzero((char *)&ddi_timer->exhash[0], TM_HASH_SZ * sizeof (timer_tw_t));
-
- for (i = 0; i < TM_HASH_SZ; i++) {
- list_create(&ddi_timer->idhash[i].req, sizeof (tm_req_t),
- offsetof(tm_req_t, id_req));
- mutex_init(&ddi_timer->idhash[i].lock, NULL, MUTEX_ADAPTIVE,
- NULL);
-
- list_create(&ddi_timer->exhash[i].req, sizeof (tm_req_t),
- offsetof(tm_req_t, ex_req));
- mutex_init(&ddi_timer->exhash[i].lock, NULL, MUTEX_ADAPTIVE,
- NULL);
- }
-
- /* Create a taskq thread pool */
- tm_taskq = taskq_create_instance("timeout_taskq", 0,
- timer_taskq_num, MAXCLSYSPRI,
- timer_taskq_min_num, timer_taskq_max_num,
- TASKQ_PREPOPULATE | TASKQ_CPR_SAFE);
-
- /*
- * Initialize the taskq queue which is dedicated to this timeout
- * interface/timer.
- */
- list_create(&kern_queue, sizeof (tm_req_t),
- offsetof(tm_req_t, disp_req));
-
- /* Create a worker thread to dispatch the taskq thread */
- tm_work_thread = thread_create(NULL, 0, timeout_taskq_thread, NULL,
- 0, &p0, TS_RUN, MAXCLSYSPRI);
-
- /*
- * Initialize the software interrupt queue which is dedicated to
- * this timeout interface/timer.
- */
- list_create(&intr_queue, sizeof (tm_req_t),
- offsetof(tm_req_t, disp_req));
-
- /*
- * Initialize the mutex lock used for both of kern_queue and
- * intr_queue.
- */
- mutex_init(&disp_req_lock, NULL, MUTEX_ADAPTIVE, NULL);
- cv_init(&kern_cv, NULL, CV_DEFAULT, NULL);
-
- /* Register the callback handler for the system suspend/resume */
- (void) callb_add(timer_cpr_callb, 0, CB_CL_CPR_CALLOUT, "cyclicTimer");
-}
-
-/*
- * timeout_t
- * i_timeout(void (*func)(void *), void *arg, hrtime_t interval,
- * int level, int flags)
- *
- * Overview
- * i_timeout() is an internal function scheduling the passed function
- * to be invoked in the interval in nanoseconds. The callback function
- * keeps invoked until the request is explicitly canceled by i_untimeout().
- * This function is used for ddi_periodic_add(9F).
- *
- * Arguments
- *
- * func: the callback function
- * the callback function will be invoked in kernel context if
- * the level passed is the zero. Otherwise be invoked in interrupt
- * context at the specified level by the argument "level".
- *
- * Note that It's guaranteed by the cyclic subsystem that the
- * function is invoked on the only one CPU and is never executed
- * simultaneously even on MP system.
- *
- * arg: the argument passed to the callback function
- *
- * interval: interval time in nanoseconds
- * if the interval is the zero, the timer resolution is used.
- *
- * level : callback interrupt level
- * If the value is 0 (the zero), the callback function is invoked
- * in kernel context. If the value is more than 0 (the zero), but
- * less than or equal to 10, the callback function is invoked in
- * interrupt context at the specified interrupt level.
- * This value must be in range of 0-10.
- *
- * Return value
- * returns a non-zero opaque value (timeout_t) on success.
- *
- * Caller's context
- * i_timeout() can be called in user or kernel context.
- */
-timeout_t
-i_timeout(void (*func)(void *), void *arg, hrtime_t interval, int level)
-{
- hrtime_t start_time = gethrtime(), res;
- tm_req_t *req = NULL;
-
- /* Allocate and initialize the timeout request */
- req = kmem_cache_alloc(req_cache, KM_SLEEP);
- req->handler = func;
- req->arg = arg;
- req->h_thread = NULL;
- req->level = level;
- req->flags = 0;
- req->cnt = 0;
- mutex_init(&req->lock, NULL, MUTEX_ADAPTIVE, NULL);
- cv_init(&req->cv, NULL, CV_DEFAULT, NULL);
-
- /*
- * The resolution must be finer than or equal to
- * the requested interval. If it's not, set the resolution
- * to the interval.
- * Note. There is a restriction currently. Regardless of the
- * clock resolution used here, 10ms is set as the timer resolution.
- * Even on the 1ms resolution timer, the minimum interval is 10ms.
- */
- if ((res = i_get_res()) > interval) {
- uintptr_t pc = (uintptr_t)req->handler;
- ulong_t off;
- cmn_err(CE_WARN,
- "The periodic timeout (handler=%s, interval=%lld) "
- "requests a finer interval than the supported resolution. "
- "It rounds up to %lld\n", kobj_getsymname(pc, &off),
- interval, res);
- interval = res;
- }
-
- /*
- * If the specified interval is already multiples of
- * the resolution, use it as is. Otherwise, it rounds
- * up to multiples of the timer resolution.
- */
- req->interval = roundup(interval, i_get_res());
-
- /*
- * For the periodic timeout requests, the first expiration time will
- * be adjusted to the timer tick edge to take advantage of the cyclic
- * subsystem. In that case, the first fire is likely not an expected
- * one, but the fires later can be more accurate due to this.
- */
- req->exp_time = roundup(start_time + req->interval, i_get_res());
-
- /* Add the request to the timer */
- return (add_req(req));
-}
-
-/*
- * void
- * i_untimeout(timeout_t req)
- *
- * Overview
- * i_untimeout() is an internal function canceling the i_timeout()
- * request previously issued.
- * This function is used for ddi_periodic_delete(9F).
- *
- * Argument
- * req: timeout_t opaque value i_timeout() returned previously.
- *
- * Return value
- * Nothing.
- *
- * Caller's context
- * i_untimeout() can be called in user, kernel or interrupt context.
- * It cannot be called in high interrupt context.
- *
- * Note. This function is used by ddi_periodic_delete(), which cannot
- * be called in interrupt context. As a result, this function is called
- * in user or kernel context only in practice.
- */
-void
-i_untimeout(timeout_t timeout_req)
-{
- timer_tw_t *tid;
- tm_req_t *req;
- timeout_t id;
-
- /* Retrieve the id for this timeout request */
- id = (timeout_t)timeout_req;
- tid = &ddi_timer->idhash[TM_HASH((uintptr_t)id)];
-
- mutex_enter(&tid->lock);
- for (req = list_head(&tid->req); req != NULL;
- req = list_next(&tid->req, req)) {
- if (req->id == id)
- break;
- }
- if (req == NULL) {
- /* There is no requests with this id after all */
- mutex_exit(&tid->lock);
- return;
- }
- mutex_enter(&req->lock);
-
- /* Unregister this request first */
- list_remove(&tid->req, req);
-
- /* Notify that this request is canceled */
- req->flags |= TM_CANCEL;
-
- /* Check if the handler is invoked */
- if (req->flags & TM_INVOKING) {
- /*
- * This request will be removed by timeout_execute() later,
- * so that there is no extra thing to do any more.
- */
- mutex_exit(&req->lock);
- mutex_exit(&tid->lock);
- return;
- }
- mutex_exit(&req->lock);
- mutex_exit(&tid->lock);
-
- /*
- * Notify untimeout() is about to be finished, and this request
- * can be freed.
- */
- atomic_or_uint(&req->flags, TM_UTMCOMP);
-}
diff --git a/usr/src/uts/common/os/main.c b/usr/src/uts/common/os/main.c
index 27aeac594d..7afc1cfe00 100644
--- a/usr/src/uts/common/os/main.c
+++ b/usr/src/uts/common/os/main.c
@@ -26,6 +26,10 @@
/* Copyright (c) 1988 AT&T */
/* All Rights Reserved */
+/*
+ * Copyright (c) 2013, Joyent, Inc. All rights reserved.
+ */
+
#include <sys/types.h>
#include <sys/param.h>
#include <sys/sysmacros.h>
@@ -53,7 +57,7 @@
#include <sys/modctl.h>
#include <sys/vm.h>
#include <sys/callb.h>
-#include <sys/ddi_timer.h>
+#include <sys/ddi_periodic.h>
#include <sys/kmem.h>
#include <sys/vmem.h>
#include <sys/cpuvar.h>
@@ -415,7 +419,7 @@ main(void)
segkmem_gc();
callb_init();
cbe_init_pre(); /* x86 must initialize gethrtimef before timer_init */
- timer_init(); /* timer must be initialized before cyclic starts */
+ ddi_periodic_init();
cbe_init();
callout_init(); /* callout table MUST be init'd after cyclics */
clock_tick_init_pre();
diff --git a/usr/src/uts/common/os/sunddi.c b/usr/src/uts/common/os/sunddi.c
index 8daea07d18..d30a9b1c2e 100644
--- a/usr/src/uts/common/os/sunddi.c
+++ b/usr/src/uts/common/os/sunddi.c
@@ -58,7 +58,7 @@
#include <sys/conf.h>
#include <sys/ddi_impldefs.h> /* include implementation structure defs */
#include <sys/ndi_impldefs.h> /* include prototypes */
-#include <sys/ddi_timer.h>
+#include <sys/ddi_periodic.h>
#include <sys/hwconf.h>
#include <sys/pathname.h>
#include <sys/modctl.h>
diff --git a/usr/src/uts/common/sys/Makefile b/usr/src/uts/common/sys/Makefile
index 50846d0cb3..36f653216d 100644
--- a/usr/src/uts/common/sys/Makefile
+++ b/usr/src/uts/common/sys/Makefile
@@ -20,7 +20,7 @@
#
#
# Copyright (c) 1989, 2010, Oracle and/or its affiliates. All rights reserved.
-# Copyright 2012, Joyent, Inc. All rights reserved.
+# Copyright 2013, Joyent, Inc. All rights reserved.
#
include $(SRC)/uts/Makefile.uts
@@ -157,7 +157,7 @@ CHKHDRS= \
ddi_impldefs.h \
ddi_implfuncs.h \
ddi_obsolete.h \
- ddi_timer.h \
+ ddi_periodic.h \
ddidevmap.h \
ddidmareq.h \
ddimapreq.h \
diff --git a/usr/src/uts/common/sys/ddi_periodic.h b/usr/src/uts/common/sys/ddi_periodic.h
new file mode 100644
index 0000000000..0af46e5c01
--- /dev/null
+++ b/usr/src/uts/common/sys/ddi_periodic.h
@@ -0,0 +1,78 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source. A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+/*
+ * Copyright (c) 2013, Joyent, Inc. All rights reserved.
+ */
+
+#ifndef _SYS_DDI_PERIODIC_H
+#define _SYS_DDI_PERIODIC_H
+
+#include <sys/list.h>
+#include <sys/taskq_impl.h>
+#include <sys/cyclic.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef _KERNEL
+
+/*
+ * Opaque handle type for i_timeout() and i_untimeout().
+ */
+typedef struct __timeout *timeout_t;
+
+typedef enum ddi_periodic_flags {
+ DPF_DISPATCHED = 0x01,
+ DPF_EXECUTING = 0x02,
+ DPF_CANCELLED = 0x04
+} ddi_periodic_flags_t;
+
+/*
+ * Each instance of this structure represents a single periodic handler
+ * registered through ddi_periodic_add(9F).
+ */
+typedef struct ddi_periodic_impl {
+ struct list_node dpr_link; /* protected by periodics_lock */
+ struct list_node dpr_softint_link; /* only used when DPF_DISPATCHED */
+ id_t dpr_id;
+ hrtime_t dpr_interval;
+
+ kmutex_t dpr_lock;
+ kcondvar_t dpr_cv;
+ ddi_periodic_flags_t dpr_flags;
+ uint_t dpr_level; /* 0 <= dpr_level <= 10 */
+ taskq_ent_t dpr_taskq_ent; /* only used for level of 0 */
+ uint64_t dpr_fire_count;
+ kthread_t *dpr_thread;
+
+ cyclic_id_t dpr_cyclic_id;
+
+ void (*dpr_handler)(void *);
+ void *dpr_arg;
+} ddi_periodic_impl_t;
+
+/*
+ * Internal implementation functions for the DDI periodic interface.
+ */
+void ddi_periodic_init(void);
+void ddi_periodic_fini(void);
+void ddi_periodic_softintr(int level);
+timeout_t i_timeout(void (*)(void *), void *, hrtime_t, int);
+void i_untimeout(timeout_t);
+
+#endif /* _KERNEL */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _SYS_DDI_PERIODIC_H */
diff --git a/usr/src/uts/common/sys/ddi_timer.h b/usr/src/uts/common/sys/ddi_timer.h
deleted file mode 100644
index 425afb1b8b..0000000000
--- a/usr/src/uts/common/sys/ddi_timer.h
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * 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 2009 Sun Microsystems, Inc. All rights reserved.
- * Use is subject to license terms.
- */
-
-#ifndef _SYS_DDI_TIMER_H
-#define _SYS_DDI_TIMER_H
-
-#include <sys/list.h>
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#ifdef _KERNEL
-
-/*
- * Used by the new timeout functions
- */
-typedef struct __timeout *timeout_t;
-
-/*
- * Forward declarations.
- */
-struct cyc_timer;
-struct tm_req;
-
-/*
- * Timing wheel cog.
- * Each cog has a timeout request queue which is guarded by the lock
- * here.
- */
-typedef struct timer_tw {
- list_t req; /* timeout request queue */
- kmutex_t lock; /* lock for this queue */
-} timer_tw_t;
-
-/*
- * Timer based on the cyclic subsystem.
- * For each resolution, this timer structure should be allocated.
- * Note. currently only one timer is used for periodic timeout requests,
- * which is based on the system clock resolution.
- */
-typedef struct cyc_timer {
- hrtime_t res; /* this cyclic resolution */
- hrtime_t tick; /* tick of this cyclic */
- hrtime_t tick_time; /* current time on this timer */
-/*
- * The hash size might need to be tuned if the lock contention is
- * observed. So far the current size (1024) is sufficient though.
- */
-#define TM_HASH_SZ (1024) /* must be power of 2 */
-#define TM_HASH(x) ((x) & (TM_HASH_SZ -1))
- timer_tw_t idhash[TM_HASH_SZ]; /* ID hash */
- timer_tw_t exhash[TM_HASH_SZ]; /* expiration time hash */
-} cyc_timer_t;
-
-/*
- * This value determines how many requests within 10ms can be allocated to
- * different slots. This is an exponential number powered by 2.
- * This value should be tuned with the hash size.
- * Note. This value is fixed now, but can be adjusted by checking the number
- * of CPUs when the timer structure is allocated.
- */
-#define TICK_FACTOR (3)
-
-/*
- * Timer request.
- */
-typedef struct tm_req {
- struct list_node id_req; /* request on ID hash */
- struct list_node ex_req; /* request on expire hash */
- struct list_node disp_req; /* request on dispatch queue */
- hrtime_t interval; /* interval this request needs */
- hrtime_t exp_time; /* time when the request executes */
- void (*handler)(void *); /* timeout handler */
- void *arg; /* timeout argument */
- kthread_t *h_thread; /* handler thread */
- kmutex_t lock; /* lock for setting counter and flag */
- kcondvar_t cv; /* condition variable against the lock */
- timeout_t id; /* this request id */
- int level; /* interrupt level */
- volatile uint_t flags; /* flags passed to ddi_timeout() */
- /*
- * State flags
- * These are used internally.
- */
-#define TM_INVOKING 0x00000001 /* cyclic is invoked now */
-#define TM_EXECUTING 0x00000002 /* timeout is executed now */
-#define TM_CANCEL 0x00000004 /* request is canceled */
-#define TM_TRANSFER 0x00000008 /* request is transfered */
-#define TM_UTMCOMP 0x00000040 /* untimeout is complete */
- uint_t cnt; /* invoke counter */
-} tm_req_t;
-
-/*
- * Software interrupt intr_state:
- *
- * 31 16 15 0
- * +------------------+------------------+
- * | interrupt start | interrupt set |
- * +------------------+------------------+
- *
- * Note. This structure can accomodate interrupts up to the level 15,
- * but supported interrupts are up to the level 10 in practice because
- * of the ddi timer restriction.
- */
-#define TM_INTR_SET(l) (1 << (l))
-#define TM_INTR_START(l) (1 << ((l) + 16))
-
-/*
- * internal functions for the ddi timeout
- */
-void timer_init(void);
-void cyclic_timer(void);
-void timer_softintr(int);
-timeout_t i_timeout(void (*)(void *), void *, hrtime_t, int);
-void i_untimeout(timeout_t);
-
-#endif /* _KERNEL */
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif /* _SYS_DDI_TIMER_H */
diff --git a/usr/src/uts/common/syscall/uadmin.c b/usr/src/uts/common/syscall/uadmin.c
index dbff1b637c..68aa1a95f5 100644
--- a/usr/src/uts/common/syscall/uadmin.c
+++ b/usr/src/uts/common/syscall/uadmin.c
@@ -22,7 +22,7 @@
/*
* Copyright 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
- * Copyright 2011 Joyent, Inc. All rights reserved.
+ * Copyright 2013 Joyent, Inc. All rights reserved.
*/
#include <sys/param.h>
@@ -48,6 +48,7 @@
#include <sys/cmn_err.h>
#include <sys/panic.h>
#include <sys/ddi.h>
+#include <sys/ddi_periodic.h>
#include <sys/sunddi.h>
#include <sys/policy.h>
#include <sys/zone.h>
@@ -285,6 +286,12 @@ kadmin(int cmd, int fcn, void *mdep, cred_t *credp)
(void) VFS_MOUNTROOT(rootvfs, ROOT_UNMOUNT);
vfs_syncall();
+ /*
+ * Check for (and unregister) any DDI periodic handlers that
+ * still exist, as they most likely constitute resource leaks:
+ */
+ ddi_periodic_fini();
+
dump_ereports();
dump_messages();
diff --git a/usr/src/uts/i86pc/io/apix/apix.c b/usr/src/uts/i86pc/io/apix/apix.c
index 1e4b966576..0182dd2ec2 100644
--- a/usr/src/uts/i86pc/io/apix/apix.c
+++ b/usr/src/uts/i86pc/io/apix/apix.c
@@ -27,7 +27,7 @@
* All rights reserved.
*/
/*
- * Copyright (c) 2012, Joyent, Inc. All rights reserved.
+ * Copyright (c) 2013, Joyent, Inc. All rights reserved.
*/
/*
@@ -75,6 +75,7 @@
#include <sys/pci_intr_lib.h>
#include <sys/spl.h>
#include <sys/clock.h>
+#include <sys/cyclic.h>
#include <sys/dditypes.h>
#include <sys/sunddi.h>
#include <sys/x_call.h>
@@ -1072,6 +1073,9 @@ apix_post_cyclic_setup(void *arg)
{
UNREFERENCED_1PARAMETER(arg);
+ cyc_handler_t cyh;
+ cyc_time_t cyt;
+
/* cpu_lock is held */
/* set up a periodic handler for intr redistribution */
@@ -1084,12 +1088,19 @@ apix_post_cyclic_setup(void *arg)
/*
* Register a periodical handler for the redistribution processing.
- * On X86, CY_LOW_LEVEL is mapped to the level 2 interrupt, so
- * DDI_IPL_2 should be passed to ddi_periodic_add() here.
+ * Though we would generally prefer to use the DDI interface for
+ * periodic handler invocation, ddi_periodic_add(9F), we are
+ * unfortunately already holding cpu_lock, which ddi_periodic_add will
+ * attempt to take for us. Thus, we add our own cyclic directly:
*/
- apic_periodic_id = ddi_periodic_add(
- (void (*)(void *))apix_redistribute_compute, NULL,
- apic_redistribute_sample_interval, DDI_IPL_2);
+ cyh.cyh_func = (void (*)(void *))apix_redistribute_compute;
+ cyh.cyh_arg = NULL;
+ cyh.cyh_level = CY_LOW_LEVEL;
+
+ cyt.cyt_when = 0;
+ cyt.cyt_interval = apic_redistribute_sample_interval;
+
+ apic_cyclic_id = cyclic_add(&cyh, &cyt);
}
/*
diff --git a/usr/src/uts/i86pc/io/pcplusmp/apic.c b/usr/src/uts/i86pc/io/pcplusmp/apic.c
index 843595fe64..6b5646b745 100644
--- a/usr/src/uts/i86pc/io/pcplusmp/apic.c
+++ b/usr/src/uts/i86pc/io/pcplusmp/apic.c
@@ -26,6 +26,9 @@
* Copyright (c) 2010, Intel Corporation.
* All rights reserved.
*/
+/*
+ * Copyright (c) 2013, Joyent, Inc. All rights reserved.
+ */
/*
* To understand how the pcplusmp module interacts with the interrupt subsystem
@@ -73,6 +76,7 @@
#include <sys/pci_intr_lib.h>
#include <sys/spl.h>
#include <sys/clock.h>
+#include <sys/cyclic.h>
#include <sys/dditypes.h>
#include <sys/sunddi.h>
#include <sys/x_call.h>
@@ -994,6 +998,10 @@ static void
apic_post_cyclic_setup(void *arg)
{
_NOTE(ARGUNUSED(arg))
+
+ cyc_handler_t cyh;
+ cyc_time_t cyt;
+
/* cpu_lock is held */
/* set up a periodic handler for intr redistribution */
@@ -1003,14 +1011,22 @@ _NOTE(ARGUNUSED(arg))
*/
if (!apic_oneshot)
return;
+
/*
* Register a periodical handler for the redistribution processing.
- * On X86, CY_LOW_LEVEL is mapped to the level 2 interrupt, so
- * DDI_IPL_2 should be passed to ddi_periodic_add() here.
+ * Though we would generally prefer to use the DDI interface for
+ * periodic handler invocation, ddi_periodic_add(9F), we are
+ * unfortunately already holding cpu_lock, which ddi_periodic_add will
+ * attempt to take for us. Thus, we add our own cyclic directly:
*/
- apic_periodic_id = ddi_periodic_add(
- (void (*)(void *))apic_redistribute_compute, NULL,
- apic_redistribute_sample_interval, DDI_IPL_2);
+ cyh.cyh_func = (void (*)(void *))apic_redistribute_compute;
+ cyh.cyh_arg = NULL;
+ cyh.cyh_level = CY_LOW_LEVEL;
+
+ cyt.cyt_when = 0;
+ cyt.cyt_interval = apic_redistribute_sample_interval;
+
+ apic_cyclic_id = cyclic_add(&cyh, &cyt);
}
static void
diff --git a/usr/src/uts/i86pc/io/pcplusmp/apic_common.c b/usr/src/uts/i86pc/io/pcplusmp/apic_common.c
index 0cc45ff4e0..12368abf89 100644
--- a/usr/src/uts/i86pc/io/pcplusmp/apic_common.c
+++ b/usr/src/uts/i86pc/io/pcplusmp/apic_common.c
@@ -22,6 +22,9 @@
/*
* Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
*/
+/*
+ * Copyright (c) 2013, Joyent, Inc. All rights reserved.
+ */
/*
* PSMI 1.1 extensions are supported only in 2.6 and later versions.
@@ -1351,7 +1354,7 @@ restart_sitka_bmc:
}
-ddi_periodic_t apic_periodic_id;
+cyclic_id_t apic_cyclic_id;
/*
* The following functions are in the platform specific file so that they
diff --git a/usr/src/uts/i86pc/os/startup.c b/usr/src/uts/i86pc/os/startup.c
index 0fa6290b83..eed0c80bef 100644
--- a/usr/src/uts/i86pc/os/startup.c
+++ b/usr/src/uts/i86pc/os/startup.c
@@ -118,7 +118,7 @@
#include <sys/smbios.h>
#include <sys/debug_info.h>
#include <sys/bootinfo.h>
-#include <sys/ddi_timer.h>
+#include <sys/ddi_periodic.h>
#include <sys/systeminfo.h>
#include <sys/multiboot.h>
@@ -2245,14 +2245,13 @@ startup_end(void)
"softlevel1", NULL, NULL); /* XXX to be moved later */
/*
- * Register these software interrupts for ddi timer.
+ * Register software interrupt handlers for ddi_periodic_add(9F).
* Software interrupts up to the level 10 are supported.
*/
for (i = DDI_IPL_1; i <= DDI_IPL_10; i++) {
- char name[sizeof ("timer_softintr") + 2];
- (void) sprintf(name, "timer_softintr%02d", i);
(void) add_avsoftintr((void *)&softlevel_hdl[i-1], i,
- (avfunc)timer_softintr, name, (caddr_t)(uintptr_t)i, NULL);
+ (avfunc)ddi_periodic_softintr, "ddi_periodic",
+ (caddr_t)(uintptr_t)i, NULL);
}
#if !defined(__xpv)
diff --git a/usr/src/uts/i86pc/sys/apic_common.h b/usr/src/uts/i86pc/sys/apic_common.h
index c7b6d925f1..efbf04f1ba 100644
--- a/usr/src/uts/i86pc/sys/apic_common.h
+++ b/usr/src/uts/i86pc/sys/apic_common.h
@@ -21,6 +21,9 @@
/*
* Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
*/
+/*
+ * Copyright (c) 2013, Joyent, Inc. All rights reserved.
+ */
#ifndef _SYS_APIC_COMMON_H
#define _SYS_APIC_COMMON_H
@@ -29,6 +32,7 @@
#include <sys/avintr.h>
#include <sys/privregs.h>
#include <sys/pci.h>
+#include <sys/cyclic.h>
#ifdef __cplusplus
extern "C" {
@@ -159,7 +163,7 @@ extern int apic_stretch_interrupts;
extern int apic_stretch_ISR; /* IPL of 3 matches nothing now */
#endif
-extern ddi_periodic_t apic_periodic_id;
+extern cyclic_id_t apic_cyclic_id;
extern void apic_nmi_intr(caddr_t arg, struct regs *rp);
extern int apic_clkinit();
diff --git a/usr/src/uts/sun4/os/intr.c b/usr/src/uts/sun4/os/intr.c
index cefc121b5c..8036ca5bcb 100644
--- a/usr/src/uts/sun4/os/intr.c
+++ b/usr/src/uts/sun4/os/intr.c
@@ -22,6 +22,9 @@
* Copyright 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
+/*
+ * Copyright (c) 2013, Joyent, Inc. All rights reserved.
+ */
#include <sys/sysmacros.h>
#include <sys/stack.h>
@@ -43,7 +46,7 @@
#include <sys/debug.h>
#include <sys/cyclic.h>
#include <sys/kdi_impl.h>
-#include <sys/ddi_timer.h>
+#include <sys/ddi_periodic.h>
#include <sys/cpu_sgnblk_defs.h>
@@ -119,7 +122,8 @@ intr_init(cpu_t *cp)
* Software interrupts up to the level 10 are supported.
*/
for (i = DDI_IPL_1; i <= DDI_IPL_10; i++) {
- siron_inum[i-1] = add_softintr(i, (softintrfunc)timer_softintr,
+ siron_inum[i - 1] = add_softintr(i,
+ (softintrfunc)ddi_periodic_softintr,
(caddr_t)(uintptr_t)(i), SOFTINT_ST);
}