Why input_register_device()
function when called from .disconnect
callback of other input device managed in the same kernel module blocks whole system? Is there a way to directly unregister®ister some virtual input device upon disconnection of some real device or somehow workaround this problem?
Here is a kernel module source illustrating the problem on joystick devices:
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/uaccess.h>
#include <linux/major.h>
#include <linux/cdev.h>
#include <linux/signal.h>
#include <linux/input.h>
#include <linux/joystick.h>
#include <linux/list.h>
#include <linux/buffer_head.h>
#include <linux/slab.h>
#include <linux/mutex.h>
#include <linux/sched.h>
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("Author");
MODULE_DESCRIPTION("Testmodule");
MODULE_SUPPORTED_DEVICE("input/jsN");
#define UNIJOY_MINOR_BASE 0
#define UNIJOY_MINORS 16
struct unijoy_inph_source {
long id;
struct input_handle handle;
};
static struct {
struct input_dev *idev;
struct mutex mutex;
} output;
static bool unijoy_inph_match(struct input_handler *, struct input_dev *);
static int unijoy_inph_connect(struct input_handler *, struct input_dev *,
const struct input_device_id *);
static void unijoy_inph_disconnect(struct input_handle *);
static void unijoy_inph_refresh(void);
/* match joysticks */
static const struct input_device_id unijoy_inph_ids[] = {
{
.flags = INPUT_DEVICE_ID_MATCH_EVBIT |
INPUT_DEVICE_ID_MATCH_ABSBIT,
.evbit = { BIT_MASK(EV_ABS) },
.absbit = { BIT_MASK(ABS_X) },
},
{
.flags = INPUT_DEVICE_ID_MATCH_EVBIT |
INPUT_DEVICE_ID_MATCH_ABSBIT,
.evbit = { BIT_MASK(EV_ABS) },
.absbit = { BIT_MASK(ABS_WHEEL) },
},
{
.flags = INPUT_DEVICE_ID_MATCH_EVBIT |
INPUT_DEVICE_ID_MATCH_ABSBIT,
.evbit = { BIT_MASK(EV_ABS) },
.absbit = { BIT_MASK(ABS_THROTTLE) },
},
{
.flags = INPUT_DEVICE_ID_MATCH_EVBIT |
INPUT_DEVICE_ID_MATCH_KEYBIT,
.evbit = { BIT_MASK(EV_KEY) },
.keybit = {[BIT_WORD(BTN_JOYSTICK)] = BIT_MASK(BTN_JOYSTICK) },
},
{
.flags = INPUT_DEVICE_ID_MATCH_EVBIT |
INPUT_DEVICE_ID_MATCH_KEYBIT,
.evbit = { BIT_MASK(EV_KEY) },
.keybit = { [BIT_WORD(BTN_GAMEPAD)] = BIT_MASK(BTN_GAMEPAD) },
},
{
.flags = INPUT_DEVICE_ID_MATCH_EVBIT |
INPUT_DEVICE_ID_MATCH_KEYBIT,
.evbit = { BIT_MASK(EV_KEY) },
.keybit = { [BIT_WORD(BTN_TRIGGER_HAPPY)] = BIT_MASK(BTN_TRIGGER_HAPPY) },
},
{ }
};
MODULE_DEVICE_TABLE(input, unijoy_inph_ids);
static struct input_handler unijoy_inph = {
.match = unijoy_inph_match,
.connect = unijoy_inph_connect,
.disconnect = unijoy_inph_disconnect,
.legacy_minors = true,
.minor = UNIJOY_MINOR_BASE,
.name = "unijoy",
.id_table = unijoy_inph_ids
};
/* Helper function */
static inline long unijoy_make_id(struct input_id id) {
return ((long)id.bustype << 48)
+ ((long)id.vendor << 32)
+ ((long)id.product << 16)
+ ((long)id.version );
}
/* Impl */
static bool unijoy_inph_match(struct input_handler *handler,
struct input_dev *dev) {
/* do not match touchpads */
if (test_bit(EV_KEY, dev->evbit) && test_bit(BTN_TOUCH, dev->keybit))
return false;
/* and digitizers */
if (test_bit(EV_KEY, dev->evbit) && test_bit(BTN_DIGI, dev->keybit))
return false;
return true;
}
static int unijoy_inph_connect(struct input_handler *handler,
struct input_dev *dev,
const struct input_device_id *dev_id) {
struct unijoy_inph_source *source;
long id = unijoy_make_id(dev->id);
int error;
if (id == 0)
return 0;
printk("[unijoy] %ld connected\n", id);
source = kzalloc(sizeof(struct unijoy_inph_source), GFP_KERNEL);
source->id = id;
source->handle.dev = input_get_device(dev);
source->handle.name = "jsN";
source->handle.handler = handler;
source->handle.private = source;
error = input_register_handle(&source->handle);
if (error)
return error;
return 0;
}
static void unijoy_inph_disconnect(struct input_handle *handle) {
struct unijoy_inph_source *source = handle->private;
if (!source)
return;
printk("[unijoy] %ld disconnecting\n", source->id);
input_unregister_handle(handle);
unijoy_inph_refresh();
kfree(source);
}
static void unijoy_inph_refresh() {
printk("[unijoy] refresh is called from .disconnect\n");
if (!mutex_trylock(&output.mutex)) {
printk("[unijoy] failed to acquire a mutex\n");
return;
}
printk("[unijoy] locked\n");
if (output.idev) {
printk("[unijoy] freeing old idev\n");
input_unregister_device(output.idev);
input_free_device(output.idev);
output.idev = 0;
}
printk("[unijoy] registering new idev\n");
output.idev = input_allocate_device();
/* this call blocks everything */
if (input_register_device(output.idev) != 0) {
input_free_device(output.idev);
output.idev = 0;
}
mutex_unlock(&output.mutex);
printk("[unijoy] unlocked\n");
}
/* Entry points */
int __init unijoy_init(void) {
mutex_init(&output.mutex);
return input_register_handler(&unijoy_inph);
}
void __exit unijoy_exit(void) {
mutex_lock(&output.mutex);
if (output.idev) {
input_unregister_device(output.idev);
input_free_device(output.idev);
output.idev = 0;
}
mutex_unlock(&output.mutex);
input_unregister_handler(&unijoy_inph);
}
module_init(unijoy_init);
module_exit(unijoy_exit);