Changeset View
Changeset View
Standalone View
Standalone View
sys/dev/evdev/evdev.c
Show All 21 Lines | |||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | ||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | ||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | ||||
* SUCH DAMAGE. | * SUCH DAMAGE. | ||||
* | * | ||||
* $FreeBSD$ | * $FreeBSD$ | ||||
*/ | */ | ||||
#include "opt_ddb.h" | |||||
#include "opt_evdev.h" | #include "opt_evdev.h" | ||||
#include <sys/param.h> | #include <sys/param.h> | ||||
#include <sys/bitstring.h> | #include <sys/bitstring.h> | ||||
#include <sys/conf.h> | #include <sys/conf.h> | ||||
#include <sys/kdb.h> | |||||
#include <sys/kernel.h> | #include <sys/kernel.h> | ||||
#include <sys/malloc.h> | #include <sys/malloc.h> | ||||
#include <sys/module.h> | #include <sys/module.h> | ||||
#include <sys/sysctl.h> | #include <sys/sysctl.h> | ||||
#include <sys/systm.h> | #include <sys/systm.h> | ||||
#include <dev/evdev/evdev.h> | #include <dev/evdev/evdev.h> | ||||
#include <dev/evdev/evdev_private.h> | #include <dev/evdev/evdev_private.h> | ||||
Show All 16 Lines | |||||
{ | { | ||||
EV_SKIP_EVENT, /* Event value not changed */ | EV_SKIP_EVENT, /* Event value not changed */ | ||||
EV_REPORT_EVENT, /* Event value changed */ | EV_REPORT_EVENT, /* Event value changed */ | ||||
EV_REPORT_MT_SLOT, /* Event value and MT slot number changed */ | EV_REPORT_MT_SLOT, /* Event value and MT slot number changed */ | ||||
}; | }; | ||||
MALLOC_DEFINE(M_EVDEV, "evdev", "evdev memory"); | MALLOC_DEFINE(M_EVDEV, "evdev", "evdev memory"); | ||||
#ifdef DDB | |||||
#define EVDEV_HZ_DEFAULT 20 | |||||
#else | |||||
#define EVDEV_HZ_DEFAULT 0 | |||||
#endif | |||||
int evdev_rcpt_mask = EVDEV_RCPT_SYSMOUSE | EVDEV_RCPT_KBDMUX; | int evdev_rcpt_mask = EVDEV_RCPT_SYSMOUSE | EVDEV_RCPT_KBDMUX; | ||||
int evdev_sysmouse_t_axis = 0; | int evdev_sysmouse_t_axis = 0; | ||||
static int evdev_hz = EVDEV_HZ_DEFAULT; | |||||
#ifdef EVDEV_SUPPORT | #ifdef EVDEV_SUPPORT | ||||
SYSCTL_NODE(_kern, OID_AUTO, evdev, CTLFLAG_RW, 0, "Evdev args"); | SYSCTL_NODE(_kern, OID_AUTO, evdev, CTLFLAG_RW, 0, "Evdev args"); | ||||
SYSCTL_INT(_kern_evdev, OID_AUTO, rcpt_mask, CTLFLAG_RW, &evdev_rcpt_mask, 0, | SYSCTL_INT(_kern_evdev, OID_AUTO, rcpt_mask, CTLFLAG_RW, &evdev_rcpt_mask, 0, | ||||
"Who is receiving events: bit0 - sysmouse, bit1 - kbdmux, " | "Who is receiving events: bit0 - sysmouse, bit1 - kbdmux, " | ||||
"bit2 - mouse hardware, bit3 - keyboard hardware"); | "bit2 - mouse hardware, bit3 - keyboard hardware"); | ||||
SYSCTL_INT(_kern_evdev, OID_AUTO, sysmouse_t_axis, CTLFLAG_RW, | SYSCTL_INT(_kern_evdev, OID_AUTO, sysmouse_t_axis, CTLFLAG_RW, | ||||
&evdev_sysmouse_t_axis, 0, "Extract T-axis from 0-none, 1-ums, 2-psm"); | &evdev_sysmouse_t_axis, 0, "Extract T-axis from 0-none, 1-ums, 2-psm"); | ||||
/* Setting to 0 disables lockless mode and enables notification on interrupt */ | |||||
SYSCTL_INT(_kern_evdev, OID_AUTO, poll_rate, CTLFLAG_RW, | |||||
&evdev_hz, 0, "Userland notification rate (hz), 0 - disable polling"); | |||||
#endif | #endif | ||||
static void evdev_start_repeat(struct evdev_dev *, uint16_t); | static void evdev_start_repeat(struct evdev_dev *, uint16_t); | ||||
static void evdev_stop_repeat(struct evdev_dev *); | static void evdev_stop_repeat(struct evdev_dev *); | ||||
static void evdev_start_notification(struct evdev_dev *); | |||||
static void evdev_stop_notification(struct evdev_dev *); | |||||
static int evdev_check_event(struct evdev_dev *, uint16_t, uint16_t, int32_t); | static int evdev_check_event(struct evdev_dev *, uint16_t, uint16_t, int32_t); | ||||
static inline void | static inline void | ||||
bit_change(bitstr_t *bitstr, int bit, int value) | bit_change(bitstr_t *bitstr, int bit, int value) | ||||
{ | { | ||||
if (value) | if (value) | ||||
bit_set(bitstr, bit); | bit_set(bitstr, bit); | ||||
else | else | ||||
▲ Show 20 Lines • Show All 113 Lines • ▼ Show 20 Lines | evdev_register_common(struct evdev_dev *evdev) | ||||
debugf(evdev, "%s: registered evdev provider: %s <%s>\n", | debugf(evdev, "%s: registered evdev provider: %s <%s>\n", | ||||
evdev->ev_shortname, evdev->ev_name, evdev->ev_serial); | evdev->ev_shortname, evdev->ev_name, evdev->ev_serial); | ||||
/* Initialize internal structures */ | /* Initialize internal structures */ | ||||
LIST_INIT(&evdev->ev_clients); | LIST_INIT(&evdev->ev_clients); | ||||
if (evdev_event_supported(evdev, EV_REP) && | if (evdev_event_supported(evdev, EV_REP) && | ||||
bit_test(evdev->ev_flags, EVDEV_FLAG_SOFTREPEAT)) { | bit_test(evdev->ev_flags, EVDEV_FLAG_SOFTREPEAT)) { | ||||
/* Initialize callout */ | |||||
callout_init_mtx(&evdev->ev_rep_callout, &evdev->ev_mtx, 0); | |||||
if (evdev->ev_rep[REP_DELAY] == 0 && | if (evdev->ev_rep[REP_DELAY] == 0 && | ||||
evdev->ev_rep[REP_PERIOD] == 0) { | evdev->ev_rep[REP_PERIOD] == 0) { | ||||
/* Supply default values */ | /* Supply default values */ | ||||
evdev->ev_rep[REP_DELAY] = 250; | evdev->ev_rep[REP_DELAY] = 250; | ||||
evdev->ev_rep[REP_PERIOD] = 33; | evdev->ev_rep[REP_PERIOD] = 33; | ||||
} | } | ||||
} | } | ||||
callout_init_mtx(&evdev->ev_rep_callout, evdev->ev_lock, 0); | |||||
callout_init_mtx(&evdev->ev_note_callout, evdev->ev_lock, 0); | |||||
/* Initialize multitouch protocol type B states */ | /* Initialize multitouch protocol type B states */ | ||||
if (bit_test(evdev->ev_abs_flags, ABS_MT_SLOT) && | if (bit_test(evdev->ev_abs_flags, ABS_MT_SLOT) && | ||||
evdev->ev_absinfo != NULL && MAXIMAL_MT_SLOT(evdev) > 0) | evdev->ev_absinfo != NULL && MAXIMAL_MT_SLOT(evdev) > 0) | ||||
evdev_mt_init(evdev); | evdev_mt_init(evdev); | ||||
/* Estimate maximum report size */ | /* Estimate maximum report size */ | ||||
if (evdev->ev_report_size == 0) { | if (evdev->ev_report_size == 0) { | ||||
▲ Show 20 Lines • Show All 43 Lines • ▼ Show 20 Lines | debugf(evdev, "%s: unregistered evdev provider: %s\n", | ||||
evdev->ev_shortname, evdev->ev_name); | evdev->ev_shortname, evdev->ev_name); | ||||
EVDEV_LOCK(evdev); | EVDEV_LOCK(evdev); | ||||
evdev->ev_cdev->si_drv1 = NULL; | evdev->ev_cdev->si_drv1 = NULL; | ||||
/* Wake up sleepers */ | /* Wake up sleepers */ | ||||
LIST_FOREACH(client, &evdev->ev_clients, ec_link) { | LIST_FOREACH(client, &evdev->ev_clients, ec_link) { | ||||
evdev_revoke_client(client); | evdev_revoke_client(client); | ||||
evdev_dispose_client(evdev, client); | evdev_dispose_client(evdev, client); | ||||
if (EVDEV_CLIENT_LOCKQ_EXISTS(client)) | |||||
EVDEV_CLIENT_LOCKQ(client); | EVDEV_CLIENT_LOCKQ(client); | ||||
evdev_notify_event(client); | evdev_notify_event(client); | ||||
if (EVDEV_CLIENT_LOCKQ_EXISTS(client)) | |||||
EVDEV_CLIENT_UNLOCKQ(client); | EVDEV_CLIENT_UNLOCKQ(client); | ||||
} | } | ||||
EVDEV_UNLOCK(evdev); | EVDEV_UNLOCK(evdev); | ||||
/* destroy_dev can sleep so release lock */ | /* destroy_dev can sleep so release lock */ | ||||
ret = evdev_cdev_destroy(evdev); | ret = evdev_cdev_destroy(evdev); | ||||
evdev->ev_cdev = NULL; | evdev->ev_cdev = NULL; | ||||
if (ret == 0 && evdev->ev_lock_type == EV_LOCK_INTERNAL) | if (ret == 0 && evdev->ev_lock_type == EV_LOCK_INTERNAL) | ||||
mtx_destroy(&evdev->ev_mtx); | mtx_destroy(&evdev->ev_mtx); | ||||
▲ Show 20 Lines • Show All 428 Lines • ▼ Show 20 Lines | evdev_propagate_event(struct evdev_dev *evdev, uint16_t type, uint16_t code, | ||||
EVDEV_LOCK_ASSERT(evdev); | EVDEV_LOCK_ASSERT(evdev); | ||||
/* Propagate event through all clients */ | /* Propagate event through all clients */ | ||||
LIST_FOREACH(client, &evdev->ev_clients, ec_link) { | LIST_FOREACH(client, &evdev->ev_clients, ec_link) { | ||||
if (evdev->ev_grabber != NULL && evdev->ev_grabber != client) | if (evdev->ev_grabber != NULL && evdev->ev_grabber != client) | ||||
continue; | continue; | ||||
if (EVDEV_CLIENT_LOCKQ_EXISTS(client)) | |||||
EVDEV_CLIENT_LOCKQ(client); | EVDEV_CLIENT_LOCKQ(client); | ||||
evdev_client_push(client, type, code, value); | evdev_client_push(client, type, code, value); | ||||
if (type == EV_SYN && code == SYN_REPORT) | /* wakeup, selwakeup and knote take internal locks */ | ||||
if (type == EV_SYN && code == SYN_REPORT && !(evdev_hz > 0 && | |||||
bit_test(evdev->ev_flags, EVDEV_FLAG_LOCKLESS))) | |||||
evdev_notify_event(client); | evdev_notify_event(client); | ||||
if (EVDEV_CLIENT_LOCKQ_EXISTS(client)) | |||||
EVDEV_CLIENT_UNLOCKQ(client); | EVDEV_CLIENT_UNLOCKQ(client); | ||||
} | } | ||||
if (type == EV_SYN && code == SYN_REPORT && evdev_hz > 0 && | |||||
bit_test(evdev->ev_flags, EVDEV_FLAG_LOCKLESS)) | |||||
evdev->ev_note = true; | |||||
evdev->ev_event_count++; | evdev->ev_event_count++; | ||||
} | } | ||||
void | void | ||||
evdev_send_event(struct evdev_dev *evdev, uint16_t type, uint16_t code, | evdev_send_event(struct evdev_dev *evdev, uint16_t type, uint16_t code, | ||||
int32_t value) | int32_t value) | ||||
{ | { | ||||
Show All 11 Lines | evdev_send_event(struct evdev_dev *evdev, uint16_t type, uint16_t code, | ||||
case EV_REPORT_EVENT: | case EV_REPORT_EVENT: | ||||
evdev_propagate_event(evdev, type, code, value); | evdev_propagate_event(evdev, type, code, value); | ||||
/* FALLTHROUGH */ | /* FALLTHROUGH */ | ||||
case EV_SKIP_EVENT: | case EV_SKIP_EVENT: | ||||
break; | break; | ||||
} | } | ||||
} | } | ||||
void | |||||
evdev_restore_after_kdb(struct evdev_dev *evdev) | |||||
{ | |||||
int code; | |||||
EVDEV_LOCK_ASSERT(evdev); | |||||
/* Report postponed leds */ | |||||
for (code = 0; code < LED_CNT; code++) | |||||
if (bit_test(evdev->ev_kdb_led_states, code)) | |||||
evdev_send_event(evdev, EV_LED, code, | |||||
!bit_test(evdev->ev_led_states, code)); | |||||
bit_nclear(evdev->ev_kdb_led_states, 0, LED_MAX); | |||||
/* Release stuck keys (CTRL + ALT + ESC) */ | |||||
evdev_stop_repeat(evdev); | |||||
for (code = 0; code < KEY_CNT; code++) { | |||||
if (bit_test(evdev->ev_key_states, code)) { | |||||
evdev_send_event(evdev, EV_KEY, code, KEY_EVENT_UP); | |||||
evdev_send_event(evdev, EV_SYN, SYN_REPORT, 1); | |||||
} | |||||
} | |||||
} | |||||
int | int | ||||
evdev_push_event(struct evdev_dev *evdev, uint16_t type, uint16_t code, | evdev_push_event(struct evdev_dev *evdev, uint16_t type, uint16_t code, | ||||
int32_t value) | int32_t value) | ||||
{ | { | ||||
if (evdev_check_event(evdev, type, code, value) != 0) | if (evdev_check_event(evdev, type, code, value) != 0) | ||||
return (EINVAL); | return (EINVAL); | ||||
/* | |||||
* Discard all but LEDs kdb events as unrelated to userspace. | |||||
* Aggregate LED updates and postpone reporting until kdb deactivation. | |||||
*/ | |||||
if (kdb_active) { | |||||
evdev->ev_kdb_active = true; | |||||
if (type == EV_LED) | |||||
bit_set(evdev->ev_kdb_led_states, | |||||
bit_test(evdev->ev_led_states, code) != value); | |||||
return (0); | |||||
} | |||||
EVDEV_ENTER(evdev); | EVDEV_ENTER(evdev); | ||||
/* | |||||
* Fix evdev state corrupted with discarding of kdb events. | |||||
* Usually fires on ENTER key release after 'continue' have been typed. | |||||
*/ | |||||
if (evdev->ev_kdb_active) { | |||||
evdev->ev_kdb_active = false; | |||||
evdev_restore_after_kdb(evdev); | |||||
} | |||||
evdev_modify_event(evdev, type, code, &value); | evdev_modify_event(evdev, type, code, &value); | ||||
if (type == EV_SYN && code == SYN_REPORT && | if (type == EV_SYN && code == SYN_REPORT && | ||||
bit_test(evdev->ev_flags, EVDEV_FLAG_MT_AUTOREL)) | bit_test(evdev->ev_flags, EVDEV_FLAG_MT_AUTOREL)) | ||||
evdev_send_mt_autorel(evdev); | evdev_send_mt_autorel(evdev); | ||||
if (type == EV_SYN && code == SYN_REPORT && evdev->ev_report_opened && | if (type == EV_SYN && code == SYN_REPORT && evdev->ev_report_opened && | ||||
bit_test(evdev->ev_flags, EVDEV_FLAG_MT_STCOMPAT)) | bit_test(evdev->ev_flags, EVDEV_FLAG_MT_STCOMPAT)) | ||||
evdev_send_mt_compat(evdev); | evdev_send_mt_compat(evdev); | ||||
evdev_send_event(evdev, type, code, value); | evdev_send_event(evdev, type, code, value); | ||||
▲ Show 20 Lines • Show All 56 Lines • ▼ Show 20 Lines | |||||
evdev_register_client(struct evdev_dev *evdev, struct evdev_client *client) | evdev_register_client(struct evdev_dev *evdev, struct evdev_client *client) | ||||
{ | { | ||||
int ret = 0; | int ret = 0; | ||||
debugf(evdev, "adding new client for device %s", evdev->ev_shortname); | debugf(evdev, "adding new client for device %s", evdev->ev_shortname); | ||||
EVDEV_LOCK_ASSERT(evdev); | EVDEV_LOCK_ASSERT(evdev); | ||||
if (LIST_EMPTY(&evdev->ev_clients) && evdev->ev_methods != NULL && | if (LIST_EMPTY(&evdev->ev_clients)) { | ||||
evdev->ev_methods->ev_open != NULL) { | |||||
debugf(evdev, "calling ev_open() on device %s", | debugf(evdev, "calling ev_open() on device %s", | ||||
evdev->ev_shortname); | evdev->ev_shortname); | ||||
ret = evdev->ev_methods->ev_open(evdev, evdev->ev_softc); | if (evdev->ev_methods != NULL && | ||||
evdev->ev_methods->ev_open != NULL) | |||||
ret = evdev->ev_methods->ev_open(evdev, | |||||
evdev->ev_softc); | |||||
if (!ret && bit_test(evdev->ev_flags, EVDEV_FLAG_LOCKLESS)) | |||||
evdev_start_notification(evdev); | |||||
} | } | ||||
if (ret == 0) | if (ret == 0) | ||||
LIST_INSERT_HEAD(&evdev->ev_clients, client, ec_link); | LIST_INSERT_HEAD(&evdev->ev_clients, client, ec_link); | ||||
return (ret); | return (ret); | ||||
} | } | ||||
void | void | ||||
evdev_dispose_client(struct evdev_dev *evdev, struct evdev_client *client) | evdev_dispose_client(struct evdev_dev *evdev, struct evdev_client *client) | ||||
{ | { | ||||
debugf(evdev, "removing client for device %s", evdev->ev_shortname); | debugf(evdev, "removing client for device %s", evdev->ev_shortname); | ||||
EVDEV_LOCK_ASSERT(evdev); | EVDEV_LOCK_ASSERT(evdev); | ||||
LIST_REMOVE(client, ec_link); | LIST_REMOVE(client, ec_link); | ||||
if (LIST_EMPTY(&evdev->ev_clients)) { | if (LIST_EMPTY(&evdev->ev_clients)) { | ||||
if (evdev->ev_methods != NULL && | if (evdev->ev_methods != NULL && | ||||
evdev->ev_methods->ev_close != NULL) | evdev->ev_methods->ev_close != NULL) | ||||
evdev->ev_methods->ev_close(evdev, evdev->ev_softc); | evdev->ev_methods->ev_close(evdev, evdev->ev_softc); | ||||
if (evdev_event_supported(evdev, EV_REP) && | if (evdev_event_supported(evdev, EV_REP) && | ||||
bit_test(evdev->ev_flags, EVDEV_FLAG_SOFTREPEAT)) | bit_test(evdev->ev_flags, EVDEV_FLAG_SOFTREPEAT)) | ||||
evdev_stop_repeat(evdev); | evdev_stop_repeat(evdev); | ||||
if (bit_test(evdev->ev_flags, EVDEV_FLAG_LOCKLESS)) | |||||
evdev_stop_notification(evdev); | |||||
} | } | ||||
evdev_release_client(evdev, client); | evdev_release_client(evdev, client); | ||||
} | } | ||||
int | int | ||||
evdev_grab_client(struct evdev_dev *evdev, struct evdev_client *client) | evdev_grab_client(struct evdev_dev *evdev, struct evdev_client *client) | ||||
{ | { | ||||
▲ Show 20 Lines • Show All 56 Lines • ▼ Show 20 Lines | |||||
{ | { | ||||
EVDEV_LOCK_ASSERT(evdev); | EVDEV_LOCK_ASSERT(evdev); | ||||
if (evdev->ev_rep_key != KEY_RESERVED) { | if (evdev->ev_rep_key != KEY_RESERVED) { | ||||
callout_stop(&evdev->ev_rep_callout); | callout_stop(&evdev->ev_rep_callout); | ||||
evdev->ev_rep_key = KEY_RESERVED; | evdev->ev_rep_key = KEY_RESERVED; | ||||
} | } | ||||
} | |||||
static void | |||||
evdev_notification_callout(void *arg) | |||||
{ | |||||
struct evdev_dev *evdev = arg; | |||||
struct evdev_client *client; | |||||
EVDEV_LOCK_ASSERT(evdev); | |||||
if (evdev->ev_note) { | |||||
evdev->ev_note = false; | |||||
LIST_FOREACH(client, &evdev->ev_clients, ec_link) { | |||||
if (evdev->ev_grabber == NULL || | |||||
evdev->ev_grabber == client) | |||||
evdev_notify_event(client); | |||||
} | |||||
} | |||||
evdev_start_notification(evdev); | |||||
} | |||||
static void | |||||
evdev_start_notification(struct evdev_dev *evdev) | |||||
{ | |||||
EVDEV_LOCK_ASSERT(evdev); | |||||
if (evdev_hz > 0) | |||||
callout_reset(&evdev->ev_note_callout, | |||||
evdev_hz > hz ? 1 : hz / evdev_hz, | |||||
evdev_notification_callout, evdev); | |||||
} | |||||
static void | |||||
evdev_stop_notification(struct evdev_dev *evdev) | |||||
{ | |||||
EVDEV_LOCK_ASSERT(evdev); | |||||
callout_stop(&evdev->ev_note_callout); | |||||
} | } | ||||
MODULE_VERSION(evdev, 1); | MODULE_VERSION(evdev, 1); |