1

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&register 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);
4

1 回答 1

2

如果您阅读 drivers/input/input.c 中的源代码,您会看到disconnect正在调用回调,input_unregister_device()并且在此之前已获取全局input_mutex锁。同样,input_register_device()在将处理程序附加到设备之前,也需要获取相同的互斥锁。

这意味着,如果您尝试调用input_unregister_device()input_register_device()从您的回调函数中调用,您最终将尝试获取已持有互斥锁的互斥锁,并最终导致死锁。

如果您确实需要从断开回调中调用,一种解决方案可能是使用work_structunijoy_inph_refresh()安排它稍后运行。

于 2013-08-02T15:56:44.403 回答