Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F142510302
D27865.id81746.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
15 KB
Referenced Files
None
Subscribers
None
D27865.id81746.diff
View Options
diff --git a/sys/dev/evdev/cdev.c b/sys/dev/evdev/cdev.c
--- a/sys/dev/evdev/cdev.c
+++ b/sys/dev/evdev/cdev.c
@@ -32,6 +32,7 @@
#include <sys/param.h>
#include <sys/bitstring.h>
#include <sys/conf.h>
+#include <sys/epoch.h>
#include <sys/filio.h>
#include <sys/fcntl.h>
#include <sys/kernel.h>
@@ -125,23 +126,20 @@
mtx_init(&client->ec_buffer_mtx, "evclient", "evdev", MTX_DEF);
knlist_init_mtx(&client->ec_selp.si_note, &client->ec_buffer_mtx);
+ ret = EVDEV_LIST_LOCK_SIG(evdev);
+ if (ret != 0)
+ goto out;
/* Avoid race with evdev_unregister */
- EVDEV_LOCK(evdev);
if (dev->si_drv1 == NULL)
ret = ENODEV;
else
ret = evdev_register_client(evdev, client);
-
- if (ret != 0)
- evdev_revoke_client(client);
- /*
- * Unlock evdev here because non-sleepable lock held
- * while calling devfs_set_cdevpriv upsets WITNESS
- */
- EVDEV_UNLOCK(evdev);
-
- if (!ret)
+ EVDEV_LIST_UNLOCK(evdev);
+out:
+ if (ret == 0)
ret = devfs_set_cdevpriv(client, evdev_dtor);
+ else
+ client->ec_revoked = true;
if (ret != 0) {
debugf(client, "cannot register evdev client");
@@ -156,11 +154,13 @@
{
struct evdev_client *client = (struct evdev_client *)data;
- EVDEV_LOCK(client->ec_evdev);
+ EVDEV_LIST_LOCK(client->ec_evdev);
if (!client->ec_revoked)
evdev_dispose_client(client->ec_evdev, client);
- EVDEV_UNLOCK(client->ec_evdev);
+ EVDEV_LIST_UNLOCK(client->ec_evdev);
+ if (client->ec_evdev->ev_lock_type != EV_LOCK_MTX)
+ epoch_wait_preempt(INPUT_EPOCH);
knlist_clear(&client->ec_selp.si_note, 0);
seldrain(&client->ec_selp);
knlist_destroy(&client->ec_selp.si_note);
@@ -547,12 +547,12 @@
if (*(int *)data != 0)
return (EINVAL);
- EVDEV_LOCK(evdev);
+ EVDEV_LIST_LOCK(evdev);
if (dev->si_drv1 != NULL && !client->ec_revoked) {
evdev_dispose_client(evdev, client);
evdev_revoke_client(client);
}
- EVDEV_UNLOCK(evdev);
+ EVDEV_LIST_UNLOCK(evdev);
return (0);
case EVIOCSCLOCKID:
@@ -717,7 +717,7 @@
evdev_revoke_client(struct evdev_client *client)
{
- EVDEV_LOCK_ASSERT(client->ec_evdev);
+ EVDEV_LIST_LOCK_ASSERT(client->ec_evdev);
client->ec_revoked = true;
}
diff --git a/sys/dev/evdev/evdev.h b/sys/dev/evdev/evdev.h
--- a/sys/dev/evdev/evdev.h
+++ b/sys/dev/evdev/evdev.h
@@ -30,6 +30,7 @@
#define _DEV_EVDEV_EVDEV_H
#include <sys/types.h>
+#include <sys/epoch.h>
#include <sys/kbio.h>
#include <dev/evdev/input.h>
#include <dev/kbd/kbdreg.h>
@@ -87,6 +88,8 @@
* for MT protocol type B reports */
#define EVDEV_FLAG_MT_AUTOREL 0x02 /* Autorelease MT-slots not listed in
* current MT protocol type B report */
+#define EVDEV_FLAG_EXT_EPOCH 0x03 /* evdev_push_* is allways called with
+ * input (global) epoch entered */
#define EVDEV_FLAG_MAX 0x1F
#define EVDEV_FLAG_CNT (EVDEV_FLAG_MAX + 1)
diff --git a/sys/dev/evdev/evdev.c b/sys/dev/evdev/evdev.c
--- a/sys/dev/evdev/evdev.c
+++ b/sys/dev/evdev/evdev.c
@@ -31,12 +31,15 @@
#include <sys/param.h>
#include <sys/bitstring.h>
+#include <sys/ck.h>
#include <sys/conf.h>
+#include <sys/epoch.h>
#include <sys/kdb.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/module.h>
#include <sys/proc.h>
+#include <sys/sx.h>
#include <sys/sysctl.h>
#include <sys/systm.h>
@@ -295,12 +298,14 @@
evdev->ev_shortname, evdev->ev_name, evdev->ev_serial);
/* Initialize internal structures */
- LIST_INIT(&evdev->ev_clients);
+ CK_SLIST_INIT(&evdev->ev_clients);
+ sx_init(&evdev->ev_list_lock, "evsx");
if (evdev_event_supported(evdev, EV_REP) &&
bit_test(evdev->ev_flags, EVDEV_FLAG_SOFTREPEAT)) {
/* Initialize callout */
- callout_init_mtx(&evdev->ev_rep_callout, evdev->ev_lock, 0);
+ callout_init_mtx(&evdev->ev_rep_callout,
+ evdev->ev_state_lock, 0);
if (evdev->ev_rep[REP_DELAY] == 0 &&
evdev->ev_rep[REP_PERIOD] == 0) {
@@ -332,6 +337,8 @@
evdev_sysctl_create(evdev);
bail_out:
+ if (ret != 0)
+ sx_destroy(&evdev->ev_list_lock);
return (ret);
}
@@ -340,8 +347,11 @@
{
int ret;
- evdev->ev_lock_type = EV_LOCK_INTERNAL;
- evdev->ev_lock = &evdev->ev_mtx;
+ if (bit_test(evdev->ev_flags, EVDEV_FLAG_EXT_EPOCH))
+ evdev->ev_lock_type = EV_LOCK_EXT_EPOCH;
+ else
+ evdev->ev_lock_type = EV_LOCK_INTERNAL;
+ evdev->ev_state_lock = &evdev->ev_mtx;
mtx_init(&evdev->ev_mtx, "evmtx", NULL, MTX_DEF);
ret = evdev_register_common(evdev);
@@ -356,7 +366,7 @@
{
evdev->ev_lock_type = EV_LOCK_MTX;
- evdev->ev_lock = mtx;
+ evdev->ev_state_lock = mtx;
return (evdev_register_common(evdev));
}
@@ -370,22 +380,23 @@
sysctl_ctx_free(&evdev->ev_sysctl_ctx);
- EVDEV_LOCK(evdev);
+ EVDEV_LIST_LOCK(evdev);
evdev->ev_cdev->si_drv1 = NULL;
/* Wake up sleepers */
- LIST_FOREACH_SAFE(client, &evdev->ev_clients, ec_link, tmp) {
+ CK_SLIST_FOREACH_SAFE(client, &evdev->ev_clients, ec_link, tmp) {
evdev_revoke_client(client);
evdev_dispose_client(evdev, client);
EVDEV_CLIENT_LOCKQ(client);
evdev_notify_event(client);
EVDEV_CLIENT_UNLOCKQ(client);
}
- EVDEV_UNLOCK(evdev);
+ EVDEV_LIST_UNLOCK(evdev);
- /* destroy_dev can sleep so release lock */
+ /* release lock to avoid deadlock with evdev_dtor */
ret = evdev_cdev_destroy(evdev);
evdev->ev_cdev = NULL;
- if (ret == 0 && evdev->ev_lock_type == EV_LOCK_INTERNAL)
+ sx_destroy(&evdev->ev_list_lock);
+ if (ret == 0 && evdev->ev_lock_type != EV_LOCK_MTX)
mtx_destroy(&evdev->ev_mtx);
evdev_free_absinfo(evdev->ev_absinfo);
@@ -689,7 +700,7 @@
} else {
/* Start/stop callout for evdev repeats */
if (bit_test(evdev->ev_key_states, code) == !*value &&
- !LIST_EMPTY(&evdev->ev_clients)) {
+ !CK_SLIST_EMPTY(&evdev->ev_clients)) {
if (*value == KEY_EVENT_DOWN)
evdev_start_repeat(evdev, code);
else
@@ -817,6 +828,7 @@
evdev_propagate_event(struct evdev_dev *evdev, uint16_t type, uint16_t code,
int32_t value)
{
+ struct epoch_tracker et;
struct evdev_client *client;
debugf(evdev, "%s pushed event %d/%d/%d",
@@ -825,7 +837,14 @@
EVDEV_LOCK_ASSERT(evdev);
/* Propagate event through all clients */
- LIST_FOREACH(client, &evdev->ev_clients, ec_link) {
+ if (evdev->ev_lock_type == EV_LOCK_INTERNAL)
+ epoch_enter_preempt(INPUT_EPOCH, &et);
+
+ KASSERT(
+ evdev->ev_lock_type == EV_LOCK_MTX || in_epoch(INPUT_EPOCH) != 0,
+ ("Input epoch has not been entered\n"));
+
+ CK_SLIST_FOREACH(client, &evdev->ev_clients, ec_link) {
if (evdev->ev_grabber != NULL && evdev->ev_grabber != client)
continue;
@@ -835,6 +854,8 @@
evdev_notify_event(client);
EVDEV_CLIENT_UNLOCKQ(client);
}
+ if (evdev->ev_lock_type == EV_LOCK_INTERNAL)
+ epoch_exit_preempt(INPUT_EPOCH, &et);
evdev->ev_event_count++;
}
@@ -932,6 +953,7 @@
evdev_inject_event(struct evdev_dev *evdev, uint16_t type, uint16_t code,
int32_t value)
{
+ struct epoch_tracker et;
int ret = 0;
switch (type) {
@@ -962,11 +984,16 @@
case EV_ABS:
case EV_SW:
push:
- if (evdev->ev_lock_type != EV_LOCK_INTERNAL)
+ if (evdev->ev_lock_type == EV_LOCK_MTX)
EVDEV_LOCK(evdev);
+ else if (evdev->ev_lock_type == EV_LOCK_EXT_EPOCH)
+ epoch_enter_preempt(INPUT_EPOCH, &et);
ret = evdev_push_event(evdev, type, code, value);
- if (evdev->ev_lock_type != EV_LOCK_INTERNAL)
+ if (evdev->ev_lock_type == EV_LOCK_MTX)
EVDEV_UNLOCK(evdev);
+ else if (evdev->ev_lock_type == EV_LOCK_EXT_EPOCH)
+ epoch_exit_preempt(INPUT_EPOCH, &et);
+
break;
default:
@@ -983,16 +1010,16 @@
debugf(evdev, "adding new client for device %s", evdev->ev_shortname);
- EVDEV_LOCK_ASSERT(evdev);
+ EVDEV_LIST_LOCK_ASSERT(evdev);
- if (LIST_EMPTY(&evdev->ev_clients) && evdev->ev_methods != NULL &&
+ if (CK_SLIST_EMPTY(&evdev->ev_clients) && evdev->ev_methods != NULL &&
evdev->ev_methods->ev_open != NULL) {
debugf(evdev, "calling ev_open() on device %s",
evdev->ev_shortname);
ret = evdev->ev_methods->ev_open(evdev);
}
if (ret == 0)
- LIST_INSERT_HEAD(&evdev->ev_clients, client, ec_link);
+ CK_SLIST_INSERT_HEAD(&evdev->ev_clients, client, ec_link);
return (ret);
}
@@ -1001,18 +1028,27 @@
{
debugf(evdev, "removing client for device %s", evdev->ev_shortname);
- EVDEV_LOCK_ASSERT(evdev);
+ EVDEV_LIST_LOCK_ASSERT(evdev);
- LIST_REMOVE(client, ec_link);
- if (LIST_EMPTY(&evdev->ev_clients)) {
+ CK_SLIST_REMOVE(&evdev->ev_clients, client, evdev_client, ec_link);
+ if (CK_SLIST_EMPTY(&evdev->ev_clients)) {
if (evdev->ev_methods != NULL &&
evdev->ev_methods->ev_close != NULL)
(void)evdev->ev_methods->ev_close(evdev);
if (evdev_event_supported(evdev, EV_REP) &&
- bit_test(evdev->ev_flags, EVDEV_FLAG_SOFTREPEAT))
+ bit_test(evdev->ev_flags, EVDEV_FLAG_SOFTREPEAT)) {
+ if (evdev->ev_lock_type != EV_LOCK_MTX)
+ EVDEV_LOCK(evdev);
evdev_stop_repeat(evdev);
+ if (evdev->ev_lock_type != EV_LOCK_MTX)
+ EVDEV_UNLOCK(evdev);
+ }
}
+ if (evdev->ev_lock_type != EV_LOCK_MTX)
+ EVDEV_LOCK(evdev);
evdev_release_client(evdev, client);
+ if (evdev->ev_lock_type != EV_LOCK_MTX)
+ EVDEV_UNLOCK(evdev);
}
int
@@ -1046,10 +1082,15 @@
static void
evdev_repeat_callout(void *arg)
{
+ struct epoch_tracker et;
struct evdev_dev *evdev = (struct evdev_dev *)arg;
+ if (evdev->ev_lock_type == EV_LOCK_EXT_EPOCH)
+ epoch_enter_preempt(INPUT_EPOCH, &et);
evdev_send_event(evdev, EV_KEY, evdev->ev_rep_key, KEY_EVENT_REPEAT);
evdev_send_event(evdev, EV_SYN, SYN_REPORT, 1);
+ if (evdev->ev_lock_type == EV_LOCK_EXT_EPOCH)
+ epoch_exit_preempt(INPUT_EPOCH, &et);
if (evdev->ev_rep[REP_PERIOD])
callout_reset(&evdev->ev_rep_callout,
diff --git a/sys/dev/evdev/evdev_private.h b/sys/dev/evdev/evdev_private.h
--- a/sys/dev/evdev/evdev_private.h
+++ b/sys/dev/evdev/evdev_private.h
@@ -31,12 +31,15 @@
#define _DEV_EVDEV_EVDEV_PRIVATE_H
#include <sys/bitstring.h>
+#include <sys/ck.h>
+#include <sys/epoch.h>
#include <sys/kbio.h>
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/mutex.h>
#include <sys/queue.h>
#include <sys/selinfo.h>
+#include <sys/sx.h>
#include <sys/sysctl.h>
#include <dev/evdev/evdev.h>
@@ -75,10 +78,33 @@
EV_CLOCK_BOOTTIME /* monotonic, suspend-awared */
};
+/*
+ * Locking.
+ *
+ * Internal evdev structures are protected with next locks:
+ * State lock (s) - Internal state. The data it protects is changed
+ * by incoming evdev events and some ioctls.
+ * Client list epoch (l) - Read access to client list.
+ * Client list lock (l) - Write access to client list.
+ * Client queue locks (q) - One lock per client to serialize access to data
+ * available through character device node.
+ *
+ * Depending on evdev_register_() suffix evdev can run in following modes:
+ * 1. Internal epoch. evdev_register(). All locks are internal.
+ * 2. External epoch. Evdev expects to be run under input epoch entered by
+ * parent driver. The mode is enabled with EVDEV_FLAG_EXT_EPOCH flag.
+ * 3. External mutex. evdev_register_mtx(). Evdev uses mutex provided by parent
+ * driver as both "State lock" and "Client list lock". This mode is
+ * deprecated as it causes ev_open and ev_close handlers to be called with
+ * parent driver mutex taken.
+ */
+#define INPUT_EPOCH global_epoch_preempt
+
enum evdev_lock_type
{
- EV_LOCK_INTERNAL = 0, /* Internal evdev mutex */
+ EV_LOCK_INTERNAL = 0, /* Internal epoch */
EV_LOCK_MTX, /* Driver`s mutex */
+ EV_LOCK_EXT_EPOCH, /* External epoch */
};
struct evdev_dev
@@ -89,10 +115,11 @@
struct cdev * ev_cdev;
int ev_unit;
enum evdev_lock_type ev_lock_type;
- struct mtx * ev_lock;
- struct mtx ev_mtx;
+ struct mtx * ev_state_lock; /* State lock */
+ struct mtx ev_mtx; /* Internal state lock */
+ struct sx ev_list_lock; /* Client list lock */
struct input_id ev_id;
- struct evdev_client * ev_grabber;
+ struct evdev_client * ev_grabber; /* (s) */
size_t ev_report_size;
/* Supported features: */
@@ -105,31 +132,31 @@
bitstr_t bit_decl(ev_led_flags, LED_CNT);
bitstr_t bit_decl(ev_snd_flags, SND_CNT);
bitstr_t bit_decl(ev_sw_flags, SW_CNT);
- struct input_absinfo * ev_absinfo;
+ struct input_absinfo * ev_absinfo; /* (s) */
bitstr_t bit_decl(ev_flags, EVDEV_FLAG_CNT);
/* Repeat parameters & callout: */
- int ev_rep[REP_CNT];
- struct callout ev_rep_callout;
- uint16_t ev_rep_key;
+ int ev_rep[REP_CNT]; /* (s) */
+ struct callout ev_rep_callout; /* (s) */
+ uint16_t ev_rep_key; /* (s) */
/* State: */
- bitstr_t bit_decl(ev_key_states, KEY_CNT);
- bitstr_t bit_decl(ev_led_states, LED_CNT);
- bitstr_t bit_decl(ev_snd_states, SND_CNT);
- bitstr_t bit_decl(ev_sw_states, SW_CNT);
- bool ev_report_opened;
+ bitstr_t bit_decl(ev_key_states, KEY_CNT); /* (s) */
+ bitstr_t bit_decl(ev_led_states, LED_CNT); /* (s) */
+ bitstr_t bit_decl(ev_snd_states, SND_CNT); /* (s) */
+ bitstr_t bit_decl(ev_sw_states, SW_CNT); /* (s) */
+ bool ev_report_opened; /* (s) */
/* KDB state: */
bool ev_kdb_active;
bitstr_t bit_decl(ev_kdb_led_states, LED_CNT);
/* Multitouch protocol type B state: */
- struct evdev_mt * ev_mt;
+ struct evdev_mt * ev_mt; /* (s) */
/* Counters: */
- uint64_t ev_event_count;
- uint64_t ev_report_count;
+ uint64_t ev_event_count; /* (s) */
+ uint64_t ev_report_count; /* (s) */
/* Parent driver callbacks: */
const struct evdev_methods * ev_methods;
@@ -139,47 +166,75 @@
struct sysctl_ctx_list ev_sysctl_ctx;
LIST_ENTRY(evdev_dev) ev_link;
- LIST_HEAD(, evdev_client) ev_clients;
+ CK_SLIST_HEAD(, evdev_client) ev_clients; /* (l) */
};
#define SYSTEM_CONSOLE_LOCK &Giant
-#define EVDEV_LOCK(evdev) mtx_lock((evdev)->ev_lock)
-#define EVDEV_UNLOCK(evdev) mtx_unlock((evdev)->ev_lock)
+#define EVDEV_LOCK(evdev) mtx_lock((evdev)->ev_state_lock)
+#define EVDEV_UNLOCK(evdev) mtx_unlock((evdev)->ev_state_lock)
#define EVDEV_LOCK_ASSERT(evdev) do { \
- if ((evdev)->ev_lock != SYSTEM_CONSOLE_LOCK) \
- mtx_assert((evdev)->ev_lock, MA_OWNED); \
+ if ((evdev)->ev_state_lock != SYSTEM_CONSOLE_LOCK) \
+ mtx_assert((evdev)->ev_state_lock, MA_OWNED); \
} while (0)
#define EVDEV_ENTER(evdev) do { \
- if ((evdev)->ev_lock_type == EV_LOCK_INTERNAL) \
+ if ((evdev)->ev_lock_type != EV_LOCK_MTX) \
EVDEV_LOCK(evdev); \
else \
EVDEV_LOCK_ASSERT(evdev); \
} while (0)
#define EVDEV_EXIT(evdev) do { \
- if ((evdev)->ev_lock_type == EV_LOCK_INTERNAL) \
+ if ((evdev)->ev_lock_type != EV_LOCK_MTX) \
+ EVDEV_UNLOCK(evdev); \
+} while (0)
+
+#define EVDEV_LIST_LOCK(evdev) do { \
+ if ((evdev)->ev_lock_type == EV_LOCK_MTX) \
+ EVDEV_LOCK(evdev); \
+ else \
+ sx_xlock(&(evdev)->ev_list_lock); \
+} while (0)
+#define EVDEV_LIST_UNLOCK(evdev) do { \
+ if ((evdev)->ev_lock_type == EV_LOCK_MTX) \
EVDEV_UNLOCK(evdev); \
+ else \
+ sx_unlock(&(evdev)->ev_list_lock); \
} while (0)
+#define EVDEV_LIST_LOCK_ASSERT(evdev) do { \
+ if ((evdev)->ev_lock_type == EV_LOCK_MTX) \
+ EVDEV_LOCK_ASSERT(evdev); \
+ else \
+ sx_assert(&(evdev)->ev_list_lock, MA_OWNED); \
+} while (0)
+static inline int
+EVDEV_LIST_LOCK_SIG(struct evdev_dev *evdev)
+{
+ if (evdev->ev_lock_type == EV_LOCK_MTX) {
+ EVDEV_LOCK(evdev);
+ return (0);
+ }
+ return (sx_xlock_sig(&evdev->ev_list_lock));
+}
struct evdev_client
{
struct evdev_dev * ec_evdev;
- struct mtx ec_buffer_mtx;
+ struct mtx ec_buffer_mtx; /* Client queue lock */
size_t ec_buffer_size;
- size_t ec_buffer_head;
- size_t ec_buffer_tail;
- size_t ec_buffer_ready;
+ size_t ec_buffer_head; /* (q) */
+ size_t ec_buffer_tail; /* (q) */
+ size_t ec_buffer_ready; /* (q) */
enum evdev_clock_id ec_clock_id;
- struct selinfo ec_selp;
+ struct selinfo ec_selp; /* (q) */
struct sigio * ec_sigio;
- bool ec_async;
- bool ec_revoked;
- bool ec_blocked;
- bool ec_selected;
+ bool ec_async; /* (q) */
+ bool ec_revoked; /* (l) */
+ bool ec_blocked; /* (q) */
+ bool ec_selected; /* (q) */
- LIST_ENTRY(evdev_client) ec_link;
+ CK_SLIST_ENTRY(evdev_client) ec_link; /* (l) */
- struct input_event ec_buffer[];
+ struct input_event ec_buffer[]; /* (q) */
};
#define EVDEV_CLIENT_LOCKQ(client) mtx_lock(&(client)->ec_buffer_mtx)
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Wed, Jan 21, 12:36 PM (12 h, 31 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
27803994
Default Alt Text
D27865.id81746.diff (15 KB)
Attached To
Mode
D27865: evdev: Make open(2) and close(3) handlers sleepable.
Attached
Detach File
Event Timeline
Log In to Comment